I could have run this post in my Adventures while building a Silverlight Enterprise Application series, but I found it to become too easy that way, so I posted it with an actual title. :-) I did run into this while working on the Selector control as mentioned in part #11 of this series, but it's such a general pitfall, I figured it makes sense to put it outside the series.
As you may remember from this article, I placed two datagrid
s inside a usercontrol
to allow users to select items by moving them from one list to another. As many of you already know, one of the features of the DataGrid
control is the option to specify your own columns through the Columns
property. I wanted the users of my control to have the same functionality.
To make everyone's lives as easy as possible, I thought I'd simply copy the DataGrid
behaviour for this. I dug into the documentation and found that the DataGrid.Columns
property is in fact an ObservableCollection
of DataGridColumn
objects. Thus, I defined a dependency property of this type. I have pasted some example code below, but before you dive into that, please let me state that I always use the propdp code snippet for dependency properties and I don't find it useful to clean it up as I consider it to be generated code. If anyone disagrees, please let me know (and why, of course).
So, the dependency property code looked like this:
public ObservableCollection<DataGridColumn> Columns
{
get { return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty); }
set
{
SetValue(ColumnsProperty, value);
UpdateColumns();
}
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns",
typeof(ObservableCollection<DataGridColumn>),
typeof(Selector), new PropertyMetadata(null));
The UpdateColumns
that is called from the properties setter simply copies all the columns from the Columns
property into the Columns
properties of either DataGrid
s. So I figured I would try this out in XAML. The following code snippet is slightly altered to conceal some of the details of the underlying project.
<controls:Selector x:Name="stringsSelector"
SourceHeaderText="My source header"
TargetHeaderText="My target header"
>
<controls:Selector.Columns>
<data:DataGridTextColumn />
</controls:Selector.Columns>
</controls:Selector>
As you can see, I simply tried to add a DataGridTextColumn
to my Columns
collection. As I run this, the InitializeComponent()
method of the page fails with a parse exception, stating that there is some invalid property (any Silverlight developer knows how useful these are :-( ). After I then stop the debugger, Visual Studio does put a blue line under the DataGridTextColum
tag in the XAML, but also with the same exception. It doesn't make much sense as the DataGridTextColumn
class should be valid in this position and I do hope some better exception handling will be introduced for problems like these.
At first, I thought that it may have something to do with the different namespace
s and how they have to work together, but I ruled this out by building a small trial application with a similar scenario. I figured I would also add a collection variant to this application and it worked as well! Then I thought, maybe it had something to do with the DataGridColumn
and I added that as well and it worked! Then I went in and compared the dependency properties and I found out that I had changed the default value of my dependency property in the trial application. I initialized an empty collection there, instead of setting it to null
by default. I changed this in my trial application and it failed with the parser exception again. So here is the changed dependency property as it now works.
public ObservableCollection<DataGridColumn> Columns
{
get { return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty); }
set
{
SetValue(ColumnsProperty, value);
UpdateColumns();
}
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns",
typeof(ObservableCollection<DataGridColumn>),
typeof(Selector), new PropertyMetadata
(new ObservableCollection<DataGridColumn>()));
Conclusion
The dependency property gets accessed by the parser well before it gets set and apparently some member is accessed as well, causing a null
reference exception, which is translated into a parser exception. By simply creating an empty instance, this goes away. A decent exception would have saved me several hours of troubleshooting, so I hope Microsoft handles this in future releases.
UPDATE
Microsoft has put in place a more descriptive error message in this particular case. Also, I should point out that calling code from the property setter is, in the case of a dependency property, not correct, as the setter is not called when accessed from XAML. To fix that, you should implement a PropertyChangedCallback
delegate and pass that as an argument to the PropertyMetadata
. That does not change the fact that you should still provide an instance as a default value for this dependency property.
Thanks for reading in again and I hope you found this article useful. If you have any questions or remarks, please leave them below. I always enjoy reading them and replying whenever needed.