Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checkbox with DataGrid WPF

I am trying to create a DataGrid in WPF 4.0 using MVVM...

Features required -

  1. Muti - Select rows using a checkbox (single click)
  2. A select all checkbox to check all the checkboxes in the datagrid

Something like this -

enter image description here

It has been 2 days and i am not able to figure out how to solve the problem effectively..

A working example is what I need now ASAP..

I'll highly appreciate if someone has a working solution to share with me...

N please don't tell me to google this thing because none of the things worked out for me...

UPDATE -

  1. I am using AutoGeneration of Columns
  2. I don't want to add "IsSelected" or any of such property in my MODEL..
  3. I am just facing 2 problems -

Firstly, "Select all" Feature i.e. checking all checkboxes on the checkbox click of the one present in the column header...(I am able to select and unselect the datagrid but not able to tick/untick the checkboxes)

Secondly, Multiple selection on mouse click without holding Ctrl Key..

like image 848
Rajat Suneja Avatar asked Jun 14 '13 18:06

Rajat Suneja


2 Answers

When you're working with MVVM, you have to be aware of what is considered data and what is strictly UI.

Is your SelectedItems going to be part of your data, or only your UI?

If it's part of your data, you really should have an IsSelected property on your data model, even if that means extending the data class to include an IsSelected property, or creating a wrapper class that only contains bool IsSelected and object MyDataItem. The first option is probably preferred, since you could keep AutoGenerateColumns="True", and it makes the column bindings simpler.

Then you would just bind your DataGridRow.SelectedItem to the IsSelected property of the data item:

<Style TargetType="{x:Type DataGridRow}">
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>

But if your SelectedItems is only for the UI, or if you are breaking the MVVM pattern for some reason in this instance, than you can create the unbound CheckBox and use some code behind to ensure the CheckBox is correctly synchronized to the SelectedItem.

I did a quick sample app, and here is what my code looked like:

First off, I just added the unbound CheckBox column to the column list using a DataGridTemplateColumn. This will get added before the AutoGenerateColumns list of columns.

<DataGrid x:Name="TestDataGrid" ItemsSource="{Binding Test}" 
          SelectionMode="Extended" CanUserAddRows="False"
          PreviewMouseLeftButtonDown="TestDataGrid_PreviewMouseLeftButtonDown_1">
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox x:Name="TestCheckBox"
                              PreviewMouseLeftButtonDown="CheckBox_PreviewMouseLeftButtonDown" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Second, I added a PreviewMouseDown event to the CheckBox to make it set the IsSelected property of the row.

private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var chk = (CheckBox)sender;
    var row = VisualTreeHelpers.FindAncestor<DataGridRow>(chk);
    var newValue = !chk.IsChecked.GetValueOrDefault();

    row.IsSelected = newValue;
    chk.IsChecked = newValue;

    // Mark event as handled so that the default 
    // DataGridPreviewMouseDown doesn't handle the event
    e.Handled = true;
}

It needs to navigate the VisualTree to find the DataGridRow associated with the clicked CheckBox to select it, and to make life easier I am using some custom VisualTreeHelpers that I have on my blog to find the DataGridRow. You can use the same code, or you can create your own method for searching the VisualTree.

And last of all, if the user clicks on anywhere other than the CheckBox, we want to disable the default DataGrid selection event. This ensures that the IsSelected value will only change when you click on the CheckBox.

There are multiple ways of doing this that will disable the selection at different levels, but to make life simple I just disabled the DataGrid.PreviewMouseLeftButtonDown event if the user didn't click on the CheckBox.

private void TestDataGrid_PreviewMouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
    var chk = VisualTreeHelpers.FindAncestor<CheckBox>((DependencyObject)e.OriginalSource, "TestCheckBox");

    if (chk == null)
        e.Handled = true;
}

I using my custom VisualTreeHelpers again to navigate the visual tree and find out if the CheckBox was clicked on, and cancelling the event if the user clicked on anywhere other than the CheckBox.

As for your 2nd request of adding a CheckBox to SelectAll or UnselectAll items, this would once again be dependent on if your selection is part of the UI or the data.

If it's part of the UI, simply add a CheckBox to the DataGridTemplateColumn.HeaderTemplate, and when it's clicked, loop through the DataGrid.Rows, find the CheckBox in the first column, and check or uncheck it.

If it's part of the data you could still do the same thing (only set the bound value in the DataGrid.Items instead of the CheckBox.IsChecked from the DataGrid.Rows), or you could do as Adolfo Perez suggested, and bind it to a property on the ViewModel.

like image 190
Rachel Avatar answered Oct 16 '22 13:10

Rachel


For an MVVM Solution you could try this:

    <StackPanel>
    <DataGrid ItemsSource="{Binding Path=TestItems}" AutoGenerateColumns="False" Name="MyDataGrid"
              CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridCheckBoxColumn Binding="{Binding IsSelected}" Width="50" >
                <DataGridCheckBoxColumn.HeaderTemplate>
                    <DataTemplate x:Name="dtAllChkBx">
                        <CheckBox Name="cbxAll" Content="All" IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                    </DataTemplate>
                </DataGridCheckBoxColumn.HeaderTemplate>
            </DataGridCheckBoxColumn>
            <DataGridTemplateColumn Header="Name" Width="SizeToCells" IsReadOnly="True">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</StackPanel>

In your ViewModel:

private void PopulateTestItems()
        {
            TestItems = new ObservableCollection<TestItem>();
            for (int i = 0; i < 5; i++)
            {
                TestItem ti = new TestItem();
                ti.Name = "TestItem" + i;
                ti.IsSelected = true;
                TestItems.Add(ti);
            }
        }

        private bool _AllSelected;
        public bool AllSelected
        {
            get { return _AllSelected; }
            set
            {
                _AllSelected = value;
                TestItems.ToList().ForEach(x => x.IsSelected = value);
                NotifyPropertyChanged(m => m.AllSelected);
            }
        }

    private ObservableCollection<TestItem> _TestItems;
    public ObservableCollection<TestItem> TestItems
    {
        get { return _TestItems; }
        set
        {
            _TestItems = value;
            NotifyPropertyChanged(m => m.TestItems);
        }
    }

And finally the sample Model class:

public class TestItem : ModelBase<TestItem>
{
    private string _Name;
    public string Name
    {
        get { return _Name; }
        set
        {
            _Name = value;
            NotifyPropertyChanged(m => m.Name);
        }
    }
    private bool _IsSelected;
    public bool IsSelected
    {
        get { return _IsSelected; }
        set
        {
            _IsSelected = value;
            NotifyPropertyChanged(m => m.IsSelected);
        }
    }
}

Most of the code above should be self-explanatory but if you have any question let me know

like image 34
Adolfo Perez Avatar answered Oct 16 '22 12:10

Adolfo Perez