I did ran 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 datagrids 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 trough the Columns property. I wanted the users of my control to have the same functionality.
To make everyones 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 a 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, off course).
So, the dependency property code looked like this:
public ObservableCollection<DataGridColumn> Columns
{
get { return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty); }
set
{
SetValue(ColumnsProperty, value);
UpdateColumns();
}
}
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
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 DataGrids. 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 namespaces 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, in stead 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();
}
}
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
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.