24 May 2010

WPF: One-time, One-way and Two-way Data Binding a DataGrid to a collection

If you are trying to use WPF Data Binding to a collection, MSDN is currently largely useless.

Basic data binding to a collection

The WPF DataGrid can bind to any object that exposes IEnumerable.

I have a DataGrid and I want to bind it to a collection of custom objects. The collection is a List. The first problem is how to expose an object that is really only available in code to XAML. This should work (but for some reason, does not):

MainWindow.xaml.cs
public partial class MainWindow : Window
{
public List<CharSet> CharSets { get; set; }
...
}

MainWindow.xaml
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyApp" Height="478" Width="644"
x:Name="mainWindow">
  <DockPanel Name="dockPanel1">
  <DataGrid Name="charsetGrid" ItemsSource="{Binding ElementName=mainWindow, Path=CharSets}" />
  </DockPanel>
</Window>

Even though the binding engine does not report a problem with getting the CharSets property, the binding does not actually work. Various experiments with setting DataContext did not go any better.

I had to make the following modifications to get this to work:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
    public List<CharSet> CharSets { get; set; }
    ...
    private void LoadData()
    {
        ...
        this.charsetGrid.ItemsSource = CharSets;
    }
}

MainWindow.xaml
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyApp" Height="478" Width="644"
x:Name="mainWindow">
  <DockPanel Name="dockPanel1">
    <DataGrid Name="dataGrid1">
      <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
        <DataGridTextColumn Header="Value" Binding="{Binding Value}" Width="*" />
      </DataGrid.Columns>
    </DataGrid>
  </DockPanel>
</Window>

Name and Value are public properties on the objects in the collection.

One-way and Two-way data binding to the collection

99% of the time when you bind to a collection, you will want to reflect and changes to it automatically. A lot of the time you will also need to update the collection from the DataGrid. In other words, you will be doing one-way or two-way data binding most of the time. It is surprising then that it is so difficult to find good information on how to do this.

Most sources will direct you to use an ObservableCollection, in order to implement INotifyCollectionChanged, but this is unnecessary and diminishes the usefulness of data binding - after all, you want your control to bind seamlessly with your engine code, not have to tailor your engine code to your UI. A List or Collection will provide you with the needed functionality.

The other piece of a advise you will find is to provide property change notifications for the objects that you are storing by making them implement INotifyPropertyChanged. This seems to be completely unnecessary when binding to a List. The code above in fact already provides full two way binding (as long as the DataGrid properties allow it).

No comments: