블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,318
Today37
Yesterday108

DependencyProperty의 개념 소개 및 사용 방법에 대한 포스트 입니다.



Dependency Property와 Attached Property의 차이점이 뭐지? part0

Dependency Property와 Attached Property의 차이점이 뭐지? part1




이미 이전 part1이 공개되었는데.. 사실은 이 포스트가 먼저 작성이 되어야 하는 것이였네요..그래서 part0입니다. 우선 어려운 주제를 다루기에 앞서 이야기를 하나 풀어 보겠습니다.



사람과 사람은 서로 공통점과 차이점이 존재합니다. 차이점에는 얼굴 크기 모양, 눈 컬러, 머리카락 길이 등이 있고, 공통점에는 손가락 10개, 눈 2개, 머리 1개 등의 정보들이 있습니다. 공통적인 정보들은 대부분 평범한 내용들로 구성됩니다.


그런데 말입니다. 누군가 당신한테 인간의 유전자 수는 몇개 인가요? 라고 물어 본다면 당신은 그 질문의 대답을 어디서 가지고 올까요? 어떤 사람은 유전자 수를 종이에 적어서 뒷 주머니에 넣고 다니고 있어서, 그 질문을 하면 바로 찾아서 알려줄 수 있을 것 입니다. 하지만, 일반적으로 그런 정보를 종이에 적어서 가지고 다니는 사람은 없을 것이라는 생각이 드네요


만약, 그런 사소한 정보들까지 모두 가지고 다녀야 한다면, 아마도 제 뒷주머니의 크기는 아주 큰 백과사전이 들어갈 수 있을 정도가 되어야 할지도 모릅니다. 


그래서, 누군가가 이런 아이디어를 내 놓습니다. 인간에 대한 공통적인 정보는 대부분은 동일하며, 거의 변하지 않는다. 그렇다면, 그런 정보들을 한 곳에 저장해 놓고 찾아서 보면 되지 않을까? 그리고, 예외적인 정보만 종이에 적어서 뒷 주머니에 넣고 다니면서 본다면??

그래서, 사람에 대한 공통적인 정보는 공용 저장소에 저장하고 예외적인 정보는 로컬 저장소에 저장하게 되었다는...이야기가...쿨럭;; (참고 포스트 중 일부를 인용했습니다.)



0. 참고


What is a dependency property?

Dependency Properties

Dependency properties overview


1. 개요


DependencyProperty직역하면 종속속성이라고 합니다. 이 속성을 사용하기 위해서는 DependencyObject를 상속 받은 클래스나 컨트롤에서만 사용이 가능합니다. 이 녀석은 WPF(Windows Presentation Foundation)에서 처음 등장했으며, 이 후 Silverlight, UWP 앱 등 XAML을 기반으로 하는 개발 환경에서 다양하게 사용되고 있습니다. 



2. DependencyProperty 의 기본적인 구성


1) Dependency property: 여기서 이야기 하는 종속속성은 DependencyProperty를 이야기 합니다.


2) Dependency property identifier: 종속속성 식별자는 IsSpinningProperty를 이야기 합니다. 이 식별자는 종속속성의 값을 저장하고, 불러올 때 키 값으로 사용됩니다.


3) CLR(Common Language Runtime) "wrapper": Public bool IsSpinning... 부분을 이야기 하며, 이 종속속성에 값을 넣거나 불러올 때 코드나 XAML에서 실제로 이용하게 됩니다. 그래서, 절대로 내용을 수정하면 않됩니다.


public static readonly DependencyProperty IsSpinningProperty =
    DependencyProperty.Register(
    "IsSpinning", typeof(Boolean),
    typeof(MyCode)
    );
public bool IsSpinning
{
    get { return (bool)GetValue(IsSpinningProperty); }
    set { SetValue(IsSpinningProperty, value); }
}



쉽게 DependencyProperty를 만드는 방법은 Ctrl+K,X를 입력 후에 NetFX30을 선택하시면 선택하는 메뉴가 출력됩니다. part1에 자세한 사항이 나오니 참고하시면 됩니다.



3. DependencyProperty가 GetValue의 동작원리


간단한 예제를 통해서 알아 보도록 하겠습니다.


MainPage.xaml


<Page
    x:Class="PropertySample.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:commons="using:PropertySample.Commons"
    Style="{StaticResource PageStyle}"
    mc:Ignorable="d">
    <Grid
        x:Name="ContentArea"
        Margin="{StaticResource MediumLeftRightMargin}">

        <Grid.RowDefinitions>
            <RowDefinition x:Name="TitleRow" Height="48"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <TextBlock
            x:Name="TitlePage"
            x:Uid="Main_Title"
            Style="{StaticResource PageTitleStyle}" />

        <StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
            <
TextBlock Text="Part0" Margin="10"/>
            <TextBlock Text="Part1" Margin="10"/>
        </StackPanel>

    </Grid>
</Page>


StackPanel 내부에 2개의 TextBlock을 배치했습니다. 너무 붙어있지 못하도록 마진을 10씩 넣었습니다. 겨울이라 서로 멀리 떨어져 있도록...




그런데, TextBlock의 글씨 크기를 더 크게 만들고 싶네요..여기에는 2가지 방법이 있습니다.


하나는 각 TextBlock에 FontSize를 추가하는 일반적인 방법입니다. 물론 스타일로 지정해도 가능합니다.~ 


그렇다면, 다른 하나는??


<Page
    x:Class="PropertySample.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:commons="using:PropertySample.Commons"
    Style="{StaticResource PageStyle}"
    mc:Ignorable="d" FontSize="30">


Page에 FontSize를 추가하는 것 입니다. 


Page에 FontSize를 추가 했는데..어떻게 TextBlock의 FontSize가 커질까요?? 


이 비밀의 열쇄는 처음에 사람으로 이야기를 했던 부분에 있습니다. 


우선 FontSize를 찾아 내는 방법 입니다.



다음은 FontFamily를 찾아 내는 방법 입니다.



이렇게 GetValue가 호출이 될 때 값을 찾아서 반환하는 것을 resolved dynamically 이라고 합니다.


결과는 다음과 같습니다.


4. DependencyProperty를 사용하면 어떤 장점이 있을까요?


어떤 장점이 있길래 이런 복잡한 방법으로 데이터를 가지고 올까요?


1) 메모리 사용량 감소 : 화면에 모든 컨트롤들이 자신의 프로퍼티 값을 가지고 있다면,, 그 양이 얼마나 커질까요? 하지만, 이런 방법으로 누군가 프로퍼티의 값을 요청하면 찾아보고 알려줄 수 있기 때문에 메모리를 더 적게 사용할 수 있습니다.


2) 값 상속 : 비주얼 트리의 하위 컨트롤들은 상위 컨트롤이 가지고 있는 프로퍼티의 값을 상속 받을 수 있습니다. Style들을 이용해서 다양한 형태로 값을 상속 시킬 수 있습니다.


3) 값 변경 알림 사용 : DependencyProperty는 값이 변경될 때 MVVM pattern에서 처럼 INotifyPropertyChanged 이벤트를 발생 시키며, DataBinding도 연결할 수 있고, Callback을 등록해서 사용할 수 있는 다양한 기능을 제공합니다.



5.  다음 항목들을 참고하세요


사용자 정의 컨트롤을 만드는 방법에 대한 포스트 입니다. Part1~Part4번까지 있습니다.

Custom Control 만들기 Part1



6. 소스

https://github.com/kaki104/PropertySample


Posted by MVP kaki104

옛날 옛날 언제인지도 기억나지 않는 시절에 생긴 의문이 바로 이 것이였습니다. 그동안, 그냥 저냥 구렁이 담넘어가 듯이 넘어가다가 진행 중인 프로젝트에서 각 DependencyProperty, AttachedProperty들을 대량으로 사용하고 있어서 이번 기회에 정리를 하기위해 포스트를 하려고 합니다.



0. 참고

Understanding WPF Dependency Property and Attached Property

Dependency properties overview

Attached Properties Overview


셈플 환경은 VisualStudio 2017, UWP 앱입니다. 하지만, 일반적인 내용이기 때문에 WPF에서도 사용이 가능합니다.



