옛날 옛날 언제인지도 기억나지 않는 시절에 생긴 의문이 바로 이 것이였습니다. 그동안, 그냥 저냥 구렁이 담넘어가 듯이 넘어가다가 진행 중인 프로젝트에서 각 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 진행 하도록 하겠습니다.



블로그 이미지

kaki104

/// Microsoft MVP - Windows Development 2014 ~ 2019 5ring /// twitter : @kaki104, facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바