Adding CheckBoxes to the Record Selectors in XamDataGrid

This blog post explains how to add CheckBox controls to the record selectors in XamDataGrid.  We will also see how to add a CheckBox to the header area above the record selectors, so that you can have Check All / Uncheck All behavior.   You can download the demo application here.

The screenshot below shows the demo application in action:


If you were to check Ned's CheckBox, all of the records would be in the checked state, so the header CheckBox would automatically enter the checked state.  If you toggle the state of the header CheckBox, all of the other CheckBoxes beneath it would assume that new check state.  If you click the button on the bottom, a MessageBox would show you which items are checked and unchecked.

In my demo app, the data objects are of type Person, as seen below:

/// <summary>
/// A simple data object that stores
/// information about a person.
/// </summary>
public class Person
{
    public static Person[] GetPeople()
    {
        return new Person[]
        {
            new Person("Jane", 23),
            new Person("Mike", 45),
            new Person("Ned", 67),
        };
    }

    public Person(string name, int age)
    {
        this.Name = name;
        this.Age = age;
    }

    public int Age { get; private set; }
    public string Name { get; private set; }
}

Notice that the Person class does not have any notion of being "checked."  This makes sense, because a person in the real world is not checked or unchecked.  In order to extend the semantics of a person, and make changes to a person observable to other objects, I created a PersonViewModel class.  That class is below:

/// <summary>
/// A presentation-friendly wrapper for the Person
/// class, which has support for being 'checked.'
/// </summary>
public class PersonViewModel : INotifyPropertyChanged
{
    readonly Person _person;
    bool _isChecked;

    public PersonViewModel(Person person)
    {
        _person = person;
    }

    public bool IsChecked
    {
        get { return _isChecked; }
        set
        {
            if (value == _isChecked)
                return;

            _isChecked = value;

            this.OnPropertyChanged("IsChecked");
        }
    }


    public int Age { get { return _person.Age; } }
    public string Name { get { return _person.Name; } }
   

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string prop)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }

    #endregion
}

The XamDataGrid displays a list of PersonViewModel objects.  The DataItem property of each DataRecord in the grid references a PersonViewModel.  The following Style exists in the XamDataGrid's Resources collection, applying a CheckBox to the record selector of each row.

<Style TargetType="{x:Type igDP:RecordSelector}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type igDP:RecordSelector}">
        <CheckBox
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          IsChecked="{Binding Path=DataItem.IsChecked}"
          >
        </CheckBox>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The collection of PersonViewModel objects to which the XamDataGrid is bound exists in an instance of my CommunityViewModel class.  That class serves two purposes.  First, it contains the list of PersonViewModel objects in its Members property.  Second, it provides a get/set property called AllMembersAreChecked.  This property is used to maintain the state of the CheckBox in the header area above the record selectors, and to update the IsChecked property of all PersonViewModel objects when the property is set.  Here is the CommunityViewModel class:

public class CommunityViewModel : INotifyPropertyChanged
{
    public CommunityViewModel(List<PersonViewModel> members)
    {
        this.Members = members;

        foreach (PersonViewModel member in members)
            member.PropertyChanged += delegate
            {
                this.OnPropertyChanged("AllMembersAreChecked");
            };
    }

    public List<PersonViewModel> Members { get; private set; }

    public bool? AllMembersAreChecked
    {
        get
        {
            bool? value = null;
            for (int idx = 0; idx < this.Members.Count; ++idx)
            {
                if (idx == 0)
                {
                    value = this.Members[0].IsChecked;
                }
                else if (value != this.Members[idx].IsChecked)
                {
                    value = null;
                    break;
                }
            }

            return value;
        }
        set
        {
            if (value == null)
                return;

            foreach (PersonViewModel member in this.Members)
                member.IsChecked = value.Value;
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    void OnPropertyChanged(string prop)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }

    #endregion
}

The Style used to apply a CheckBox to the header area is quite similar to the one we saw previously.  One difference is that this Style explicitly sets the target element's Visibility property to ‘Visible', otherwise we would never see the CheckBox.  That Style is below:

<Style TargetType="{x:Type igDP:HeaderPrefixArea}">
  <Setter Property="Visibility" Value="Visible" />
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type igDP:HeaderPrefixArea}">
        <CheckBox
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          IsChecked="{Binding Path=DataPresenter.DataContext.AllMembersAreChecked}"
          >
        </CheckBox>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Download the demo project here.  The demo was built in Visual Studio 2008, using NetAdvantage for WPF v8.1.


Tags /

Comments  (8 )

pfarkas
on Wed, May 6 2009 4:16 PM

If you group the data, clicking on the checkbox in the header prefix are (un)sets everything, not just the ones in the selected group.

What should I change to modify this functionality?

pfarkas
on Wed, May 13 2009 10:28 AM

In the meantime I figured out a solution for my question. Here is a style to solve it:

averbay
on Mon, Oct 26 2009 6:09 AM

Hello,

Is there a way to see the solution which pfarkas posted here? I guess the XAML Tags got stripped out...

averbay
on Mon, Oct 26 2009 6:27 AM

I forgot to mention here:

There is one problem: If you include a filter in your grid, it will also get a CheckBox.

subhashkonda
on Tue, Jul 27 2010 4:26 AM

I had grouped based on the Name and Age properties, i.e. Drag and dropped in to GroupBy Area and it is working fine.

and now i had checked all the records in one Group, but still that Group Header is not getting selected, only when i select all the child records checkboxes all the Group headers are getting selected.

The query here is, how to select the GroupHeader check box if all of its child records are get selected with out changing the sate of other Group header check boxes.

Thanks in advance,

vicero
on Thu, Oct 7 2010 4:14 PM

For all wondering how to get rid of the checkbox in the filter row, add a trigger to the recordselector style:

<Trigger Property="IsFilterRecord" Value="True">

   <Setter TargetName="HeaderCheckBox" Property="Visibility" Value="Collapsed"/>

</Trigger>

kmujeeb
on Sun, Apr 3 2011 9:51 AM

if (value == null)

     return;

I cant find the purpose of above two lines i.e. in a setter of "AllMembersAreChecked"

Can anyone explain this ?

raorags
on Wed, Nov 23 2011 1:45 PM

Hi, Was anyone able to find out the solution to above problem posted by pfarkas ? I am having similar issue.

"If you group the data, clicking on the checkbox in the header prefix are (un)sets everything, not just the ones in the selected group.

What should I change to modify this functionality?

"