1. 공통점


1) 만드는 방법이 비슷합니다. 코드에서 Ctrl+K,X -> NetFX30 -> DependencyProperty, AttachedProperty 2개 중에 하나를 선택하면 기본 골격이 만들어 집니다.


코드 비하인드에서 아래와 같은 화면이 나오면서 입력 할 수 있습니다.





2) DependencyProperty, AttachedProperty의 타입은 모두 DependencyProperty입니다. 그래서, Binding을 사용할 수 있으며  PropertyChanged 이벤트가 발생합니다.



DependencyProperty의 Type은 DependencyProperty이며, Register를 이용해서 등록됩니다.


        public string GuideText
        {
            get { return (string)GetValue(GuideTextProperty); }
            set { SetValue(GuideTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GuideText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GuideTextProperty =
            DependencyProperty.Register("GuideText", typeof(string), typeof(GuideTextBox), new PropertyMetadata(null));



AttachedProperty의 Type은 DependencyProperty이며, RegisterAttached를 이용해서 등록됩니다.


        public static string GetBindablePassword(DependencyObject obj)
        {
            return (string)obj.GetValue(BindablePasswordProperty);
        }

        public static void SetBindablePassword(DependencyObject obj, string value)
        {
            obj.SetValue(BindablePasswordProperty, value);
        }

        // Using a DependencyProperty as the backing store for BindablePassword.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BindablePasswordProperty =
            DependencyProperty.RegisterAttached("BindablePassword", typeof(string), typeof(ExtensionPropertys), new PropertyMetadata(null));




3) 이 녀석들의 용도는 컨트롤, 비헤이비어, 컨버터 등등에 기존에 없는 사용자 정의 속성을 추가해서 사용하는 것 입니다.




2. AttachedProperty의 특징


1) AttachedProperty에 대한 설명을 할 때 사용하는 컨트롤이 WPF에서는 DockPanel 입니다. 그런데 UWP에서는 기본적으로는 제공하지 않으니.. Canvas 컨트롤를 이용하는 방법을 살펴 보도록 하겠습니다. 이 두 컨트롤의 특징은 하위 아이템들이 자신의 위치를 지정할 때 부모의 프로퍼티값을 직접 변경하는 구조라는 것입니다.


아래 빨간색 Rectangle가 보입니다. 이녀석의 위치를 왼쪽에서 100, 위에서 100의 위치로 이동을 시키기 위해서 어떻게 할까요?



Canvas.Top="100" Canvas.Left="100" 이라는 값을 지정하면 해당 위치로 이동 합니다. 여기서 Canvas.Top, Canvas.Left가 바로 AttachedProperty입니다. 자식 컨트롤에서 부모 컨트롤의 속성을 변경해야 내 위치가 변경되는 구조를 가지고 있습니다.


아마도, 이런 구조를 가지는 이유는 Canvas는 어떤 컨트롤이 자식으로 등록될지, 몇개가 등록될지에 대한 관리를 하지 않거나 필요성을 느끼지 못하기 때문인 것 같습니다. 


너~의 위치는 너 스스로 알려줘라..그러면 그 값을 기준으로 화면에 뿌려는 줄께~의 느낌이네요.



혹시 이런식으로 프로퍼티를 사용해야 한다면, AttachedProperty를 사용하셔야 합니다.



2) 서비스 성격의 AttachedProperty 만들기


AttachedProperty는 일반 클래스, 컨트롤, 비헤이비어, 컨버터 등등에 추가하고, 어디서든 값 입력/조회가 가능한 범용성을 가지고 있습니다. 그래서, 이것을 두고 서비스 성격의 프로퍼티라고 이야기를 합니다.


예를 들어서 보면, 비밀번호를 입력하는 PasswordBox는 보안을 이유로 Password 프로퍼티에 바인딩이 불가능 합니다. 또한, PasswordBox를 상속받는 컨트롤을 만들 수도 없습니다. 역시 보안상의 문제이겠죠? 이런 경우에 문제를 해결하는 가장 쉬운 방법은 AttachedProperty를 이용하는 것입니다.


우선 아래와 같이 2개의 AttachedProperty를 만들어 줍니다. BindablePassword는 바인딩을 하기 위한 프로퍼티이고, IsPasswordBindable는 바인딩을 할지 말지 여부를 정하는 녀석입니다. 여기서! 왜 두개나 필요하나요?? 라는 궁금증을 가질 수 있을 것 같습니다. 


그 이유는 BindablePassword 프로퍼티 한개만 가지고는 PasswordBox와 연결을 해서 값을 가지고 올 수 없기 때문입니다. 값이 변경이 되면 변경된 값을 넣어야 하는데..이벤트 연결이 않되어 있기 때문입니다. 그래서, IsPasswordBindable 프로퍼티가 True가 되면, PasswordChanged 이벤트 핸들을 추가하고, 그 이벤트 핸들러 내에서 BindablePassword에 Password 값을 변경하는 형태로 이용합니다.



ExPropertys.cs


    public class ExPropertys
    {
        public static string GetBindablePassword(DependencyObject obj)
        {
            return (string)obj.GetValue(BindablePasswordProperty);
        }

        public static void SetBindablePassword(DependencyObject obj, string value)
        {
            obj.SetValue(BindablePasswordProperty, value);
        }

        // Using a DependencyProperty as the backing store for BindablePassword.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BindablePasswordProperty =
            DependencyProperty.RegisterAttached("BindablePassword", typeof(string), typeof(ExPropertys), new PropertyMetadata(null));



        public static bool GetIsPasswordBindable(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsPasswordBindableProperty);
        }

        public static void SetIsPasswordBindable(DependencyObject obj, bool value)
        {
            obj.SetValue(IsPasswordBindableProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsPasswordBindable.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsPasswordBindableProperty =
            DependencyProperty.RegisterAttached("IsPasswordBindable", typeof(bool), typeof(ExPropertys), new PropertyMetadata(false, IsPasswordBindableChanged));

       

        private static void IsPasswordBindableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //이렇게 사용하는 것은 저도 처음 입니다. VisualStudio 2017에서만 가능할 듯 합니다.
            if (!(d is PasswordBox passwordBox)) return;

            if ((bool) e.NewValue)
            {
                passwordBox.PasswordChanged += PasswordBox_PasswordChanged;
            }
            else
            {
                passwordBox.PasswordChanged -= PasswordBox_PasswordChanged;

            }
        }

        private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var passwordBox = (PasswordBox) sender;
            SetBindablePassword(passwordBox, passwordBox.Password);

        }
    }    


MainPage.xaml


<Page
    x:Class="PropertySample.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:commons="using:PropertySample.Commons"
    Style="{StaticResource PageStyle}" FontSize="20"
    mc:Ignorable="d">
    <Grid
        x:Name="ContentArea"
        Margin="{StaticResource MediumLeftRightMargin}">

        <Grid.RowDefinitions>
            <RowDefinition x:Name="TitleRow" Height="48"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <TextBlock
            x:Name="TitlePage"
            x:Uid="Main_Title"
            Style="{StaticResource PageTitleStyle}" />

        <Grid
            Grid.Row="1"
            Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}">

            <StackPanel VerticalAlignment="Center">
                <TextBlock Text="Property Sample" Margin="20"/>
                <TextBlock Text="PasswordBox"/>
                <PasswordBox x:Name="passwordBox" commons:ExPropertys.IsPasswordBindable="True"/>
                <TextBlock Text="Password you entered"/>
                <TextBlock Text="{Binding (commons:ExPropertys.BindablePassword), ElementName=passwordBox}"/>
            </StackPanel>
        </Grid>
    </Grid>
</Page>


실행 결과





3) AttachedProperty는 단독으로 사용할 수도 있기 범용성이 좋지만, XAML Style에서 Trigger를 사용해서 값을 직접 비교하는 것은 불가능 합니다. 이는 실제 값(Value)를 저장하고 사용하기 위해서 2개의 메소드로 구성되어 있기 때문입니다. 


아래와 같은 2개의 메소드로 구분되어 있는 이유는 값이 설정된 인스턴스의 CLR 네임스페이스의 일부가 아니기 때문입니다.

passwordBox에 값을 저장하고는 있지만, 저장하고 불러올 때 사용하는 이름을 passwordBox는 모릅니다. 그러니 직접 모든 이름을 다 입력해 줘야지만 사용할 수 있습니다.


        public static string GetBindablePassword(DependencyObject obj)
        {
            return (string)obj.GetValue(BindablePasswordProperty);
        }

        public static void SetBindablePassword(DependencyObject obj, string value)
        {
            obj.SetValue(BindablePasswordProperty, value);
        }


        private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var passwordBox = (PasswordBox) sender;
            SetBindablePassword(passwordBox, passwordBox.Password);
        }



4) AttachedProperty를 단독으로 사용하는 경우 이벤트 핸들러 추가/제거를 하는 시점을 특정하기가 어렵다는 것입니다. 위의 예에서 이벤트 핸들러를 제거하기 위해서는, PasswordBox의 Unloaded 이벤트 핸들을 추가로 걸어서 처리를 해야합니다. 



3. 소스


UWP로 만들어진진 소스입니다. Visual Studio 2017에서 오픈 가능합니다.


PropertySample_part1.zip



4. 포스트 쓰는데..


시간이 너무 오래 걸렸네요..처음에 가벼운 마음으로 시작했는데..ㅋㅋ 옆에 계시는 분이 기대를 한다고 해서..신경을 쓰다보니..

빨리 Part2 진행 하도록 하겠습니다.



Posted by MVP kaki104

Windows Template Studio를 이용해서 UWP 앱 개발 시작하기 포스트에서 이어서, 활용 및 심화 과정 포스트를 시작 하도록 하겠습니다. 자세한 설명보다는 간단하게 개념을 정리하고, 상세 내용을 알기 위해서는 어디를 참고하면 되는지에 대한 정보 제공을 목표로 합니다.



1. 프로젝트 생성


File -> New -> Project -> Windows Template Studio -> Name : WTSSample1 -> OK -> Blank -> MVVM Basic -> Next 여기까지는 공통으로 하고, 


Suspend and Resume +, Background Task + 이름은 WTSBackgroundTask 2개를 선택하고 Create를 눌러서 완료 합니다.



2. 백그라운드 작업에 대한 변경 사항


Windows 10 Anniversary Update가 되면서, 추가된 기능 중에 하나 중 기존 Multi Process Model에서만 Background 작업을 할 수 있던 것을 Single Process Model에서도 가능하도록 했습니다. 이 기능을 수행하기 위해서 새로운 앱 라이프 사이클이 필요하게 되었고, Background activity with the Single Process Model 페이지를 참고하면 변경된 사항을 자세하게 확인 할 수 있습니다.


Windows 10 universal Windows platform (UWP) app lifecycle

백그라운드 작업으로 추천되는 작업은 Background Triggers, App Services, Background media playback, Extended Execution가 있습니다.


이제 코드를 추가해서, 확인을 하도록 하겠습니다. 아래 코드에 굵은 글씨대로 추가를 한 후에 앱을 실행 합니다.


App.xaml.cs


        public App()
        {
            InitializeComponent();

            EnteredBackground += App_EnteredBackground;
            Suspending += (s, e) =>
            {
                Debug.WriteLine("Suspending");
            };
            LeavingBackground += (s, e) =>
            {
                Debug.WriteLine("LeavingBackground");

            };
            Resuming += (s, e) =>
            {
                Debug.WriteLine("Resuming");

            };

            // Deferred execution until used. Check https://msdn.microsoft.com/library/dd642331(v=vs.110).aspx for further info on Lazy<T> class.
            _activationService = new Lazy<ActivationService>(CreateActivationService);
        }


        private async void App_EnteredBackground(object sender, EnteredBackgroundEventArgs e)
        {
            var deferral = e.GetDeferral();
            Debug.WriteLine("EnteredBackground");
            await Helpers.Singleton<SuspendAndResumeService>.Instance.SaveStateAsync();
            deferral.Complete();
        }



Output 창 내용을 확인해 보면


LeavingBackground


라는 문자가 출력된 것을 볼 수 있습니다. 즉, 앱이 시작될 때부터 Background에서 시작한 것이라는 것을 알 수 있습니다. 


이제 Suspend를 상태로 변경해 보겠습니다.



Output 창을 보면 EnteredBackground 모드가 실행 된 후 쭈욱 내려가서 마지막에 Suspending이라는 글씨가 출력된 것을 볼 수 있습니다. 


Suspend 상태였다가 앱이 완전히 종료가 되던지, Resume으로 살아날 수 있습니다.

이번에는 Resume 상태로 변경해 보겠습니다.


LeavingBackground
EnteredBackground

'WTSSample.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'E:\samples\WTSSample\WTSSample\bin\x86\Debug\AppX\Newtonsoft.Json.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
...
'WTSSample.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'Anonymously Hosted DynamicMethods Assembly'.
Suspending


Suspending 뒤에 바로 Resuming과 LeveingBackground가 호출되면서 앱이 Activate되는 것을 확인 할 수 있습니다.


Suspending
The thread 0x10d4 has exited with code 0 (0x0).
Resuming
LeavingBackground




일단 기본적인 앱의 라이프사이클을 알 고 있어야, 백그라운드 작업을 만들고 이해하는데 도움이 되기 때문에 언급을 했으며, 추후 백그라운드 작업을 실제로 구현하는 내용은 나중에 다시 설명을 할 예정입니다.



3. 백그라운드 작업이 없을 때는 Suspend, Resume 상태로 변경될 때 무슨 작업을 하나요?


Suspend 상태가 될 때 현재 사용자가 보고 있는 화면의 상태를 저장했다가, Resume이 될 때 처음 상태대로 되 돌리는 작업을 추가 한다면, 앱의 사용자 경험을 높이는 좋은 앱의 기본이 되겠죠.


MainPage에 입력된 택스트 내용을 저장했다가, 앱이 시작했을 때 복구해 주는 방법을 간단하게 만들어 보겠습니다.


아래 내용을 추가합니다.


MainPage.xaml


        <Grid
            Grid.Row="1"
            Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}">
            <!--The SystemControlPageBackgroundChromeLowBrush background represents where you should place your content.
                Place your content here.-->
            <TextBox x:Name="SampleTextbox" VerticalAlignment="Center" HorizontalAlignment="Left" MinWidth="100"/>
        </Grid>


MainPage.xaml.cs


    public sealed partial class MainPage : Page
    {
        public MainViewModel ViewModel { get; } = new MainViewModel();

        public MainPage()
        {
            InitializeComponent();

            if (DesignMode.DesignModeEnabled) return;
            //백그라운드 모드로 전환되는 이벤트 핸들러 추가
            Singleton<SuspendAndResumeService>.Instance.OnBackgroundEntering += Instance_OnBackgroundEntering;

        }

        private void Instance_OnBackgroundEntering(object sender, OnBackgroundEnteringEventArgs e)
        {
            //백그라운드 모드로 전환되면, 입력된 텍스트를 저장합니다.
            e.SuspensionState.Data = SampleTextbox.Text;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            //네비게이션 파라메터가 저장된 내용인지 확인합니다. 이 예제에서는 MainPage가 첫번째 페이지 이기 때문에 발생하지 않습니다.
            if (e.Parameter is SuspensionState == false) return;
            var para = (SuspensionState) e.Parameter;
            SampleTextbox.Text = para.Data.ToString();
        }

        public void SetParameter(SuspensionState saveStateSuspensionState)
        {
            //파라메터 값을 전달 받기 위해서 추가한 메서드 입니다.
            SampleTextbox.Text = saveStateSuspensionState.Data.ToString();
        }

    }



SuspendAndResumeService.cs


        private async Task RestoreStateAsync()
        {
            var saveState = await ApplicationData.Current.LocalFolder.ReadAsync<OnBackgroundEnteringEventArgs>(StateFilename);
            if (saveState?.Target != null && typeof(Page).IsAssignableFrom(saveState.Target))
            {
                //첫번째 페이지와 네비게이션 하려는 페이지가 동일하다면, false가 반환됩니다.
                if (NavigationService.Navigate(saveState.Target, saveState.SuspensionState) == false)
                {
                    //이 경우 프레임에 Content를 뷰로 형변환해서 강제로 파라메터를 전달 합니다.
                    //이 내용은 일반적인 내용이 아니기 때문에 필요에 의해서만 추가되어야 할 것입니다.
                    var view = NavigationService.Frame.Content as MainPage;
                    if (view == null) return;
                    view.SetParameter(saveState.SuspensionState);
                }

            }
        }



