I have researched this and am stumped: I have a WPF DataGrid, and using an MVVM model. I want to, under certain circumstances, prevent the ability to change a row in the DataGrid. I have researched this and have tried techniques like the one found here.
In practice, this works, however there is an undesirable 'flicker' (it selects the clicked row for a moment then goes back to the previous selection), while this is a close solution I wish there was a more elegant way such as preventing the row change at all in the first place.
I am surprised there is not a SelectionChanging or BeforeSelectionChanged so I could cancel the event from firing; and forcibly preventing the index change in my view model does not seem to make any difference.
How can I do this?
Thank you.
what happens if you take the previewkeydown
and previewmousedown
events and just call e.Handled=true
under your certain circumstance?
Edit:
to satisfy the mvvm style:
you can create a Behavior
with a DependencyProperty
you can bind your circumstance to.
in this behavior you can handle the events and maybe some other stuff, like does the user click on a datarow or header...
DispatcherPriority has been set to ContextIdle. This makes you have flickering, since your SelectedItem is set twice (and it's been rendered twice). Just set priority to Normal, and you'll have flickering no more.
There are some examples for the PreviewMouseDown method here.
General agreement is that reversing the DataGrid.SelectedItem back to its original value inside the datagrid's SelectionChanged handler does not work as expected; all code examples that seem to work postpone the reversal by asking the Dispatcher to schedule it later.
Do you have a CellStyle on your datagrid? For me, the following worked:
xaml:
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="DarkSlateBlue"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
codebehind:
private void MyDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
object x = e.AddedItems[0];
if (x is MyObjectType && x != myViewModel.CurrentItem &&
myViewModel.ShouldNotDeselectCurrentItem())
{
// this will actually revert the SelectedItem correctly, but it won't highlight the correct (old) row.
this.MyDataGrid.SelectedItem = null;
this.MyDataGrid.SelectedItem = myViewModel.CurrentItem;
}
}
}
The point was that the SelectedCellsChanged event fired after the SelectionChanged event -- and in particular, that setting the SelectedItem does not correctly update the SelectedCells which are a read-only property, so more codebehind:
private void MyDataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
List<DataGridCellInfo> selectedCells = MyDataGrid.SelectedCells.ToList();
List<MyObjectType> wrongObjects = selectedCells.Select(cellInfo => cellInfo.Item as MyObjectType)
.Where (myObject => myObject != myViewModel.CurrentItem).Distinct().ToList();
if (wrongObjects.Count > 0)
{
MyDataGrid.UnselectAllCells();
MyDataGrid.SelectedItem = null;
MyDataGrid.SelectedItem = myViewModel.CurrentItem;
}
}
Obviously, the handlers need to be hooked up to the corresponding events on the data grid.
This worked like expected, properly cancelled the selection change if desired, and did not produce a flicker.