Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF DataGrid removing NewItemPlaceholder on IEditableCollectionView.CancelNew()

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; }
    }
}  
like image 818
Stanislav Nedelchev Avatar asked Apr 23 '15 16:04

Stanislav Nedelchev


1 Answers

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.

like image 94
Charles Clayton Avatar answered Oct 27 '22 03:10

Charles Clayton