App.xaml.cs


            LeavingBackground += async (s, e) =>
            {
                Debug.WriteLine("LeavingBackground");
                var deferral = e.GetDeferral();
                //저장된 내용의 복구를 시도합니다.
                await Singleton<SuspendAndResumeService>.Instance.HandleAsync(e);
                deferral.Complete();

            };


결과 화면 입니다. 한번 입력한 내용이 앱을 재시작 해도 계속 나타납니다.



생각나는데로 하나씩 올리도록 하겠습니다...


Posted by MVP kaki104
TAG lifecycle, UWP

Custom Control 만들기 Part2에 이어서 컨트롤 템플릿을 추가하는 방법에 대해서 알아보자

Part2에서 사용한 소스에서 시작하도록 하겠다.

 

1. 참고 포스트(이전 Part1 1-2 참고 포스트)

Templating 이후 내용 참고

Building Custom Controls for Windows 8 Store apps

http://blogs.u2u.be/diederik/post/2012/12/06/Building-Custom-Controls-for-Windows-8-Store-apps.aspx

 

 

2. 추가 Template

Part1에서 CustomControl을 이용해서 컨트롤을 만들게되면 특징 중에 하나가 단일 style/template를 이용해서 컨트롤의 디자인을 변경할 수 있다고 이야기 했었다. 그래서 Part2와 동일한 SimpleSlider에 Template만 변경해서 전혀 다른 컨트롤 처럼 보이도록 만들어 보자.

 

오늘도 디자인 작업은 블랜드로 진행한다.

 

MainPage.xaml에서 SimpleSlider를 마우스 오른쪽으로 클릭해서 Edit Template -> Edit Copy...를 선택

 

 

스타일 이름은 PacmanSliderStyle로 변경하고 OK를 클릭한다. 

 

 

왼쪽을 보면 방금 입력한 스타일 이름이 보이고, 빨간색 테두리에는 현재 템플릿 수정 모드라는 것을 표시해 준다.

 

 

1) 우선 전체적으로 높이가 너무 작으니 모든 높이를 20으로 변경한다.(각 컨트롤을 클릭해서 Height 프로퍼티를 확인하면서 변경하거나 XAML 소스를 직접 수정)

2) Border를 선택해서 Background 컬러를 Transparent로 변경한다.

3) Border와 Canvas 사이에 Line을 추가한다.** Line은 컨트롤 목록에 없으므로 직접 XAML 코드를 입력해서 추가한다

4) Line의 속성 값을 변경한다.

 

 

<!--X2의 크기는 적당히 입력한다.-->

<Line StrokeThickness="10" Stroke="Blue" StrokeDashArray="1" VerticalAlignment="Center" Margin="0,4,0,0" X2="1000"/>

 

5) PART_Rectangle을 선택하고 Fill을 White로 변경한다.

6) PART_Thumb를 선택하고 마우스 오른쪽 버튼을 눌러서 Edit Template -> Edit a Copy...를 선택해서 스타일 이름을 PackmanThumbStyle로 변경하고 OK를 누른다.

 

 

7) 컨트롤 템플릿에 포함되어있는 VisualState는 삭제는 하지 말고 내용만 지운다. 그리고 Image 컨트롤을 추가한 후 팩맨 이미지를 연결한다. Packman 이미지는 png 파일을 구해서 사용하면 된다.

 

 

 

8) Thumb 최종 소스 - VisualState를 삭제하지 않은 것은 나중에 사용할 수 있기 때문이다. 음음 팩맨이 이동하면서 입도 움직이면 좋을 것 같아서..쿨럭..

 

<ControlTemplate TargetType="Thumb">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="PointerOver"/>
                <VisualState x:Name="Pressed"/>
                <VisualState x:Name="Disabled"/>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Image Height="30" Source="Assets/Pacman_HD.png"/>
    </Grid>
</ControlTemplate>

 

3. 실행 모습

 

 

 

 

4. 소스

 

CustomControlSample_part3.zip

 

 

5. 개선하면 좋을 것 같은 아이디어..

움직일 때 입도 움직였으면.. 비활성 모드가 되면 팩맨이 잠자는 모습으로 변했으면..ㅋㅋㅋ

 

 

 

Posted by MVP kaki104
TAG UWP

Custom Control 만들기 Part1에 이어서 Part2를 진행하는데, 처음부터 다시 만들기는 거시기하니 지난 소스부터 이어서 진행 하도록 하겠다. -0-


1. 참고 포스트(이전 Part1 1-2 참고 포스트)

사이가 춤추고 있는 이미지 다음부터 진행한다.

Building Custom Controls for Windows 8 Store apps

http://blogs.u2u.be/diederik/post/2012/12/06/Building-Custom-Controls-for-Windows-8-Store-apps.aspx



2. VisualState를 이용하기

참고 포스트에서는 IsEnabled 프로퍼티에 따라서 컨트롤에 변화를 주고 있는데 이걸 만들어 보자.


반드시 블랜드를 이용해서 이전 소스를 열자!!

난 블랜드가 싫어!! 코드로 모든 작업을 다 할 수 있어~라고 하는 분은 그냥 Visual Studio에서 작업해도 상관 없다.


아래 화면에서 Resources 탭을 선택하고, SimpleSlider default 옆에 회색 버튼을 클릭해서 편집 모드로 들어간다.


파렛트를 클릭 후에 Edit Template -> Edit Current를 선택해서 Template 편집 화면으로 들어간다.

그리고 왼쪽에 States 탭을 클릭한다.


우리는 NormalState와 DisabledState 2개의 상태를 만들어야 하는데, 우선은 VisualStateGroup을 한개 추가해준다. 노란색 테두리 있는 곳을 클릭하면된다. 그리고 State추가는 빨간색 테두리 부분을 클릭하면 추가할 수 있다. State를 추가한 후 이름을 변경해 준다. 여기까지하면 작업 준비 완료!



먼저 DisabledState를 선택한 후 PART_Thumb을 클릭하고 Property 탭으로 넘어간다.



DisabledState에서는 Thumb의 배경색을 LightGray로 변경하고, Rectangle의 채우기 색을 DimGray로 변경해야한다.

이때 반드시 오른쪽 상단에 빨간색 녹화(?) 상태에서 프로퍼티를 변경해준다.

프로퍼티의 값을 변경 후 Show Timeline 버튼을 누른 후 각 컨트롤을 펼쳐보면 컨트롤의 어떤 프로퍼티에 변경 사항이 생겼는지 확인 할 수 있다.



이제 NormalState와 DisabledState를 번갈아가며 클릭해 보면 각 상태에 따라서 색이 어떻게 변경되는지를 알 수 있다. 여기서는 컨트롤의 색만 변경을 했지만, 컨트롤의 프로퍼티나 에니메이션 기능을 넣을 수도 있다.



3. SimpleSlider.cs 코드 작업


        /// <summary>
        /// 템플릿이 적용될 때 실행
        /// </summary>
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            //이름으로 각 파트 컨트롤 땡김
            _thumb = GetTemplateChild(ThumbPartName) as Thumb;
            if (_thumb == null) return;
            //이벤트 연결
            _thumb.DragDelta += _thumb_DragDelta;
            _rectangle = GetTemplateChild(RectanglePartName) as Rectangle;
            //사이즈 체인지 이벤트 연결
            SizeChanged += SimpleSlider_SizeChanged;
            //IsEnabled 체인지 이벤트 연결
            IsEnabledChanged += SimpleSlider_IsEnabledChanged;

        }
        /// <summary>
        /// IsEnabed 변경 이벤트 처리
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SimpleSlider_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            VisualStateManager.GoToState(this, IsEnabled ? "NormalState" : "DisabledState", true);
        }


