Multiselect DataGrid with CheckBoxes

This blog post has moved to its new location:

http://www.scottlogic.co.uk/blog/wpf/2008/11/multiselect-datagrid-with-checkboxes/

Please update your bookmarks and post any comments to the article at its new location. Thanks.

I am currently very interested in the new WPF DataGrid which was released on codeplex recently. Someone posted an interesting question in the codeplex forums asking about whether it would be possible to configure the DataGrid so that a user can make multiple row selections via checkboxes which are associated with each row. I thought that this sounded like an excellent idea – afterall, the standard behaviour of ctrl-leftclick might be intuitive to the computer savvy, however you can bet the average user (which is certainly the majority) does not know this.

Fortunately the solution is quite simple to implement in WPF:

<dg:DataGrid ItemsSource="{Binding}">
  <dg:DataGrid.RowHeaderTemplate>
    <DataTemplate>
      <Grid>
        <CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay,
                  RelativeSource={RelativeSource FindAncestor,
                  AncestorType={x:Type dg:DataGridRow}}}"/>
      </Grid>
    </DataTemplate>
  </dg:DataGrid.RowHeaderTemplate>
</dg:DataGrid>

The above code provides a DataTemplate for the RowHeader allowing us to render a CheckBox for each row. The IsChecked property uses a RelativeSource binding which navigates the Visual Tree to locate the first ancestor of type DataGridRow. From here the IsSelected property which dictates that the selected state of the row is available. The binding is TwoWay so that ctrl-leftclick behaviour is still visible.

The result is illustrated below:

multiselect

The above example is a concise illustration of the beauty of WPF. Performing the above customisation of the DataGridView in Windows Forms would be at least an afternoons work. However the solution is not without its problems, if you try the above you will find that the checkboxes are rather difficult to click on because the mouse cursor will be displaying the up-down arrow that indicates that it is currently over the gripper that allows you to specify the row height.

Finding the cause of this means delving deep into the DataGrid control templates …

The template for the DataGridRowHeader is given below (in edited form):

<Style x:Key="{x:Type dgp:DataGridRowHeader}"
       TargetType="{x:Type dgp:DataGridRowHeader}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type dgp:DataGridRowHeader}">
        <Grid>
          ... snipped header content + validation error indicator ...
          <Thumb x:Name="PART_TopHeaderGripper"
                 VerticalAlignment="Top"
                 Style="{StaticResource RowHeaderGripperStyle}"/>
          <Thumb x:Name="PART_BottomHeaderGripper"
                 VerticalAlignment="Bottom"
                 Style="{StaticResource RowHeaderGripperStyle}"/>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The header template includes a pair of grippers. The visible state of these is toggled depending on the row’s location (i.e. there is no top gripper on the first row), and whether the DataGrid CanResizeRows is true.

The RowHeaderGripperStyle specifies a Transparent background for the grippers which renders them invisible. If we change this to Green we can see the culprits:

datagridselectproblem

In order to allow our Checkboxes to be clickable, we simply reduce the height of the grippers as follows:

<Style x:Key="RowHeaderGripperStyle" TargetType="{x:Type Thumb}">
  <Setter Property="Height" Value="2"/>
  <Setter Property="Background" Value="Green"/>
  <Setter Property="Cursor" Value="SizeNS"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type Thumb}">
        <Border Padding="{TemplateBinding Padding}"
                Background="{TemplateBinding Background}"/>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Unfortunately this means that we have to duplicate the DataGrid parts; the DataGridRowHeader template and the above style, in order to perform this customisation. The Beauty of XAML and a Beast of a control template!

You can download the demo project, wpfdatagridmultiselect, changing the file extension from .doc to .zip.

Regards, Colin E.

P.S. You can achieve the same effect with a ListView as detailed in this blog post.

About these ads

