Putting a CheckBox in the cells of an UnboundField of XamDataGrid

This blog post demonstrates the correct way to put a CheckBox into the cell of an unbound field for each row in a XamDataGrid.  The demo application allows the user to dynamically add and remove customers from a 3D Pie Chart, to compare their total sales against each other.  The image below is a screenshot of this demo:

The “Compare” field on the left-hand side is an “unbound field.”  This means that it does not have a corresponding property on the data source object to which it is bound.  You can add as many UnboundField objects into a FieldLayout as you like.

This demo application’s data source is an array of my Customer class, as seen below:

public class Customer
{
    public Customer(int id, string name, double totalSales)
    {
        this.ID = id;
        this.Name = name;
        this.TotalSales = totalSales;
    }

    public int ID { get; private set; }
    public string Name { get; private set; }
    public double TotalSales { get; private set; }
}

The window contains a XamDataGrid and XamChart.  When the user checks a CheckBox in a row of the grid, we add the corresponding Customer’s data to the chart.  Here is the XAML declaration of the XamDataGrid:

<igDP:XamDataGrid
  Grid.Row="0"
  AutoFit="True"
  DataSource="{Binding}"
  InitializeRecord="XamDataGrid_InitializeRecord"
  Theme="Office2k7Black"
  >
  <igDP:XamDataGrid.CommandBindings>
    <!--
    Listen for our custom ShowInChart command.
    -->
    <CommandBinding
      Command="local:Commands.ShowInChart"
      CanExecute="ShowInChart_CanExecute"
      Executed="ShowInChart_Executed"
      />
  </igDP:XamDataGrid.CommandBindings>

  <igDP:XamDataGrid.Resources>
    <!--
    Displays a CheckBox in the ShowInChart field.
    -->
    <Style
      x:Key="ShowInChartCellStyle"
      TargetType="{x:Type igDP:CellValuePresenter}"
      >
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
            <CheckBox
              Command="local:Commands.ShowInChart"
              CommandParameter="{Binding Path=DataItem}"
              HorizontalAlignment="Center"
              IsChecked="{Binding
                  RelativeSource={RelativeSource TemplatedParent},
                  Path=Value,
                  Mode=TwoWay}"
              VerticalAlignment="Center"
              />
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <!--
    Applies currency formatting to cells in the TotalSales field.
    -->
    <Style
      x:Key="TotalSalesEditorStyle"
      TargetType="{x:Type igEditors:ValueEditor}"
      >
      <Setter Property="Format" Value="c" />
    </Style>
  </igDP:XamDataGrid.Resources>

  <igDP:XamDataGrid.FieldLayouts>
    <igDP:FieldLayout>
      <igDP:FieldLayout.Fields>
        <igDP:UnboundField Name="ShowInChart" Label="Compare">
          <igDP:UnboundField.Settings>
            <igDP:FieldSettings
              CellMaxWidth="70" LabelMaxWidth="70"
              CellValuePresenterStyle="{StaticResource ShowInChartCellStyle}"
              />
          </igDP:UnboundField.Settings>
        </igDP:UnboundField>
        <igDP:Field Name="ID">
          <igDP:Field.Settings>
            <igDP:FieldSettings
              CellMaxWidth="40"
              LabelMaxWidth="40"
              />
          </igDP:Field.Settings>
        </igDP:Field>
        <igDP:Field Name="Name" Label="Customer Name" />
        <igDP:Field Name="TotalSales" Label="Total Sales">
          <igDP:Field.Settings>
            <igDP:FieldSettings
              EditorStyle="{StaticResource TotalSalesEditorStyle}"
              />
          </igDP:Field.Settings>
        </igDP:Field>
      </igDP:FieldLayout.Fields>
    </igDP:FieldLayout>
  </igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>

In the grid’s FieldLayouts section, there is one FieldLayout being created.  It’s first field is our UnboundField, named “ShowInChart.”  Notice that its CellValuePresenterStyle property is set to reference the “ShowInChartCellStyle” Style, which is declared above.  That Style applies a ControlTemplate to the CellValuePresenter in each cell of that field.  The ControlTemplate specifies that a CheckBox control should be displayed in the cell.  Looking closely at the CheckBox declaration, you can see that it’s IsChecked property is bound to its TemplatedParent, which in this case is the CellValuePresenter being templated.  We need to establish this two-way binding against the CellvaluePresenter’s Value property so that the CheckBox and cell value are kept in sync with each other.  If we did not do this, the grid would be in an inconsistent state.

When the Window loads up and the XamDataGrid is populated with data, the grid’s InitializeRecord event is handled for each row.  That code initializes the unbound field’s cell value to false, as seen below:

void XamDataGrid_InitializeRecord(object sender, InitializeRecordEventArgs e)
{
    // Initialize each ShowInChart cell to false.
    DataRecord dataRecord = e.Record as DataRecord;
    if (dataRecord != null)
        dataRecord.Cells["ShowInChart"].Value = false;
}

So far so good, but what happens when the user checks or unchecks one of those CheckBoxs?  How does the program know which Customer’s CheckBox was affected, and when it is changed?  This problem can be solved in several ways, but I prefer to use routed commands for this type of task.  In the demo app, the Commands.cs file contains this class:

public static class Commands
{
    public static readonly RoutedCommand ShowInChart = new RoutedCommand();
}

As seen in the XAML declaration previously, the XamDataGrid is given a CommandBinding for the ShowInChart command.  The command’s events are handled in the window’s code-behind, as seen below:

void ShowInChart_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    // The ShowInChart command can execute if
    // the parameter references a Customer.
    e.CanExecute = e.Parameter is Customer;
}

void ShowInChart_Executed(object sender, ExecutedRoutedEventArgs e)
{
    Customer cust = e.Parameter as Customer;
    if (cust != null)
        this.AddOrRemoveCustomer(cust);
}

void AddOrRemoveCustomer(Customer cust)
{
    DataPointCollection dataPoints = this.chart.Series[0].DataPoints;

    string label = "Cust #" + cust.ID;

    // Check if the customer is in the chart already.
    var dataPoint = dataPoints.FirstOrDefault(dp => dp.Label == label);

    if (dataPoint == null)
    {
        // Add the customer to the pie chart.
        string tooltip =
            String.Format(
            "Name: {0} {1} Total Sales: {2}",
            cust.Name,
            Environment.NewLine,
            cust.TotalSales.ToString("c"));

        dataPoint = new DataPoint
        {
            Label = label,
            Value = cust.TotalSales,
            ToolTip = tooltip
        };

        dataPoints.Add(dataPoint);
    }
    else
    {
        // Remove the customer from the pie chart.
        dataPoints.Remove(dataPoint);
    }
}

You can download the demo source code here.  You must have Visual Studio 2008 and NetAdvantage for WPF v7.2 (or later) installed to compile and run this project.


Comments  (4 )

dcabral
on Thu, Jun 2 2011 4:52 PM

When I bind the CheckBox IsChecked property, I can't check the box when I run my app.

Any thoughts on how to get this to work? I'm on the WPF4 controls version 10.3.20103.2196.

tgeisl
on Thu, Nov 3 2011 4:22 AM

@dgrajeda: Check if you set the DataContext correct and your source is not read only. That was my problem first.

tgeisl
on Thu, Nov 3 2011 4:45 AM

Second problem is the event XamDataGrid_InitializeRecord

This is called before and after command ShowInCart is executed. And because XamDataGrid_InitializeRecord sets value to false it looks like you can't check your checkbox.

K K
on Fri, May 31 2013 2:38 PM

Xaml is missing and not properly formatted.

This is exactly what I was looking for but obviously will not be able to make use of it.