위에 굵은 글씨 부분을 SimpleSlider.cs에 추가하면 된다.



4. MainPage.xaml 수정


    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="115*" />
        </Grid.RowDefinitions>
        <TextBlock Text="CustomControl Sample" Style="{StaticResource TitleTextBlockStyle}" />
        <StackPanel VerticalAlignment="Center" Grid.Row="1" Margin="10,0">
            <local:SimpleSlider x:Name="simpleSlider" Height="20" Maximum="100" Value="50" />
            <TextBlock Text="{Binding Value, ElementName=simpleSlider}" />
            <ToggleSwitch Header="SimpleSlider"
                          IsOn="{Binding IsEnabled, ElementName=simpleSlider, Mode=TwoWay}" />

        </StackPanel>
    </Grid>



5. 실행 결과






6. 소스


CustomControlSample_part2.zip



Posted by MVP kaki104
TAG UWP

앱을 만들다 보면, 기본 컨트롤로는 표현하기 힘든 내용이나, 개성있는 나만의 컨트롤을 만들어서 사용하고 싶을 때가 있다. 그럴 때 사용할 사용자 정의 컨트롤에는 어떤 종류가 있으며 어떻게 만들어야 하는지에 대해서 알아보자.

 

 

1. 참고 포스트

1-1. Building C# custom controls in WinRT Metro

http://blogs.u2u.be/diederik/post/2012/01/21/Building-C-custom-controls-in-WinRT-Metro.aspx

 

1-2. Building Custom Controls for Windows 8 Store apps

http://blogs.u2u.be/diederik/post/2012/12/06/Building-Custom-Controls-for-Windows-8-Store-apps.aspx

 

1-3. A Radial Gauge for Universal Windows Apps

http://blogs.u2u.be/diederik/post/2014/04/14/A-Radial-Gauge-for-Universal-Windows-Apps.aspx

 

 

2. 사용자 정의 컨트롤의 종류

사용자 정의 컨트롤은 UserControl과 CustomControl를 이용해서 만들 수 있다.

 

The differences between CustomControls and UserControls

http://www.wpftutorial.net/customvsusercontrol.html

 

위의 포스트에 내용을 간추려 보면 아래와 같다.

 

UserControl (Composition)

. 존재하는 여러 컨트롤을 조합해서 그룹 형태로 만드는 것

. XAML과 비하인드 코드로 구성 - UI와 코드가 함께 있음

. 여러 컨트롤을 조합하기 때문에 단일 Style/template 를 사용할 수 없음

. UserControl에서 도출되었기 때문에 기본 특성을 상속 받음 - CustomControl에 비해 무거움

 

 

 

CustomControl (Extending an existing control)

. 기존 컨트롤에 기능을 추가 확장하는 것 - 여러 컨트롤을 하나의 묶음 처럼 사용할 수도 있음

. XAML과 비하인드 코드가 분리되어 있음 - 1개의 기본 style이 Themes/Generic.xaml에 존재

. 단일 style/template 를 사용할 수 있음

. Control에서 도출되었기 때문에 UserControl에 비해 가벼움

 

 

 

3. 사용자 정의 컨트롤 만들기 시작

1-1번 포스트가 WinRT Metro앱에서 사용하는 컨트롤을 만드는 예제이기는 하지만, 개발자 경험은 상속된다! 그러므로..동일한 방법으로 만들면 된다.

 

 기본 컨셉

 

 

File -> New Project

 

 

프로젝트 폴더에서 마우스 오른쪽 버튼눌러서

Add -> New Item

 

 

컨트롤 이름은 SimpleSlider

이렇게 추가를 하면 프로젝트에 Themes 폴더와 Generic.xaml 파일이 자동으로 추가된다.

 

 

4. 의존성 프로퍼티 추가

 

SimpleSlider.cs 파일을 열고 SimpleSlider class안에 커서를 두고

 

Ctrl+K,X를 입력해서 NetFX30을 선택하면

 

Define a DependencyProperty 선택

 

아래와 같이 변경한다.

 

        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

 

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(SimpleSlider), new PropertyMetadata(0.0, OnValueChanged));

 

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var customSlider = (SimpleSlider) d;
            customSlider.UpdateControls();

        }


 

디펜던시 프로퍼티는 디자인 타임에 오른쪽 프로퍼티 창에 표시되는 녀석이다.

그리고 OnValueChanged 메소드는 Value 프로퍼티에 값이 변경되면 실행되는 녀석으로 내부 코딩은 대체적으로 d를 자기 자신으로 받은 후 특정 기능을 실행한다.

 

최소값과 최대값 프로퍼티를 위와 같은 방법으로 추가한다.

 

 

5. 디자인

 

블랜드를 이용해서 프로젝트를 연다. 그리고 MainPage.xaml을 선택하고, 오른쪽 Resources 탭을 선택한다. Generic.xaml을 확장하고 [SimpleSlider default] 오른쪽에 <> 부분을 클릭해서 편집 모드로 들어간다.

 

 

컨트롤 디자인을 하기 위해 아래와 같이 클릭 후 Edit Template -> Edit Current를 클릭한다.

 

노란색 부분을 클릭하면 편집을 종료하고 원래 화면으로 복귀한다.

빨간색 부분이 아이템 템플릿 부분으로, 이곳을 블로그의 Styling 섹션같이 만들어 보자.

 

1) Border를 선택하고 마우스 오른쪽 버튼을 클릭해서 Change Layout Type -> Grid를 선택한다.

 이렇게 변환을 하면 필요없는 프로퍼티들이 지정된다. 소스를 보면서 정리해 준다.

 

 

2) 완성된 XAML 코드

    <Style TargetType="local:SimpleSlider" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:SimpleSlider">
                    <Grid Height="8">
                        <Border Height="8" Background="LightGray"
                                VerticalAlignment="Stretch"/>
                        <Canvas MinHeight="8">
                            <Rectangle x:Name="PART_Rectangle" Fill="Yellow" Height="8" />
                            <Thumb x:Name="PART_Thumb" Height="8" Width="8" Background="Green" />
                        </Canvas>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

 

 

6. 다시 코드 작업

 

처음 그림에서 Thumb과 Rectangle가 중요한 역할을 하기 때문에 해당 컨트롤을 코드에서 직접 제어를 해야한다. 그래서,  TemplatePart라고 속성을 추가하고, OnApplyTemplate()에서 각 내부 변수들에 GetTemplateChild를 이용해서 인스턴스를 땡겨온다.

 

    [TemplatePart(Name = ThumbPartName, Type = typeof (Thumb))]
    [TemplatePart(Name = RectanglePartName, Type = typeof (Rectangle))]
    public sealed class SimpleSlider : Control
    {
        private const string ThumbPartName = "PART_Thumb";
        private const string RectanglePartName = "PART_Rectangle";

 

        private Thumb _thumb;
        private Rectangle _rectangle;

...

...

 

사용자 정의 컨트롤에서 중요한 부분은 OnApplyTemplate()에 대부분 구현이 된다. 특히 이벤트 핸들러를 추가하는 부분이 여기서 들어간다.

 

        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _thumb = GetTemplateChild(ThumbPartName) as Thumb;
            if (_thumb == null) return;
            _thumb.DragDelta += _thumb_DragDelta;
            _rectangle = GetTemplateChild(RectanglePartName) as Rectangle;
            SizeChanged += SimpleSlider_SizeChanged;
        }

 

나머지 코드는 위치 이동하고 크기 조정하는 코드들이다. 전체 소스를 참고한다.

 

6. 완성된 셈플 화면

 

 

7. 개선할 사항

썸을 마우스로 클릭해서 드래그 해야지만 값이 변경되는데, 그걸 그냥 마우스 클릭해서 값이 변경되도록 하면 좋을 듯하다. 다음에는 1-2 블로그를 따라해 보기로 하겠다

 

 

CustomControlSample.zip

 

 

Posted by MVP kaki104

OneDrive앱에 Audio play기능을 추가하려는 생각이 들어서 시작한 작업이 한 2주 정도 걸린 듯하다..3주인가? 음..작업을 하면서 여러가지 자료도 찾아보고 알게된 내용들을 정리 하려고 한다.

 

 

 

 


 

0. 참고자료(msdn의 내용들은 uri 중 en-us를 ko-kr로 변경하면 한글로 볼 수 있습니다.)

****Windows 8.1

How to play audio in the background (XAML)

https://msdn.microsoft.com/en-us/library/windows/apps/xaml/jj841209.aspx


Supporting background audio in your Windows 8.1 app

http://blogs.msdn.com/b/johnkenn/archive/2013/12/31/supporting-background-audio-in-your-windows-8-1-app.aspx


Stream Online Audio and Play in Background Windows 8.1

https://code.msdn.microsoft.com/windowsapps/Stream-Online-Audio-and-e4fb5ea1#content


Playing sounds in a Universal Windows MVVM app

http://blogs.u2u.be/diederik/post/2014/07/10/Playing-sounds-in-a-Universal-Windows-MVVM-app.aspx


****Windows Phone 8.1

Overview: Background audio (Windows Phone Store apps)

https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn642090.aspx


Background Audio for Windows Phone 8.1 Sample

https://code.msdn.microsoft.com/windowsapps/BackgroundAudio-63bbc319



1. Windows 8.1, Windows Phone 8.1, Windows 10은 각각 Background play하는 방식이 다르다.

처음에 Windows 8.1 백그라운드 오디오 관련 문서를 읽고, 셈플을 보면서 따라하니 너무 간단했다. 그래서 ..아 이거 너무 쉬운데..뭔가...내 뒷통수를 칠 것 같은데..라는 불안 감을 가지면서 시작했다.


첫번째 멘붕은 Windows Phone 8.1 백그라운드 오디오 관련 문서를 읽어보고 발생했다.  Windows 8.1과는 전혀 다른 background play 방법을 사용해야 한다는 것이다. 쿨럭.. 거기다, Windows 10은 폰과 비슷한 방법으로 사용하는 것을 알 수 있었다.


두번째는 Windows 8.1에서 MediaElement를 이용해서 백그라운드 오디오 플레이를 하다가 다른 페이지로 네비게이션을 했다가, 다시 돌아오면 그 때부터 백그라운드 오디오 플레이가 않된다... 즉, MediaElement는 반드시 xaml에 들어가 있는 상태로 앱이 시작할 때부터 끝날때까지 하나를 사용해야 한다는 것이다.


세번째는 Windows Phone에서 Wi-Fi가 연결된 상태에서는 백그라운드 오디오 플레이를 하면 오류가 발생한다는 것이다. 파일 다운로드 같은 것은 잘되는데..왜 오디오 플레이를 하는데 오류가 발생하는지..



2. Windows 8.1

2-1. MediaElement를 화면에 넣고 사용, ViewModel에서 인스턴스 시켜서는 사용할 수 없다.

2-2. MediaElement 하나를 처음부터 끝까지 사용해야 한다.

2-3. Windows 10 desktop이랑은 방법이 다르다



3. Windows Phone 8.1

3-1. BackgroundMediaPlayer에 인스턴스된  MediaPlayer를 사용한다.

3-2. IBackgroundTask를 이용해서 백그라운드에서 동작하도록 만들어야 한다.

3-3. Foreground app과 Background Task는 BackgroundMediaPlayer 녀석을 이용해서 메시지를 전송한다.

3-4. Wi-Fi에서 Background audio play를하면 0x80072F30 오류가 발생한다.

3-5. Windows 10 mobile과는 방법이 비슷한 것 같다.



4. Windows 8.1 문제 해결

Background player 기본

MediaElement 속성 중에 AudioCategory라는 녀석을 BackgroundCapableMedia로 설정하고, Package.appxmanifest -> Declarations에서 Background Tasks를 하나 추가하고, Supported task types는 Audio를 선택한다. 마지막으로 Entry point는 앱 이름을 입력한다.

정말 간단하게 처리 완료! 더 자세한 사항은 참고에 있는 링크를 이용하면 된다.


* 위에서 언급했던 이슈를 해결해보자. Windows 8.1 스타일의 앱이기 때문에 모든 화면이 네이게이션이 된다. 이런 상태에서 어떻게 MediaElement컨트롤을 화면에 고정 시켜서 사용할 수 있을까?


정답은 Frame에 끼워 넣어서 사용하는 것이다.

위에 참고 링크 중 Playing sounds in a Universal Windows MVVM app 포스트를 보면 그런 내용이 나온다. 아마 이 아저씨도 네비게이션이 되면 백그라운드 플레이가 않되는 것을 보고 맨붕이 오지 않았을까 생각해본다.


스타일

    <!-- Injecting media players on each page -->
    <Style x:Key="RootFrameStyle" TargetType="Frame">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Frame">
                    <Grid>
                        <MediaElement AutoPlay="False"/>
                        <ContentPresenter />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

 

프레임에 스타일 입력

                //새 프레임 생성

                rootFrame = new Frame {CacheSize = 1};
#if WINDOWS_APP
                //Root Frame에 MediaElement를 추가한다.
                rootFrame.Style = Application.Current.Resources["RootFrameStyle"] as Style;

#endif

                // Place the frame in the current Window

                Window.Current.Content = rootFrame;


 

플레이 화면에 진입하면 초기화 모듈을 실행해서 프레임에 들어가있는 MediaElement 컨트롤을 꺼내서 뷰모델에 넣는다.

        public void Initialize()

        {

            try

            {

                var rootGrid = VisualTreeHelper.GetChild(Window.Current.Content, 0);

                var me = VisualTreeHelper.GetChild(rootGrid, 0) as MediaElement;

                if (me == null) return;

                SetMediaElement(me);

            }

            catch (Exception ex)

            {

                Debug.Assert(false, ex.Message);

            }

        }

 


 

* Uri를 이용한 재생 방법

//me : MediaElement 인스턴스, uri : Mp3파일이 존재하는 Uri 경로
me.Source = new Uri(uri);


 

* LocalFile 재생 방법

//GetLocalFileAsync : IStorageFile을 반환하는 사용자 정의 메소드
//me : MediaElement
var file = await GetLocalFileAsync();
if (file == null) return;

var stream = await file.OpenAsync(FileAccessMode.Read);

me.SetSource(stream, file.ContentType);



* 윈도우 10에서 발생하는 최소화 했을 때 백그라운드 플레이가 중지되는 부분에 대해서는 Windows 8.1 app에서는 처리가 불가능 할 것 같다. 기본적으로 Windows Phone 8.1의 Play방법과 비슷하기 때문인데...나중에 UWP으로 포팅할 때 고민해야 할 것 같다.




5. Windows Phone 8.1 문제 해결

기본적인 사용 방법은 참고의 링크를 이용하기 바란다. 여기에 간단히 몇줄 써서 설명할 수 있는 내용이 아니다.


* Sample project에서 처럼 PlayList를 관리하는 추가 프로젝트는 필요 없다. 모든 작업을 BackgroundAudioTask를 통해서 하는 것이 훨씬 쉽다. 가득이나 Background와 Foreground간에 데이터도 수시로 주고 받아야하는데..PlayList까지 끼어들면 머리만 복잡해 진다. PlayList를 BackgroundAudioTask에 넣어서 사용하자.


* BackgroundAudioTask는 Foreground App이 Suspended, Terminated가되어도 계속 실행이 된다. 그러니, 그 내부에서 Play, Pause, Next, Previous ... 등의 작업이 단독으로 가능해야한다. Foreground App은 그냥 UI만 살짝 표시해줄 뿐이다.


* SystemMediaTransportControls에 Play정보를 출력하고, 명령 버튼에 대한 처리를 꼭 해야한다. BackgroundAudioTask 단독으로도 실행되기 때문에, 이 컨트롤과 연결 상태를 유지하는 것이 중요하다.


* Wi-Fi에서 Background audio play를하면 0x80072F30 오류가 발생한다. -> 이 문제에 대해서는 해결 방법이 없다. 우선 내가 가지고 있는 루미아 630에서는 Wifi로 접속이 되어있는지 여부를 확인할 수 있는 방법 없다. (몇 몇 예제 코드들을 이용해서 테스트를 해 보았지만 실패했다.) 그렇다고, App에서 네트워크 관련 부분을 변경 할 수 없기 때문에, 오류가 발생하면 발생한대로 메시지를 출력하는 정도가 전부이다.


