Dependency Properties are a unique and powerful feature of WPF/Silverlight though they are normally transparent to you and you will, for the most part, not worry about them. However, there are times when creating a Custom Dependency property will be the precise solution to your problem. Sometimes you will need to create a dependency property that is a collection-type. Now there is a very important point that you need to watch when creating a dependency property of this type, and that is the unintended creation of a singleton value.
Let’s see how this works and how you can resolve the issue.
(Insert here the obligatory creation of a temporary Silverlight application.)
After creating my temp application I created two classes. The first is a person class. The second is a Family classes that exposes a Members property that is backed by a dependency property and is of the type List<Person>.
1: using System;
2: using System.Net;
3: using System.Windows;
4: using System.Windows.Controls;
5: using System.Windows.Documents;
6: using System.Windows.Ink;
7: using System.Windows.Input;
8: using System.Windows.Media;
9: using System.Windows.Media.Animation;
10: using System.Windows.Shapes;
11: using System.Collections.ObjectModel;
12: using System.Collections.Generic;
13:
14: namespace SilverlightApplication1
15: { 16: public class Family : DependencyObject
17: { 18: public static readonly DependencyProperty MembersProperty = DependencyProperty.Register(
19: "Bars",
20: typeof(List<Person>),
21: typeof(Family),
22: new PropertyMetadata(new List<Person>()));
23:
24: #region Properties
25:
26: public List<Person> Members
27: { 28: get { return (List<Person>)GetValue(MembersProperty); } 29: set { SetValue(MembersProperty, value); } 30: }
31:
32: #endregion
33:
34: }
35:
36: public class Person
37: { 38: #region Properties
39:
40: public string FirstName { get; set; } 41: public string LastName { get; set; } 42:
43: #endregion
44:
45: #region Constructors
46:
47: public Person(string firstName, string lastName)
48: { 49: FirstName = firstName;
50: LastName = lastName;
51: }
52:
53: #endregion
54: }
55: }
In the application I created a simple method that creates two family objects and adds 3 members to the first and 2 members to the second. I then populate two variables with the corresponding Count from each of the Member property lists like so.
1: private void Button_Click(object sender, RoutedEventArgs e)
2: { 3: Family fam1 = new Family();
4:
5: fam1.Members.Add(new Person("David", "Risko")); 6: fam1.Members.Add(new Person("Wife", "Risko")); 7: fam1.Members.Add(new Person("Kid1", "Risko")); 8:
9: Family fam2 = new Family();
10:
11: fam2.Members.Add(new Person("John", "Doe")); 12: fam2.Members.Add(new Person("Jane", "Doe")); 13:
14: int fam1Count = fam1.Members.Count();
15: int fam2Count = fam2.Members.Count();
16: }
After placing a breakpoint at line 16 I investigated our two count variables. As you can see from the screenshot below we get an unexpected result where both Family objects show that they have 5 members each. Our code clearly adds 3 members to the first family and 2 to the second, so how is this happening?
The key to this is that we have defined a default value for our dependency property in the PropertyMetadata object when we called the Register method (line 22 in first code block). Our Member list is a reference type and so therefore the default value set in the PropertyMetadata is not a default value per Family instance but rather is is the default value for all instances of our Family. This is not the functionality that we are going to want in the vast majority of situations. We need an instance specific list backing our Members property and fortunately there is a straight forward solution. The key is to set your property to a new instance of its type in your constructor. Here is the new code for my Family class constructor that accomplishes this.
1: #region Constructors
2:
3: public Family() : base()
4: { 5: Members = new List<Person>();
6: }
7:
8: #endregion
Now when we run our application we get the following values in our count variables:
As you can see by simply re-initializing our dependency property value in our constructor we get the per instance functionality that we want and need.
Now when your out creating those custom dependency properties for collection-typed values you won’t get caught off guard.
NOTE: After typing this post I found the reference documentation on MSDN that actually covers this from a WPF perspective. The resolution in WPF is slightly different but still applicable.
Tags: .net, silverlight, dependency property