wpf: Exception on DataGrid selection with a particular setup

Problem description

Under a particular set of circumstances, selecting a cell in a DataGrid causes the application crash. The circumstances include

  1. adding data to the backing ObservableCollection<> of the DataGrid by
  2. triggering an ICommand bound to an element inside a MenuItem,
  3. the SelectedItem was null before this trigger, and
  4. the set by WPF to make (the binding of) SelectedItem not null is ignored.

Steps to reproduce

My minimal reproduction is available at TysonMN/ExceptionOnDataGridSelection.

  1. Run that reproduction
  2. Click the MenuItem
  3. Click the Button
  4. 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

2021-03-12_06-58-24_557

Workarounds

  1. Remove the MenuItem but keep the Button. (This is the is the most surprising workaround to me.)
  2. Initialize the backing ObservableCollection<> with an element. Clicking on that element does not reproduce the problem. However, clicking the Button and then clicking either element still reproduces the problem.
  3. Initialize the backing ObservableCollection<> with the value 2 and change the getter of MySelectedItem to always return 2.
  4. 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.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 17 (15 by maintainers)

Commits related to this issue

Most upvoted 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:

    public class DataGridEx : System.Windows.Controls.DataGrid
    {
        /// <summary>
        /// Turn off UI Automation
        /// </summary>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new CustomDataGridExAutomationPeer(this);
        }
    }

    public class CustomDataGridExAutomationPeer : FrameworkElementAutomationPeer
    {
        public CustomDataGridExAutomationPeer(FrameworkElement owner)
            : base(owner) { }

        protected override string GetNameCore()
        {
            return "DataGridExAutomationPeer";
        }

        protected override AutomationControlType GetAutomationControlTypeCore()
        {
            return AutomationControlType.DataGrid;
        }

        protected override List<AutomationPeer> GetChildrenCore()
        {
            return new List<AutomationPeer>();
        }
    }

I think this is the source code corresponding to the last five stack frames in that trace.

  1. DataGridItemAutomationPeer.DataGridItemAutomationPeer(object item, DataGridAutomationPeer dataGridPeer)
  2. DataGridAutomationPeer.CreateItemAutomationPeer(object item)
  3. ItemsControlAutomationPeer.FindOrCreateItemAutomationPeer(object item)
  4. DataGridAutomationPeer.RaiseAutomationSelectionEvents(SelectionChangedEventArgs e)
  5. DataGrid.OnSelectionChanged(SelectionChangedEventArgs e)

My best guess is that this.OwningDataGrid.SelectedItem is null on this line. This makes sense because the value of SelectedItem for my DataGrid is null. There exist an execution path in which that null is passed to constructor of DataGridItemAutomationPeer as its first argument. https://github.com/dotnet/wpf/blob/d49f8ddb889b5717437d03caa04d7c56819c16aa/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Automation/Peers/DataGridAutomationPeer.cs#L412

I 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.