* Background audio play를 스트리밍으로 1시간 정도 플래이 하면,  The media resource is not supported : HRESULT=0xC00D001A 오류가 발생한다. 사용자가 다시 앱을 시작하고, 명령을 입력해야 다시 동작하는 시나리오로 만들 수 밖에 없다. 아마도 네트워크 자원을 1시간까지만 사용이 가능한 것으로 판단된다. 반대로, 로컬 파일을 플레이하는 경우에는 1시간 이상 플레이해도 문제가 없다.


* Uri 재생하기

BackgroundMediaPlayer.Current.SetUriSource(new Uri(uri));


 

* Local File 재생하기

//_currentPlayStream : 전역 변수로 재생하기 전에 클리어 시켜주도록 한다.

IStorageFile sf = await StorageFile.GetFileFromPathAsync(localFilePath);
_currentPlayStream = await sf.OpenStreamForReadAsync();
if (_currentPlayStream == null) return;
BackgroundMediaPlayer.Current.SetStreamSource(_currentPlayStream.AsRandomAccessStream());


 

* Windows 10 용 백그라운드 오디오 셈플을 다운받아서 실행해 보았지만, 실행이 되지 않았다. 그래서 코드 내용만 확인했는데 Windows Phone 8.1처럼 BackgroundTask를 생성한 후에 작업을 하도록 되어있다. 이 부분은 나중에 실행 가능한 코드가 나오면 다시 확인해 보아야 할 것 같다.


* 마지막 미친 전투력이 필요한 구간이 있다.

앱 시작 -> 플레이 화면 이동 -> 플레이 -> 화면 off or 다른 화면으로 전환 (예상에는 Suspended가 되는 것 같은데...) -> 다시 화면으로 복귀 -> Resume이 될 것이라고 생각했지만..앱이 처음부터 다시 시작된다.(루미아 630 512MB) 설마 장치마다 다른거 아닌가;;

Visual Studio 2013을 이용한 Debug 모드 Device에서 재현이 않된다.(Suspended, Resume은 재현 가능하지만..이때는 앱이 처음부터 시작하지는 않는다), 에뮬레이터로 재현도 마찬가지...

앱을 디바이스에 배포하고, 실행한 경우에만 발생하기 때문에 , Debug mode 없이 Runtime에서 기존 모든 기능이 정상적으로 동작하도록 만들어야 한다.


1) 여기서 중요한 사항! 이미 백그라운드 서비스는 동작 중이다. 그 백그라운드 서비스와 앱이 다시 연결되어야 한다.

-> 백그라운드 서비스와 앱 사이에 통신을 담당하는 SendMessageToForeground, SendMessageToBackground 서비스를 이용할 때 상대방의 상태를 체그하는 경우 주의해야 한다. 상태 체크하기가 너무 어렵다..어디서 어떻게 되었는지 확인이 않되니..쿨럭

2) 앱이 다시 시작되었을 때 플레이하던 화면으로 다시 보내 주는 방법을 생각해야 한다.

-> 셈플 프로젝트에 있는 ApplicationSettingsHelper 녀석을 이용해서, 플레이 되는 곳의 위치, 플레이 중인 노래 정보 등을 저장해 놓았다가 앱이 다시 시작되면 복구를 시켜 준다.



6. Play, Pause, Next, Previous, 직접 선택의 기능만을 가지고 있는 간단 작업이였는데.. 너무나도 먼길을 돌아온 느낌이다. 아직 완벽한 버전이 아니라서 스토어에 등록을 하지는 못하고 있다. 이제 마무리 된 것 같다. 대부분의 기능이 원하는 정도까지 실행이 된다.

이제 스토어에 등록하는 일만 남은 것 같다.

 



Posted by MVP kaki104

앱에서 트리형태를 만들어야 한다면 어떻게 할 것인가?

우선 기본 컨트롤에는 트리 형태의 컨트롤이 없다.

이 때 부터 머리가 아파오기 시작한다. 자체적으로 만들어서 사용할 것인가..아니면 NuGet에서 찾아볼 것인가.. 3rd party 컨트롤을 이용해야 할 것인가.


이 중에 가장 현실적인 것은 WinRT Xaml Toolkit에 TreeView control을 사용하는 것으로, 윈도우 폰 8.1도 지원한다는 것이 큰 매력 포인트이다.








대략 위와 같은 느낌이다.


그런데, TreeView control을 사용하는데 약간 어려운 점이 있는데 ..........................




1. TreeViewSample


MainPage.xaml


<Page
    x:Class="TreeViewSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TreeViewSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:toolkit="using:WinRTXamlToolkit.Controls"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:data="using:WinRTXamlToolkit.Controls.Data"
    mc:Ignorable="d">

    <d:Page.DataContext>
        <local:MainPageVM/>
    </d:Page.DataContext>

    <interactivity:Interaction.Behaviors>
        <core:EventTriggerBehavior EventName="Loaded">
            <core:InvokeCommandAction Command="{Binding LoadedCommand}"/>
        </core:EventTriggerBehavior>
    </interactivity:Interaction.Behaviors>


    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="133*"/>
            <RowDefinition Height="520*"/>
            <RowDefinition Height="115*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="404*"/>
            <ColumnDefinition Width="557*"/>
            <ColumnDefinition Width="405*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.ColumnSpan="3" TextWrapping="Wrap" Text="TreeView Sample" Style="{StaticResource HeaderTextBlockStyle}" VerticalAlignment="Center" HorizontalAlignment="Center"/>

        <toolkit:TreeView Grid.Column="1" Grid.Row="1"
                          ItemsSource="{Binding TreeViewBindingList}"
                          BorderBrush="White" BorderThickness="1">
            <interactivity:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="SelectedItemChanged">
                    <core:InvokeCommandAction Command="{Binding SelectionChangedCommand}"/>
                </core:EventTriggerBehavior>
            </interactivity:Interaction.Behaviors>
            <toolkit:TreeView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                    <data:DataTemplateExtensions.Hierarchy>
                        <data:HierarchicalDataTemplate ItemsSource="{Binding ValueObject}" />
                    </data:DataTemplateExtensions.Hierarchy>
                </DataTemplate>
            </toolkit:TreeView.ItemTemplate>
        </toolkit:TreeView>
    </Grid>
</Page>



MainPageVM.cs

}
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

namespace TreeViewSample
{
    public class MainPageVM : BindableBase
    {
        private readonly IList<CommonM> _allItems = new List<CommonM>();
        private ICommand _loadedCommand;
        private ICommand _selectionChangedCommand;
        private IList<CommonM> _treeViewBindingList;

        public IList<CommonM> TreeViewBindingList
        {
            get { return _treeViewBindingList; }
            set { SetProperty(ref _treeViewBindingList, value); }
        }

        /// <summary>
        ///     셀렉션 체인지 커맨드
        /// </summary>
        public ICommand SelectionChangedCommand
        {
            get { return _selectionChangedCommand = _selectionChangedCommand ?? new DelegateCommand(obj => { }); }
        }

        public ICommand LoadedCommand
        {
            get
            {
                return _loadedCommand = _loadedCommand ?? new DelegateCommand(obj =>
                {
                    var subList = new List<CommonM>
                    {
                        new CommonM {Id = "01", Name = "Sub01"},
                        new CommonM {Id = "02", Name = "Sub02"},
                        new CommonM {Id = "03", Name = "Sub03"},
                        new CommonM {Id = "04", Name = "Sub04"},
                        new CommonM {Id = "05", Name = "Sub05"}
                    };
                    var add = (from kkk in subList
                        select AddItem((IList) _allItems, kkk)).Count();
                    var root = new CommonM {Id = "00", Name = "Root", ValueObject = subList};
                    AddItem((IList) _allItems, root);

                    TreeViewBindingList = new List<CommonM>
                    {
                        root
                    };
                });
            }
        }

        private bool AddItem(IList list, object item)
        {
            if (list.Contains(item)) return false;
            list.Add(item);
            return true;
        }
    }
}



