6
votes

Overview

I'm developing a WPF application (using .NET 4.5), part of which involves showing some data in a DataGrid.
The user has the ability to add a new row inside the DataGrid and delete one via a button elsewhere.

I have an issue when the user starts adding a new row that cannot be committed and then presses the delete button.
The new row should be canceled and the DataGrid reset to its previous state.
However, the DataGrid's NewItemPlaceholder row is deleted and never shown again.

I've made a sample project that demonstrates the issue.
Here is a short screencast as well.
This is what the sample app looks like.

To reproduce:

  1. Double click the Price cell on the topmost row
  2. Enter an invalid number to trigger validation via an exception
  3. (optional) Select another row
  4. Click the delete button

Code

The viewmodel gets the data in an ObservableCollection, which is used as the source for a collection view. I have a simple command wired to the delete button. If the user is adding an item (IEditableCollectionView.IsAddingNew) I try to cancel the operation using .CancelNew() on the collectionView. However, when the command completes the DataGrid has its NewItemPlaceholder deleted.

So far, I've tried:

  • Triggering the DataGrid to make the placeholder appear again by setting dataGrid.CanUserAddRows = true, which fixes the issue somewhat, but that's a horrible workaround and it's buggy, afterwards the placeholder is not editable.
  • Removing the invalid item from the source collection: this.productsObservable.Remove(this.Products.CurrentAddItem as Product).
    This doesn't change the behavior, the placeholder is still removed.
  • Removing the item from the collection view: this.Products.Remove(this.Products.CurrentAddItem).
    This throws an exception, which makes sense: 'Remove' is not allowed during an AddNew or EditItem transaction.

Is there another way I can cancel the user's add AND have the NewItemPlaceholder showing?

In the sample project I'm instantiating the data inside the VM constructor for simplicity.
In reality I'm using async calls to a service, wrapping the result in ObservableCollection and the ViewModel implements INotifyPropertyChanged. The business object doesn't implement INPC.

XAML for the sample project:

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication3"
        Title="MainWindow" Height="250">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <StackPanel Orientation="Vertical">
        <Button Command="{Binding DeleteCommand}" Content="Delete row" />
        <DataGrid
            ItemsSource="{Binding Products}"
            CanUserDeleteRows="False"
            CanUserAddRows="True"
            SelectionMode="Single">
        </DataGrid>
    </StackPanel>
</Window>

ViewModel, together with a simple Business Object:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using System.Windows.Input;

namespace WpfApplication3
{
    public class ViewModel
    {
        private readonly ObservableCollection<Product> productsObservable;

        public ViewModel()
        {
            this.productsObservable = new ObservableCollection<Product>()
            {
                new Product() { Name = "White chocolate", Price = 1},
                new Product() { Name = "Black chocolate", Price = 2},
                new Product() { Name = "Hot chocolate", Price = 3},
            };

            this.Products = CollectionViewSource.GetDefaultView(this.productsObservable) as IEditableCollectionView;
            this.Products.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtBeginning;

            this.DeleteCommand = new DelegateCommand(this.OnDeleteCommandExecuted);
        }

        public ICommand DeleteCommand { get; private set; }
        public IEditableCollectionView Products { get; private set; }

        private void OnDeleteCommandExecuted()
        {
            if (this.Products.IsAddingNew)
            {
                this.Products.CancelNew();
            }
        }

    }

    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}  
1
Did you solve your problem?, because I have the same issueJamaxack
@Jamaxack I switched to a Telerik RadGridView, as that was an option for me at the time. Also take a look at this, if it helps (I just don't remember if it was the same issue as this post, but I remember it was related): connect.microsoft.com/VisualStudio/feedback/details/1465806/… .Stanislav Nedelchev

1 Answers

2
votes

What about this:

private void OnDeleteCommandExecuted()
{
    if (this.Products.IsAddingNew)
    {
        this.Products.CancelNew();
        this.Products.AddNew();
    }
}

You'll still delete the row with the bad input, but you'll add a new (mostly) empty row. The only problem, although I'm sure its fixable, is that you get the default 0 value in the numerical column not a null.