본문 바로가기
Windows/C#/WPF

DependencyObject

by hirudev 2021. 5. 7.

 

지난 글(DispatcherObject)의 DispatcherObject 의 상속 클래스에서 한 단계 내려오면 DependencyObject 를 만나볼 수 있다.

 

DispatcherObject 는 메시지 큐잉, 다른 쓰레드에서의 컨트롤 엑세스 처리 등을 관리한다면

 

DependencyObject 는 해당 클래스(혹은 인스턴스 오브젝트)의 속성값을 관리하는 객체이다.

 

Dependency Property 는 일반 CLR Property 와 비교하면 아래와 같은 추가된 기능을 제공한다.

 

  • 리소스의 수치 취득
  • 데이터 바인딩에 대응
  • 스타일에 따른 수치 설정
  • 애니메이션
  • override 가능한 metadata
  • 부모관계에 있는 인스턴스의 프로퍼티를 계승

 

위와 같이 본문에는 적혀있었지만, 결과적으로 프로퍼티 값을 get, set 하는 역활로 쓰인다.

 

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var obj = new Person();
            Debug.WriteLine(obj.GetValue(Person.NameProperty));
            obj.SetValue(Person.NameProperty, "홍길동");
            Debug.WriteLine(obj.GetValue(Person.NameProperty));
        }
    }
    
    public class Person : DependencyObject
    {
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("default name"));
    }

위와 같은 코드를 아래와 같이 바꾸면 흔히쓰는 프로퍼티 코드로 바뀝니다.

 

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var obj = new Person();
            Debug.WriteLine(obj.Name);
            obj.Name = "홍길동";
            Debug.WriteLine(obj.Name);
        }
    }
    
    public class Person : DependencyObject
    {
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("default name"));
        public string Name {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
    }

 

다른면으로 StackPanel 의 경우 stackPanel.Children.Add 와 같이하여 컨트롤을 추가하는데

 

위와 같은 코드는 아래와 같이 작성해볼 수 있다.

 

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var obj = new Person();
            obj.Children.Add(new Person());
            obj.Children.Add(new Person());
            obj.Children.Add(new Person());
        }
    }
    
    public class Person : DependencyObject
    {
        public static readonly DependencyProperty ChildrenProperty =
            DependencyProperty.Register("Children", typeof(List<Person>), typeof(Person), new PropertyMetadata(new List<Person>()));
        public List<Person> Children {
            get { return (List<Person>)GetValue(ChildrenProperty); }
            set { SetValue(ChildrenProperty, value); }
        }
    }

 

마지막으로, 프로퍼티 값 설정의 제한 + 프로퍼티 값이 바뀌었을 경우의 이벤트 발생의 코드를 살펴본다.

 

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var obj = new Person();
            obj.Age = -1;
            obj.Age = 110;
            obj.Age = 50;
        }
    }
    
    public class Person : DependencyObject
    {
        public static readonly DependencyProperty AgeProperty =
            DependencyProperty.Register("Age",
                                        typeof(int),
                                        typeof(Person),
                                        new PropertyMetadata(20,
                                                           AgePropertyChanged,
                                                           _CoerceValueCallback));
        public int Age {
            get { return (int)GetValue(AgeProperty); }
            set { SetValue(AgeProperty, value); }
        }
        private static void AgePropertyChanged(DependencyObject _do, DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("{0} 에서 {1} 으로 변경되었습니다.", e.OldValue, e.NewValue);
        }
        private static object _CoerceValueCallback(DependencyObject _do, object obj)
        {
            int number = (int)obj;
            if (number < 0)
            {
                Debug.WriteLine("_CoerceValueCallback:: number < 0");
                return 0;
            }
            if (number > 100)
            {
                Debug.WriteLine("_CoerceValueCallback:: number > 100");
                return 100;
            }
            return obj;
        }
    }

 

해당 코드는 Person 의 프로퍼티인 Age 의 값일 기본값으로 20 으로 설정해두고

 

_CoerceValueCallback 메소드를 통해 Age 값이 0살부터 100살까지만 저장될 수 있게 설정해두었다.

 

위 상태에서

20에서 -1 값으로 바뀌었을 때 상태 변화

-1에서 110으로 바뀌었을 때 상태 변화

110에서 50으로 바뀌었을 때 상태 변화를 보면 아래의 그림과 같이 나타난다.

 

 

이 밖에도 DependencyProperty.Register 의 오버로딩된 파라미터 인자 중에 ValidateValueCallback 가 있는데

 

이를 이용하는 것 또한 좋아보인다.

 

https://docs.microsoft.com/en-us/dotnet/api/system.windows.validatevaluecallback?view=net-5.0

 

ValidateValueCallback Delegate (System.Windows)

Represents a method used as a callback that validates the effective value of a dependency property.

docs.microsoft.com


DependencyProperty 를 캡슐화하기 위해 private 설정이 필요한 경우 아래와 같이해볼 수 있다.

 

    public class Person : DependencyObject
    {
        private static readonly DependencyPropertyKey AgePropertyKey =
            DependencyProperty.RegisterReadOnly("Age",
                                        typeof(int),
                                        typeof(Person),
                                        new PropertyMetadata(20));
        private static readonly DependencyProperty AgeProperty = Person.AgePropertyKey.DependencyProperty;
        public int Age {
            get { return (int)GetValue(AgeProperty); }
            set { SetValue(AgeProperty, value); }
        }
    }

 

private 로 바뀜으로 변수 속성은 DependencyPropertyKey 로 바뀌었고 Register 또한 RegisterReadOnly 로 바뀌었다.

 


 

DependencyProperty 의 파라미터는 PropertyMetadata 클래스 외에도

PropertyMetadata 를 상속한 UIPropertyMetadata 클래스나 FrameworkPropertymetadata 클래스가 있다.

 

UIPropertyMetadata 클래스는 WPF 의 애니메이션을 무효화하는 기능을 제공한다.

 

UIPropertyMetadata 를 상속하고 있는 FrameworkPropertyMetadata 클래스는 FrameworkPropertyMetadataOptions enum 형태에 따라 레이아웃 시스템의 영향의 유무 설정이나 수치 상속의 유무 설정, 데이터 바인딩의 기본값의 설정 등 WPF의 프레임워크 레벨의 설정을 서포트하고 있다.

 

커스텀 컨트롤을 작성하는 경우 FrameworkPropertyMetadata 클래스의 DependencyProperty 의 파라미터를 사용해도 된다.

 

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var obj1 = new Person { Age = 10, Score = 100 };
            var obj2 = new Person { Age = 30 };

            obj1.AddChild(obj2);

            Debug.WriteLine("{0} {1}", obj1.Age, obj1.Score);
            Debug.WriteLine("{0} {1}", obj2.Age, obj2.Score);
        }
    }
    
    public class Person : FrameworkElement
    {
        public static readonly DependencyProperty AgeProperty =
            DependencyProperty.Register("Age",
                                        typeof(int),
                                        typeof(Person),
                                        new FrameworkPropertyMetadata(20));

        public static readonly DependencyProperty ScoreProperty =
            DependencyProperty.Register("Score",
                                typeof(int),
                                typeof(Person),
                                new FrameworkPropertyMetadata(0,
                                    FrameworkPropertyMetadataOptions.Inherits));
        public int Age {
            get { return (int)GetValue(AgeProperty); }
            set { SetValue(AgeProperty, value); }
        }

        public int Score
        {
            get { return (int)GetValue(ScoreProperty); }
            set { SetValue(ScoreProperty, value); }
        }
        public void AddChild(Person child)
        {
            this.AddLogicalChild(child);
        }
    }

 

 

 

댓글