While off reading random blog posts on the tubes, I stumbled upon a post that describes a 101-level scenario of using lookup data in the DataGrid and DataForm with RIA Services.  The author was struggling to find a solution for the problem and was clearly frustrated.

The scenario is very common; let’s use the Cars example where every Model refers to its Make.

image

We want a DataGrid that includes the make name as a column:

image

And we want a DataForm with a ComboBox for the Make:

 image

Let’s tackle this in two steps: first we’ll make the DataGrid show the Make Name and second we’ll make the DataForm show the ComboBox of Makes.

In the metadata class for the Model, annotate the properties as such:

[Display(Order = 20, Name = "Base Price")]
public Nullable<decimal> BasePrice { get; set; }
 
[Display(AutoGenerateField = false)]
public int Id { get; set; }
 
[Include]
[Display(Order = 0)]
public Make Make { get; set; }
 
[Display(AutoGenerateField = false)]
public int MakeId { get; set; }
 
[Display(Order = 10)]
public string Name { get; set; }


This will lead to auto-generated fields in this order:

  1. Make
  2. Name
  3. Base Price

The only problem this leaves for DataGrid is that we don’t want to display the Make, but instead, we want to display the Make.Name.  One solution for this would be to override the ToString() method on Make and have it return the Name.  But let’s handle this in the UI; here’s our XAML:

<riaControls:DomainDataSource AutoLoad="True"
  Name="modelDomainDataSource"
  QueryName="GetModelsQuery">
  <riaControls:DomainDataSource.DomainContext>
  <my:CarContext />
  </riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>
<sdk:DataGrid AutoGenerateColumns="True"
  Height="200"
  ItemsSource="{Binding ElementName=modelDomainDataSource, Path=Data}"
  Width="400"
  AutoGeneratingColumn="DataGrid_AutoGeneratingColumn">
  <sdk:DataGrid.Columns>
  <sdk:DataGridTextColumn x:Name="makeNameColumn"
  Binding="{Binding Path=Make.Name, Mode=OneWay}"
  Header="Make Name" IsReadOnly="True" />
  </sdk:DataGrid.Columns>
</sdk:DataGrid>

Our DomainDataSource is loading our Models and the DataGrid is bound to the results.  We are auto-generating the columns for the grid, but we’re also doing two extra things:

  1. Handling the AutoGeneratingColumn event
  2. Adding a read-only column to display the Make.Name

The code for the AutoGeneratingColumn event handler will prevent the auto-generation of the Make column, since we’re replacing it with a Make Name column:

void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  if (e.PropertyName == "Make")
    {
        e.Cancel = true;
    }
}


In order to get the DataForm to display the ComboBox, we need to customize the Make column’s auto-generation for that control too.  Here’s the XAML for the DataForm:

<tk:DataForm ItemsSource="{Binding Path=Data, ElementName=modelDomainDataSource}"
   Name="modelDataForm"
   AutoCommit="False"
   AutoEdit="False"
   AutoGeneratingField="modelDataForm_AutoGeneratingField" />


And our handler for AutoGeneratingField will create a ComboBox for the editing:

void modelDataForm_AutoGeneratingField(object sender, DataFormAutoGeneratingFieldEventArgs e)
{
  if (e.PropertyName == "Make")
    {
        ComboBox makes = new ComboBox { DisplayMemberPath = "Name" };
        Binding itemsSource = new Binding("Makes") { Source = this.modelDomainDataSource.DomainContext };
        Binding selectedItem = new Binding("Make") { Mode = BindingMode.TwoWay };
        makes.SetBinding(ComboBox.ItemsSourceProperty, itemsSource);
        makes.SetBinding(ComboBox.SelectedItemProperty, selectedItem);
 
        DataField makesField = new DataField
        {
            Content = makes,
            Label = e.Field.Label
        };
 
        e.Field = makesField;
    }
}

The ComboBox has two bindings: ItemsSource is bound to the “Makes” property on the DomainContext, and SelectedItem is two-way bound to the “Make” property on the model.

There’s only one thing left missing from this solution—we need to load all of the Makes into the DomainContext.  We can do this easily from within the page’s constructor:

public Home()
{
    InitializeComponent();
 
    CarContext context = (CarContext)this.modelDomainDataSource.DomainContext;
    context.Load(context.GetMakesQuery());
}


When adding a new model, we can see the Make field shows us the fully-populated list.

image

I would expect this pattern to be followed pretty regularly when dealing with lookup data.  There might be some ways in your applications to condense this effort so that it can be reused across screens and types of lookup data.