Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF DataGrid, application crash when adding a row

Tags:

wpf

datagrid

I have a wpf datagrid bound to a TrackableCollection. In some rare occations, and for only a few selected users, the application will crash when the user adds a new record by entering the bottom blank row. I have not been able to reproduce the issue, and all I have is a stacktrace of the exception thrown. Has anyone seen anything like this? I have limited knowledge about the automationpeer-classes, but I can confirm that we are not using any of them in our application.

Here's the stacktrace:

System.ArgumentNullException: Value cannot be null.
Parameter name: item
   at System.Windows.Automation.Peers.DataGridAutomationPeer.CreateItemAutomationPeer(Object item)
   at System.Windows.Automation.Peers.ItemsControlAutomationPeer.FindOrCreateItemAutomationPeer(Object item)
   at System.Windows.Automation.Peers.DataGridAutomationPeer.RaiseAutomationSelectionEvents(SelectionChangedEventArgs e)
   at System.Windows.Controls.DataGrid.OnSelectionChanged(SelectionChangedEventArgs e)
   at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
   at System.Windows.Controls.DataGrid.MakeFullRowSelection(Object dataItem, Boolean allowsExtendSelect, Boolean allowsMinimalSelect)
   at System.Windows.Controls.DataGrid.HandleSelectionForCellInput(DataGridCell cell, Boolean startDragging, Boolean allowsExtendSelect, Boolean allowsMinimalSelect)
   at System.Windows.Controls.DataGridCell.OnAnyMouseLeftButtonDown(MouseButtonEventArgs e)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
   at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)

XAML:

<DataGrid Name="OrdreSLinjeGrid" 
          AutoGenerateColumns="False"
          CanUserResizeRows="False"
          CanUserAddRows="{Binding KanLeggeTilOrdreLinjer}"
          HorizontalScrollBarVisibility="Disabled" 
          VerticalScrollBarVisibility="Visible" 
          ItemsSource="{Binding Order.OrderLines, Mode=TwoWay}" CanUserSortColumns="False"
          SelectedItem="{Binding ValgtOrdreLinje}" >
    <DataGrid.Columns>           
        <DataGridTextColumn
            Header="{t:Translate Antall}" 
            TextAlignment="Right" 
            Width="50" 
            HeaderStyle="{StaticResource HøyrejustertColumnHeader}" 
            Binding="{Binding Antall, UpdateSourceTrigger=LostFocus}" />

        <DataGridTextColumn 
            Header="{t:Translate Pris}" 
            Width="60" 
            HeaderStyle="{StaticResource HøyrejustertColumnHeader}" 
            Binding="{Binding Pris, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True}" 
             />
    </DataGrid.Columns>
</DataGrid>

Any input or suggestions will be appreciated.

like image 975
Sven-Erik Avatar asked Jan 16 '13 06:01

Sven-Erik


3 Answers

The problem is related to a bug in DataGridAutomationPeer.RaiseAutomationSelectionEvents internal method which simply speaking does not check if SelectedItem property is null or not. It can easily be reproduced by running "Microsoft Narrator" tool in Windows 7.

Because this is a sealed class there is no easy way to fix it except intercepting this method using Reflection.Emit or any mocking tool out there and even if it's fixed we have no guarantee that Microsoft won't change this method name, signature or behaviour.

I implemented a "good enough" fix by inheriting from DataGrid which will change the way DataGrid is unselected when the SelectedItem is null but only if narrator/touchscreen is present.

Hopefully the bug will be fixed soon. Add reference to UIAutomationProvider if necessary.

using System.Windows.Automation.Peers;
using System.Windows.Controls;

public class TooDataGrid: DataGrid
{
    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {
        if(AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected) ||
            AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementAddedToSelection) ||
            AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection))
        {
            if(SelectedItem == null)
            {
                return;
            }
        }
        base.OnSelectionChanged(e);
    }
}
like image 176
too Avatar answered Oct 19 '22 19:10

too


I know this is pretty old, but there is a good solution to this problem that I came across. You can define a custom converter using the IValueConverter interface:

public class SelectedItemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value ?? DependencyProperty.UnsetValue;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value == null || value.GetType().Name == "NamedObject") ? null : value;
    }
}

This checks if the value is null and if it is, returns DependecyProperty.UnsetValue. As described in the documentation (look at the method descriptions):

A return value of DependencyProperty.UnsetValue indicates that the converter produced no value and that the binding uses the FallbackValue, if available, or the default value instead.

Returning this rather than null should fix the issue.

like image 9
yinnonsanders Avatar answered Oct 19 '22 21:10

yinnonsanders


No need for converters or for using a derived class. Just make sure that the property you are binding to for SelectedItem is initialized with DependencyProperty.UnsetValue. Do not set it to or leave it at the default of null.

The underlying bug should be fixed in the not-too-far future, btw: https://developercommunity.visualstudio.com/content/problem/575165/vs-1604-ide-crash-argumentnullexception.html

like image 2
Patrick Kursawe Avatar answered Oct 19 '22 19:10

Patrick Kursawe