PowerShell WPF DataGrid: Exception thrown committi

2019-02-10 16:38发布

问题:

I get exception trying to commit empty DataGrid row.

System.NullReferenceException: Object reference not set to an instance of an object.
   at MS.Internal.Data.PropertyPathWorker.DetermineWhetherDBNullIsValid()
   at MS.Internal.Data.PropertyPathWorker.get_IsDBNullValidForUpdate()
   at MS.Internal.Data.ClrBindingWorker.get_IsDBNullValidForUpdate()
   at System.Windows.Data.BindingExpression.ConvertProposedValue(Object value)
   at System.Windows.Data.BindingExpressionBase.UpdateValue()
   at System.Windows.Data.BindingExpression.Update(Boolean synchronous)
   at System.Windows.Data.BindingExpression.UpdateSource()
   at Microsoft.Windows.Controls.DataGridHelper.UpdateSource(FrameworkElement element, DependencyProperty dp)
   at Microsoft.Windows.Controls.DataGridTextColumn.CommitCellEdit(FrameworkElement editingElement)
   at Microsoft.Windows.Controls.DataGridColumn.CommitEdit(FrameworkElement editingElement)
   at Microsoft.Windows.Controls.DataGridCell.CommitEdit()
   at Microsoft.Windows.Controls.DataGrid.OnExecutedCommitEdit(ExecutedRoutedEventArgs e)
   at Microsoft.Windows.Controls.DataGrid.OnExecutedCommitEdit(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.Input.CommandBinding.OnExecuted(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.Input.CommandManager.ExecuteCommandBinding(Object sender, ExecutedRoutedEventArgs e, CommandBinding commandBinding)
   at System.Windows.Input.CommandManager.FindCommandBinding(CommandBindingCollection commandBindings, Object sender, RoutedEventArgs e, ICommand command, Boolean execute)
   at System.Windows.Input.CommandManager.FindCommandBinding(Object sender, RoutedEventArgs e, ICommand command, Boolean execute)
   at System.Windows.Input.CommandManager.OnExecuted(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.UIElement.OnExecutedThunk(Object sender, ExecutedRoutedEventArgs e)
   at System.Windows.Input.ExecutedRoutedEventArgs.InvokeEventHandler(Delegate genericHandler, Object target)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
   at System.Windows.Input.RoutedCommand.ExecuteImpl(Object parameter, IInputElement target, Boolean userInitiated)
   at System.Windows.Input.RoutedCommand.Execute(Object parameter, IInputElement target)
   at Microsoft.Windows.Controls.DataGrid.EndEdit(RoutedCommand command, DataGridCell cellContainer, DataGridEditingUnit editingUnit, Boolean exitEditMode)
   at Microsoft.Windows.Controls.DataGrid.CommitEdit(DataGridEditingUnit editingUnit, Boolean exitEditingMode)
   at Microsoft.Windows.Controls.DataGrid.CommitAnyEdit()
   at Microsoft.Windows.Controls.DataGrid.OnEnterKeyDown(KeyEventArgs e)
   at Microsoft.Windows.Controls.DataGrid.OnKeyDown(KeyEventArgs e)
   at System.Windows.UIElement.OnKeyDownThunk(Object sender, KeyEventArgs e)
   at System.Windows.Input.KeyEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndKeyboardInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawKeyboardActions actions, Int32 scanCode, Boolean isExtendedKey, Boolean isSystemKey, Int32 virtualKey)
   at System.Windows.Interop.HwndKeyboardInputProvider.ProcessKeyAction(MSG& msg, Boolean& handled)
   at System.Windows.Interop.HwndSource.CriticalTranslateAccelerator(MSG& msg, ModifierKeys modifiers)
   at System.Windows.Interop.HwndSource.OnPreprocessMessage(Object param)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
Exception calling "Run" with "1" argument(s): "Object reference not set to an instance of an object."

Steps to reproduce:

  • Goto "new row"
  • Press F2
  • Press ENTER

This works ok:

  • Goto "new row"
  • Press F2
  • Press ESC

As well as:

  • Goto "new row"
  • Press F2
  • Type "sdfdsf"
  • Press ENTER

Sources can be found here.

I have found two posts that seems to be related to this problem:

  1. Exception in .Net 3.5 SP1
  2. WPF Databinding, Class Types, No Namespace Exception

Also, I tried similar code in C#, and it worked fine.

How can I fix this issue?

回答1:

This seems to be a bug in the Microsoft WPF DataGrid. You should definitely report it through Microsoft Connect, if you haven't already done so.

As a workaround, you could use a value converter on your bindings, to stop NULL values on the binding target from being transferred to the binding source, which will cause the NullReferenceException. Here is an example of such a converter:

// NullToUnsetConverter.cs    

using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;

namespace MyProject.Controls.Converters
{
    /// <summary>
    /// Converts <c>null</c> values to <see cref="DependencyProperty.UnsetValue"/>.
    /// <remarks>
    /// This converter is intented for use in situations when the binding target
    /// does not correctly handle <c>null</c> values. This is the case for example
    /// with some WPF UI controls.
    /// </remarks>
    /// </summary>
    public class NullToUnsetConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
            {
                return DependencyProperty.UnsetValue;
            }
            else
            {
                // No conversion applied
                return value;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // No conversion applied
            return value;
        }
    }
}

In order to apply the converter to your bindings, you'll have to modify your XAML like this:

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:cnv="clr-namespace:MyProject.Controls.Converters"
  xmlns:sys="clr-namespace:System;assembly=mscorlib"
  xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"
  >

  <Window.Resources>
    <x:Array x:Key="people" Type="sys:Object" />
    <cnv:NullToUnsetConverter x:Key="NullToUnsetConverter" />
  </Window.Resources>

  <StackPanel>
    <dg:DataGrid ItemsSource="{DynamicResource people}" CanUserAddRows="True" AutoGenerateColumns="False">
      <dg:DataGrid.Columns>

        <dg:DataGridTextColumn Header="First" Binding="{Binding First, Converter={StaticResource NullToUnsetConverter}}" />
        <dg:DataGridTextColumn Header="Last" Binding="{Binding Last, Converter={StaticResource NullToUnsetConverter}}" />

      </dg:DataGrid.Columns>
    </dg:DataGrid>
  </StackPanel>
</Window>

Alternatively, you could modify the NullToUnsetConverter.Convert method to return an empty string when the input value is a string, like in your case.