Exception on DataGrid selection with a particular setup
See original GitHub issueProblem description
Under a particular set of circumstances, selecting a cell in a DataGrid
causes the application crash. The circumstances include
- adding data to the backing
ObservableCollection<>
of theDataGrid
by - triggering an
ICommand
bound to an element inside aMenuItem
, - the
SelectedItem
wasnull
before this trigger, and - the set by WPF to make (the binding of)
SelectedItem
notnull
is ignored.
Steps to reproduce
My minimal reproduction is available at TysonMN/ExceptionOnDataGridSelection.
- Run that reproduction
- Click the
MenuItem
- Click the
Button
- Click the only cell in the only row and column of the
DataGrid
.
Here are some additional details in case they matter.
- .NET Core Version:
- Version: 5.0.201
- Commit: a09bd5c86c
- Windows version: 1909 (OS Build 18363.1440)
- Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
Expected behavior
I expect that cell to have focus. I do not expect the cell to appear selected. I do not expect an exception to be thrown. I do not expect the application to crash.
Actual behavior
The application crashes because the exception
System.ArgumentNullException: 'Value cannot be null.
Parameter name: item'
is thrown with stack trace
PresentationFramework.dll!System.Windows.Automation.Peers.DataGridItemAutomationPeer.DataGridItemAutomationPeer(object item, System.Windows.Automation.Peers.DataGridAutomationPeer dataGridPeer) Unknown
PresentationFramework.dll!System.Windows.Automation.Peers.DataGridAutomationPeer.CreateItemAutomationPeer(object item) Unknown
PresentationFramework.dll!System.Windows.Automation.Peers.ItemsControlAutomationPeer.FindOrCreateItemAutomationPeer(object item) Unknown
PresentationFramework.dll!System.Windows.Automation.Peers.DataGridAutomationPeer.RaiseAutomationSelectionEvents(System.Windows.Controls.SelectionChangedEventArgs e) Unknown
PresentationFramework.dll!System.Windows.Controls.DataGrid.OnSelectionChanged(System.Windows.Controls.SelectionChangedEventArgs e) Unknown
PresentationFramework.dll!System.Windows.Controls.Primitives.Selector.SelectionChanger.End() Unknown
PresentationFramework.dll!System.Windows.Controls.DataGrid.MakeFullRowSelection(System.Windows.Controls.ItemsControl.ItemInfo info, bool allowsExtendSelect, bool allowsMinimalSelect) Unknown
PresentationFramework.dll!System.Windows.Controls.DataGrid.HandleSelectionForCellInput(System.Windows.Controls.DataGridCell cell, bool startDragging, bool allowsExtendSelect, bool allowsMinimalSelect) Unknown
PresentationFramework.dll!System.Windows.Controls.DataGridCell.OnAnyMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e) Unknown
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) Unknown
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) Unknown
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) Unknown
PresentationCore.dll!System.Windows.UIElement.ReRaiseEventAs(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args, System.Windows.RoutedEvent newEvent) Unknown
PresentationCore.dll!System.Windows.UIElement.OnMouseDownThunk(object sender, System.Windows.Input.MouseButtonEventArgs e) Unknown
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) Unknown
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) Unknown
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) Unknown
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) Unknown
PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent(System.Windows.RoutedEventArgs args) Unknown
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea() Unknown
PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput(System.Windows.Input.InputEventArgs input) Unknown
PresentationCore.dll!System.Windows.Input.InputProviderSite.ReportInput(System.Windows.Input.InputReport inputReport) Unknown
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.ReportInput(System.IntPtr hwnd, System.Windows.Input.InputMode mode, int timestamp, System.Windows.Input.RawMouseActions actions, int x, int y, int wheel) Unknown
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.FilterMessage(System.IntPtr hwnd, MS.Internal.Interop.WindowMessage msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) Unknown
PresentationCore.dll!System.Windows.Interop.HwndSource.InputFilterMessage(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) Unknown
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) Unknown
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) Unknown
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) Unknown
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) Unknown
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) Unknown
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) Unknown
[Native to Managed Transition]
[Managed to Native Transition]
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) Unknown
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) Unknown
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) Unknown
Project.exe!MyNamespace.App.Main() Unknown
Workarounds
- Remove the
MenuItem
but keep theButton
. (This is the is the most surprising workaround to me.) - Initialize the backing
ObservableCollection<>
with an element. Clicking on that element does not reproduce the problem. However, clicking theButton
and then clicking either element still reproduces the problem. - Initialize the backing
ObservableCollection<>
with the value2
and change the getter ofMySelectedItem
to always return2
. - Change
MySelectedItem
into an auto property.
Additional comments
I created this minimal working example based on the reproduction provided in https://github.com/elmish/Elmish.WPF/issues/371.
I think it is reasonable to consider that the backing logic of the SelectedItem
could ignore the value set by WPF.
In that initial issue, the set by WPF was not being completely ignored. Instead, it was being queued with the Dispatcher
for later processing via InvokeAsync
. WPF calls TryGetMember
after calling TrySetMember
(for the same member of course), so from the perspective of WPF at that moment, it appears as though its call to TrySetMember
was ignored. I decided to go back to using Invoke
when handling UI events (c.f. https://github.com/elmish/Elmish.WPF/pull/374), so the present issue is not blocking us over at Elmish.WPF.
Nonetheless, I think it would be reasonable to allow the user to toggle the ability select a row in a DataGrid
. When the toggle is in the “selection-not-possible state”, then the selection behavior would be the same as the reproduction that I have provided.
Issue Analytics
- State:
- Created 3 years ago
- Comments:17 (15 by maintainers)
Top GitHub Comments
As workaround for this bug it can be possible to disable Automation Peers for DataGrid in derived class DataGridEx and using DataGridEx instead of DataGrid see details below:
I think this is the source code corresponding to the last five stack frames in that trace.
My best guess is that
this.OwningDataGrid.SelectedItem
isnull
on this line. This makes sense because the value ofSelectedItem
for myDataGrid
isnull
. There exist an execution path in which thatnull
is passed to constructor ofDataGridItemAutomationPeer
as its first argument. https://github.com/dotnet/wpf/blob/d49f8ddb889b5717437d03caa04d7c56819c16aa/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Automation/Peers/DataGridAutomationPeer.cs#L412I would love to be able to execute my reproduction while being able to break on these lines of code, but I don’t know how to do that.