실행해보자.





화면에 Root가 보이고, 왼쪽 삼각형 모양을 누르면 펼처져서 서브아이템들이 보이고, 다시 누르면 닫혀진다.


이제 사용상의 어려운 점을 이야기를 하면, 삼각형 모양을 눌러서 펼처지고, 닫혀지는 부분에 대한 이벤트가 존재하지 않다는 것이다. 이 부분에 이벤트가 없으니, 처음부터 모든 내용을 알고 있지 않다면, 중간에 아이템을 추가로 넣는 작업을 하기가 어렵다는 것이다.




그렇다면, 여기서 문제!!

어떻게하면, 맨위에 이미지처럼 펼처질 때 각 서브 아이템들의 서브를 구해서 넣을 수 있을까요?

직접 구현을 하시거나, 설명을 하시면 됩니다~



난이도 높음



지금까지의 소스

TreeViewSample.zip



Posted by MVP kaki104

오늘의 문제는 스타일이다. 음..일단 이미지로 보자


Move to 를 선택하면 MenuFlyout 메뉴가 화면에 출력되도록 디자인 했다.

그리고, 디자인 타임에 확인을 해보면 이쁘게 잘 보인다. 좋아좋아~



                <AppBarButton Label="move to ...">
                    <AppBarButton.Flyout>
                        <MenuFlyout>
                            <MenuFlyoutItem Command="{Binding MoveCommand, Mode=OneWay}" Text="local item to diff folder" />
                            <MenuFlyoutItem Command="{Binding MoveItemToYearFolderCommand}" Text="photo item to picture folder" />
                        </MenuFlyout>
                    </AppBarButton.Flyout>
                </AppBarButton>



디자인 작업을 완료하고 에물레이터로 실행을 해보면 아래와 같은 결과가 나온다. 




헉...이건 뭔가;;;쿨럭


아마도 원인은 Light 테마가 적용이 되어있는데도 불구하고, Windows Phone 8.1에서만 Dark 테마로 보이는 것 같다.

 으흠;;;버그인것 같은데..신고를 해야할 것 같기도 하고..음..


하여간 그래서 이 문제를 해결하기 위해, 다 방면으로 별짓 다해 보았지만..해결이 되지 않았다.

그래서, 최종 결론은 기본 스타일을 가져다가 수정해서 사용하기로 했다.


*블랜드나 VS 2013에서 스타일이 리소스로 빠지는 경우는 그걸 사용하면 되고, 그렇지 않은 경우에 아래와 같은 방법을 사용하면 된다.


1. 스타일 가져오기

검색을 통해서 style 페이지를 찾는다. 아무거나 내 마음대로 스타일을 만들어서 적용하면, 실행 중 오류가 발생하기 쉽다. 그렇기 때문에 기본 스타일을 찾아서 사용하는 것이 좋다.


MenuFlyoutPresenter styles and templates

https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn531042.aspx


대부분의 컨트롤들은 위와 같은 스타일 페이지가 존재한다.


2. 스타일 복사해서 내 기본 스타일 딕셔너리에 넣는다.


내가 사용 중인 DefaultStyle.xaml에 아래 내용을 추가했다. 기본 내용에서 수정해야하는 몇가지를 수정했다.


    <SolidColorBrush x:Key="FlyoutBackgroundThemeBrush" Color="#FFFFFFFF"/>
    <SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="#FF4d4d4d"/>
    <x:Double x:Key="FlyoutThemeMaxHeight">718</x:Double>
    <x:Double x:Key="FlyoutThemeMaxWidth">450</x:Double>
    <x:Double x:Key="FlyoutThemeMinHeight">54</x:Double>
    <x:Double x:Key="FlyoutThemeMinWidth">70</x:Double>
    <Thickness x:Key="FlyoutBorderThemeThickness">2</Thickness>
    <Thickness x:Key="MenuFlyoutPresenterThemePadding">20,5,0,5</Thickness>
   
    <Style TargetType="MenuFlyoutPresenter">
        <Setter Property="RequestedTheme" Value="Light" />
        <Setter Property="Background" Value="{ThemeResource FlyoutBackgroundThemeBrush}" />
        <Setter Property="BorderBrush" Value="{ThemeResource FlyoutBorderThemeBrush}" />
        <Setter Property="BorderThickness" Value="{ThemeResource FlyoutBorderThemeThickness}" />
        <Setter Property="Padding" Value="{ThemeResource MenuFlyoutPresenterThemePadding}" />
        <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled" />
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
        <Setter Property="ScrollViewer.VerticalScrollMode" Value="Auto" />
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
        <Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False" />
        <Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False" />
        <Setter Property="ScrollViewer.ZoomMode" Value="Disabled" />
        <Setter Property="MinWidth" Value="{ThemeResource FlyoutThemeMinWidth}" />
        <Setter Property="MaxWidth" Value="{ThemeResource FlyoutThemeMaxWidth}" />
        <Setter Property="MinHeight" Value="{ThemeResource FlyoutThemeMinHeight}" />
        <Setter Property="MaxHeight" Value="{ThemeResource FlyoutThemeMaxHeight}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="MenuFlyoutPresenter">
                    <Border Margin="0,0,0,5" Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                        <ScrollViewer Padding="{TemplateBinding Padding}"
                                  HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
                                  HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
                                  VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
                                  VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
                                  IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
                                  IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
                                  ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}"
                                  AutomationProperties.AccessibilityView="Raw">
                            <ItemsPresenter />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


3. 실행 후 정상 동작하는지 확인하고, 수정할 부분은 수정한다.





기본 컨트롤이 마음에 안들어서 수정하고 싶다면, 이런 방법을 이용해서 수정하는 것을 권한다.


Posted by MVP kaki104

앱을 개발하기 시작하는 단계에서는 빌드 에러 이외에는 크게 신경을 쓰지 않는다. 그러나, 어느 정도 앱이 완성이 된 후에는 주의!라고 뜨는 에러 메시지도 신경을 써 주어야 한다.


그 중에 BindingExpression 에러가 대표적인 그런 종류의 에러라고 할 수 있는데. 딱히 빌드 오류가 발생하지는 않치만, Debug output 창을 한가득 체운다면 상당히 기분이 더럽다.


그렇치 않은가??



에러 내용은 아래와 같다.


Error: Converter failed to convert value of type 'String' to type 'ImageSource'; BindingExpression: Path='BaseImageUri' DataItem='CrossPlatform.Universal.PCL.Models.SkyDriveFolderM'; target element is 'Windows.UI.Xaml.Controls.Image' (Name='null'); target property is 'Source' (type 'ImageSource').


에러 메시지의 내용은 BaseImageUri라는 string 프로퍼티를 Image 컨트롤의 ImageSource에 바인딩 해 놓았는데, BaseImageUri가 null이라서 컨버팅을 할 수 없다는 내용의 오류이다.


BaseImageUri라는 프로퍼티가 항상 값을 가진다면, 저런 오류가 나지 않겠지만, 저 프로퍼티는 원래 값이 존재할 수도, 존재하지 않을 수도 있다는 것이다.


문제의 xaml 코드


<Image VerticalAlignment="Center" Margin="11,21,11,44" HorizontalAlignment="Center" Source="{Binding BaseImageUri" Stretch="UniformToFill"/>



어떻게 이런 메시지가 출력되지 않도록 할 수 있을까?



아래 내용을 참고하자.

DependencyProperty.UnsetValue Field

https://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.unsetvalue(v=vs.110).aspx




문제 해결을 위해 간단하게 Converter를 하나 만들자.

    public class StringToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (string.IsNullOrEmpty(value as string)) return DependencyProperty.UnsetValue;
            return value.ToString();
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }


그리고, 위의 xaml 코드를 컨버터를 사용하도록 변경한다.


<Image VerticalAlignment="Center" Margin="11,21,11,44" HorizontalAlignment="Center" Source="{Binding BaseImageUri, Converter={StaticResource StringToImageSourceConverter}}" Stretch="UniformToFill"/>


그리고 실행하면, 에러 메시지가 더 이상 출력되지 않는 것을 알 수 있다.


Posted by MVP kaki104

티스토리 툴바