지난 글(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);
}
}
'Windows > C#/WPF' 카테고리의 다른 글
애니메이션 (0) | 2021.05.08 |
---|---|
이벤트 핸들러 (0) | 2021.05.07 |
DispatcherObject (0) | 2021.05.06 |
솔루션 내에 다른 프로젝트의 리소스를 얻어오는 방법 (0) | 2021.05.04 |
Property resource 로 등록된 이름을 불러오는 방법 (0) | 2021.05.04 |
댓글