8 Responses to Multiselect DataGrid with CheckBoxes

  1. klauswiesel says:

    Colin

    this is pretty nice.

    I have a similar feature in my datagrid, I have a property “IsSelected” in the data objects shown in my grid, and I can select rows without getting the rows “paint” in blue which would destroy readibility as I have hyperlinks cols.

    One thing that is disturbing me is the fact, that the “IsSelected” column is inside my datagrid. I like it to be in the row header,like in your example, only difference is that the row header is not bound to the rows’ “IsSelected” but the data object “IsSelected”

    I wonder how this would be solved

    Thanks in advance
    Klaus

  2. Hi Klaus,

    Glad you like it. A few thoughts on your problem …

    You can bind to properties of your object within the Row Header, for example this will show a persons surname:

    [dg:DataGrid ItemsSource="{Binding}"]
    [dg:DataGrid.RowHeaderTemplate]
    [DataTemplate]
    [Grid]
    [TextBox Text="{Binding Path=Item.Surname, Mode=TwoWay,
    RelativeSource={RelativeSource FindAncestor,
    AncestorType={x:Type dg:DataGridRow}}}"/]
    [/Grid]
    [/DataTemplate]
    [/dg:DataGrid.RowHeaderTemplate]
    [/dg:DataGrid]

    or …

    you can modify the DataGridCell style so that the IsSelected property trigger is the colour of your choice:

    [Style x:Key="{x:Type dg:DataGridCell}" TargetType="{x:Type dg:DataGridCell}"]
    [Style.Triggers]
    [Trigger Property="IsSelected" Value="True"]
    [Setter Property="Background" Value="Orange" /]
    [/Trigger]
    [/Style.Triggers]
    [/Style]

    or …

    you can globally modify the highlighted row colour as detailed here:

    http://imduff.wordpress.com/2008/03/01/change-highlight-color-when-an-item-in-a-listview-is-selected/

    Choices, choices.

    Have fun,
    Colin E.

  3. klauswiesel says:

    Colin

    very nice. I think I will go the first and second way as both makes sense.

    One detail: how can I set the column header for the row header? I’d like to show the text “Selected yes/no” as header for the checkbox column

    Regards
    Klaus

    P.S. You are really doing a great job for pushing wpf… keep on!!

  4. Hi,

    There is no out-of-the-box support for a heading above the row headers. To implement this you would have to become quite familiar with the visual layout of the DataGrid:

    http://blogs.msdn.com/vinsibal/archive/2008/08/14/wpf-datagrid-dissecting-the-visual-layout.aspx

    .. allowing you to implement this from scratch. Not an easy task!

    Colin E.

  5. Divya says:

    Hi Colin,

    The feature about datagrid with checkbox column described here is very useful. I had a requirement to display the checkbox in the header in the first column, for display purpose. Is this poassible?

  6. Hi Divya,

    I am not quite sure what you mean by the ‘header in the first column’? How can you use a column header to dictate row selection state?

    Or do you mean that you want the checkbox to live in the first DataGrid column? If so, you should be able to use the same relative source binding given at the start of this article within a CellTemplate. See the following example for creating CellTenmplates:

    http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx#templates

    Regards,
    Colin E.

  7. Divya says:

    Hi Colin,

    Thanks for your response. I’m sorry I hadn’t explained the expected funtionality.
    The requirement is that when the user “checks” / “selects” on the Checkbox (SelectAll) the checkboxes in all rows are are checked & all the rows are highlighted as “Selected” and if the Checkbox (SelectAll) is “unselected” / “unchecked”, the checkboxes in all the rows correspondingly get unchecked & highlighting of all the rows should be undone.
    I tried the code snippet from the website given by you, but I’m facing a problem in it. When I select multiple rows using the checkbox (For Ex: 5 continuous rows) and then if I try to uncheck the checkbox, (for ex:- checkbox pertaining to second row), then checkbox remains checked but the selection of the second row remains, instead the other rows will be unselected & so will be the checkboxes!!
    Is this behavior appearing because the row selection “IsSelected” property is assigned to the checkbox “IsChecked” property and not vice versa?
    I need to highlight the selected row in the datagrid, when the checkbox is checked and undo the highlighting / selection of the row when the checkbox is unchecked.
    Can you please help me around with this?

    Also I wanted to know if it is possible to incorporate a checkbox (instead of gray area) for the “Select All” purpose using a row header template itself?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: