Surprised by WPF DataGrid :-)

Here’s what I did. I took the WPF DataGrid and used it in my project. More specifically;

image

Then add a reference to the DataGrid’s assembly ( I have mine in d:\wpftoolkit\ );

image

Then “built a UI” which is an overstatement 🙂

<Window
  x:Class="BlogPost.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Window1"
  Height="600"
  Width="800"
  xmlns:grid="http://schemas.microsoft.com/wpf/2008/toolkit">
  <Grid>
    <Grid.Resources>
      <Style
        x:Key="myStyle"
        TargetType="{x:Type Control}">
        <Setter
          Property="Margin"
          Value="10" />
        <Setter
          Property="FontSize"
          Value="16" />
      </Style>
    </Grid.Resources>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition
        Height="4*" />
      <RowDefinition />
    </Grid.RowDefinitions>
    <TextBox
      x:Name="txtCountry"
      Style="{StaticResource myStyle}"      
      Text="{Binding Country}" />
    <grid:DataGrid
      x:Name="dataGrid"
      Grid.Row="1"
      Style="{StaticResource myStyle}"
      AutoGenerateColumns="True"
      ItemsSource="{Binding}" />
    <Button
      Grid.Row="2"
      Style="{StaticResource myStyle}"
      Content="Submit Changes"
      Click="OnSubmitChanges" />
  </Grid>
</Window>

And then added some LINQ to SQL classes from my Northwind database;

image

and dropped in all the tables;

image

then went and edited my code behind for my XAML file;

 public partial class Window1 : Window, INotifyPropertyChanged
  {
    public Window1()
    {
      InitializeComponent();

      this.Loaded += OnLoaded;
    }
    public string Country 
    {
      get
      {
        return (country);
      }
      set
      {
        country = value;
        Requery();
        FirePropertyChanged("Country");
      }
    }
    void Requery()
    {
      dataGrid.DataContext = dataContext.Customers.Where(c => c.Country == txtCountry.Text);
    }
    void OnLoaded(object sender, RoutedEventArgs e)
    {
      dataContext = new NorthwindDataContext();
      txtCountry.DataContext = this;
    }
    void FirePropertyChanged(string property)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(property));
      }
    }
    void OnSubmitChanges(object sender, EventArgs args)
    {
      // This needs error handling in the real world.
      dataContext.SubmitChanges();
    }
    NorthwindDataContext dataContext;
    string country;

    public event PropertyChangedEventHandler PropertyChanged;
  }

and what surprised me was that I got more than I bargained for with this DataGrid.

I expected it to behave just like the one in Silverlight 2 Beta 2 ( it may have changed for the RC, I haven’t checked yet ) in that I expected the grid to do a fine job of displaying (and updating) the data but I expected it to do nothing around insert or delete because that was my previous experience with the Silverlight 2 Beta 2 DataGrid.

However, here it’s different!

Firstly, I should say that Update works. That is, I can go and bring up the UI, set a country (by typing and tabbing).

image

Note that my Northwind DB is very much broken in terms of the data it contains but, regardless, I can make an update;

image

and then click “Submit Changes” and that just works.

However, I can also do an insert by typing into the blank row at the bottom of the grid to create a new row (note – it’s a bit weird to create a new row here because I have a filter for Spain so I really should force the Country to be Spain or at least default it). I’ll come back to that in a second. In the meantime I can do;

image

and then click Submit Changes and that submits a record for me and I can also highlight that new row,

image

and then press the DELete key and then “Submit Changes” and that deletes the row for me.

Magic! But how on Earth does that work?

The first thing to think about is that I’m setting the DataContext on the DataGrid to an IQueryable<Customer> and then the DataGrid is going to cause the query to be enumerated. How does it do that?

I think what happens is that we set the DataContext to a DataQuery<Customer> which is an internal class of LINQ to SQL implementing IQueryable. This also implements IListSource and the DataGrid calls into that implementation’s GetList() function which looks to ultimately cause the creation of something that is derived from BindingList<Customer> and from thereon we’re in business in that the DataGrid can (and does) call into InsertItem, RemoveItem in order to do its Inserts/Deletes.

I guess the last mystery is how I ensure that when someone has typed “Spain” into my UI then if they insert a row then the country should be set to “Spain” ( at the very least ).

I found a handy InitializingNewItem event and used that to initialise things (just pasting the 1 new/1 modified function below);

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      dataContext = new NorthwindDataContext();
      txtCountry.DataContext = this;
      dataGrid.InitializingNewItem += OnGridRowInsert;
    }
    void OnGridRowInsert(object sender, InitializingNewItemEventArgs e)
    {
      ((Customer)e.NewItem).Country = txtCountry.Text;
    }