7

I have the following DataGrid

<DataGrid CanUserDeleteRows="True" 
          CanUserAddRows="True"
          SelectedItem="{Binding SelectedResource, Mode=TwoWay}"
          ItemsSource="{Binding Path=Resources, Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged, 
                                IsAsync=True}"> ... </<DataGrid>

I am using the MVVM pattern to bind to an ObservableCollection<ResourceViewModel> Resources and this works great. I have a button that adds a new row and this is done by adding a new ResourceViewModel to the Resources collection - great. Now, I want the user to be able to click on the empty last row, and this automatically creates a new record in the DataGrid.

I have made sure the DataGrid has CanUserAddRows=True. I have made sure the class in the collection Resources (ResourceViewModel) that I am binding to has a default constructor (no parameters) and I have made sure the collection type is not readonly. When the user clicks on the last row the default constructor fires but to correctly instantiate the new ResourceViewModel object in need a reference to either the grid of the Resources collection...

I suppose I could use and AttachedCommand on the CellBeginEdit event and then add a new ResourceViewModel to the observable collection there, is there a standard way of doing this?


Note, I have read the following questions and these are of no help to me

  1. WPF DataGrid - Event for New Rows?
  2. How to add rows with a DataGrid and MVVM

Edit. It turns out that I am having problems doing this due to a bug in the WPF DataGrid. See Nigel Spencer's Blog. However, his fix is not working for me at the moment...

Community
  • 1
  • 1
MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • The updated link to Nigel Spencer's blog post is [Problems binding to SelectedValue with WPF DataGrid](http://blogs.spencen.com/?p=611) – Tone Oct 13 '16 at 21:07

2 Answers2

3

As far as I understand, you know how to correctly add new items into your data bound collection to result in a new item being added to the DataGrid and your question actually relates to how can you do this when the user clicks on the last row in the DataGrid. The general way to handle some event in the view model is to create an Attached Property (if one does not already exist) that handles that event for you.

For instance, you could create an Attached Property that attaches a handler to the relevant event and another of type ICommand which you could execute when the event handler is called. Then you could write the functionality of that ICommand in your view model (in which you add a new item to your data bound collection) and data bind your ICommand implementation to the Attached ICommand Property.

As I'm fairly sure you know how to define Attached Propertys, I won't insult you by showing you. Please let me know if I have misunderstood you, or not made myself clear.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • 1
    Thanks for the reply. The problem is that I set an attached command to fire on the `InitializingNewItem` event. This fires when I put the last row of the `DataGrid` in edit mode and before the grid attempts to create the new item. Here I can add the new item, but the `DataGrid` goes on to try an instantiate the items itself using the default constructor. I wan't to be able to stop the grid from automatically doing this as I can use the command and the event to do what I want... – MoonKnight Nov 25 '13 at 15:29
  • So I suppose the question becomes, what is the best way to do this; A. Cancel the event after I add the new row (how, I have no idea). B. Let the grid do this itself - but then how to get the object correctly initialised in the default ctor? Is there a standard way of doing this? – MoonKnight Nov 25 '13 at 15:31
  • I very rarely use these controls, so unfortunately, I'm not really in a position to be able to give advanced advise about them. Good luck with your problem. – Sheridan Nov 25 '13 at 16:04
2

Here's an attached property that registers a command for adding rows (assuming the source collection contains a generic type argument):

public static readonly DependencyProperty RegisterAddCommandProperty = DependencyProperty.RegisterAttached("RegisterAddCommand", typeof(bool), typeof(DataGridExtensions), new PropertyMetadata(false, OnRegisterAddCommandChanged));
public static bool GetRegisterAddCommand(DependencyObject obj)
{
    return (bool)obj.GetValue(RegisterAddCommandProperty);
}
public static void SetRegisterAddCommand(DependencyObject obj, bool value)
{
    obj.SetValue(RegisterAddCommandProperty, value);
}
static void OnRegisterAddCommandChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (sender is DataGrid)
    {
        var DataGrid = sender as DataGrid;
        if ((bool)e.NewValue)
            DataGrid.CommandBindings.Add(new CommandBinding(AddCommand, AddCommand_Executed, AddCommand_CanExecute));
    }
}

public static readonly RoutedUICommand AddCommand = new RoutedUICommand("AddCommand", "AddCommand", typeof(DataGridExtensions));
static void AddCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
    var DataGrid = sender as DataGrid;

    var ItemsSourceType = DataGrid.ItemsSource.GetType();
    var ItemType = ItemsSourceType.GetGenericArguments().Single();

    DataGrid.Items.Add(Activator.CreateInstance(ItemType));
}
static void AddCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = (sender as DataGrid).CanUserAddRows;
}

And then you can apply the command to a button somewhere like this:

<Button Command="{x:Static Extensions:DataGridExtensions.AddCommand}"/>

Don't forget to specify the command target.