I am trying to create a DataGrid in WPF 4.0 using MVVM...
Features required -
Something like this -
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 -
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..
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
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With