开发者

Editing a DataGrid in WPF causes a System.NullReferenceException

开发者 https://www.devze.com 2023-02-21 11:48 出处:网络
I have a simple application that reads a list of Albums from a DataBase and fills a ListBox (AlbumShowCase).

I have a simple application that reads a list of Albums from a DataBase and fills a ListBox (AlbumShowCase). Whenever a ListBoxItem is selected I update a DataGrid (trackDataGrid) with the list of tracks in that Album (also from the DataBase).

The problem is, I can edit the items in the DataGrid, and for all the existing tracks, changes are persistent. But if I try to add a new track, once I finish editing the row I get the System.NullReferenceException.

private TunesDBDataContext db;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    db = new TunesDBDataContext("TunesDB.sdf");
    var query = from a in db.Albums select new AlbumCase(a);
    AlbumShowCase.ItemsSource = query;
}

private void trackDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    db.SubmitChanges();
}

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var query = from a in db.Albums
                where a.AlbumID == ((AlbumCase)e.AddedItems[0]).Album.AlbumID
                select a.Tracks;

    trackDataGrid.ItemsSource = query;
}

The Exception occurs right after my ValueConverter:

[ValueConversion(typeof(String), typeof(int))]
public class TimeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int time = (int)value;
        TimeSpan ts = TimeSpan.FromSeconds(time);
        return ts.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        // The validation runs before this, so we know that if we got here
        // the data must be valid and won't throw an exception.
        return (int)TimeSpan.Parse((string)value).TotalSeconds;
        // THE EXCEPTION OCCURS AFTER THIS LINE FOR NEW ROWS
    }

The TimeConverter is paired with a TimeConverterRule that makes sure the track length entered is valid, and for all I know it's working fine. It's just when the user edits the LAST row (the empty one) of the DataGrid that the crash occurs. And here's the stack trace:

System.NullReferenceException was unhandled
Message=Object reference not set to an instance of an object.
Source=PresentationFramework
StackTrace:
    at System.Windows.Data.BindingExpression.IsValidValueForUpdate(Object value, Type sourceType)
    at System.Windows.Data.BindingExpression.ConvertProposedValue(Object value)
    at System.Windows.Data.BindingExpression.ValidateAndConvertProposedValue(Collection1& values)
    at System.Windows.Controls.DataGridHelper.ValidateWithoutUpdate(FrameworkElement element)
    at System.Windows.Controls.DataGridColumn.CommitCellEdit(FrameworkElement editingElement)
    at System.Windows.Controls.DataGridColumn.CommitEdit(FrameworkElement editingElement)
    at System.Windows.Controls.DataGridCell.CommitEdit()
    at System.Windows.Controls.DataGrid.OnExecutedCommitEdit(ExecutedRoutedEventArgs e)
    at System.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 System.Windows.Controls.DataGrid.EndEdit(RoutedCommand command, DataGridCell cellContainer, DataGridEditingUnit editingUnit, Boolean exitEditMode)
    at System.Windows.Controls.DataGrid.CommitAnyEdit()
    at System.Windows.Controls.DataGrid.OnEnterKeyDown(KeyEventArgs e)
    at System.Windows.Controls.DataGrid.OnKeyDown(KeyEventArgs e)
    etc..开发者_JAVA百科.etc...
    }


My suspicion is that this is because you are binding to the results of a LINQ to SQL query. When you edit the row, WPF tries to "Add" the new item to your query - but it doesn't support adding.

Try something like this:

var query = from a in db.Albums
            where a.AlbumID == ((AlbumCase)e.AddedItems[0]).Album.AlbumID
            select a.Tracks;

var dataSource = new ObservableCollection<Track>();
foreach (var item in query)
    dataSource.Add(item);

trackDataGrid.ItemsSource = dataSource;

You might then need to subscribe to events on the grid so that when an item is added, you add it to the DbContext.

Also, make sure AlbumCase is a public class with a public, parameterless constructor. That's because WPF will try to "new" one up to set properties on.


Poking with .NET Reflector, this is the code you're running into (in System.Windows.Data.BindingExpression):

internal override object ConvertProposedValue(object value)
{
    ...
    Type sourcePropertyType = this.Worker.SourcePropertyType;
    IValueConverter dynamicConverter = null;
    CultureInfo culture = base.GetCulture();
    if (this.Converter != null)
    {
        if (!base.UseDefaultValueConverter)
        {
            value = this.Converter.ConvertBack(value, sourcePropertyType, this.ParentBinding.ConverterParameter, culture);
            if (((value != Binding.DoNothing) && (value != DependencyProperty.UnsetValue)) && !this.IsValidValueForUpdate(value, sourcePropertyType))
            {
                dynamicConverter = this.DynamicConverter;
            }
        }

From what I can understand the "SourcePropertyType" is a null value (and then it throws in the call to IsValidValueForUpdate).

So, this is clearly a bug in PresentationFramework (it sould report a nice error and fail gracefully), but it happens because somehow, you pass to WPF a source property type that is null. Maybe because of a generic open type or an anonymouse type.

Hope this helps.

To help you diagnose, I suggest you turn WPF traces one, see this thread on this subject on SO: How to detect broken WPF Data binding?


public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
//check value null
if(value==null) return 0;
        return (int)TimeSpan.Parse((string)value).TotalSeconds;

    }
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号