Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF DataGrid: Save cell changes immediately with MVVM

I have a DataGrid simplified looking like this:

<DataGrid AutoGenerateColumns="False" 
          ItemsSource="{Binding Parts}" 
          SelectedItem="{Binding SelectedPart}" >
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=TwoWay}" />
        <DataGridTemplateColumn Header="PartType" >
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.PartTypes}" 
                              SelectedItem="{Binding PartType, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding PartType}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

My ViewModel looks something like this:

public class PartListViewModel
{
    private ObservableCollection<Part> _parts;
    public ObservableCollection<Part> Parts
    {
        get { return _parts; }
        set
        {
            _parts = value;
             OnPropertyChanged("Parts");
        }
    }

    private Part _selectedPart;
    public Part SelectedPart
    {
        get { return _selectedPart; }
        set
        {
            _selectedPart = value;
            OnPropertyChanged("SelectedPart");
        }
    }
}

Now I want changes to datagrid cells being stored immediately to the database. How to do that in MVVM?

Currently I listen to the OnCellEditEnding event of the DataGrid and save the record in the code-behind. But that is pretty ugly:

private void DataGrid_OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    var viewModel = (PartListViewModel) DataContext;
    viewModel.SavePart((Part) e.Row.Item);
}
like image 716
juergen d Avatar asked Oct 16 '22 14:10

juergen d


3 Answers

You can do something like this. (you need to add reference to System.Windows.Interactivity, you can download the assemblies from here)

Then add reference to the namespace in the XAML. xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<i:Interaction.Triggers>
  <i:EventTrigger EventName="CellEditEnding">
     <i:InvokeCommandAction Command="{Binding SomeCommand}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>
like image 98
TheLastStark Avatar answered Oct 19 '22 11:10

TheLastStark


One way to do this is with an event broker like the EventAggregator that comes with the Prism libs.

When you add each Part entity ("model") into the Parts collection you can wrap it with its own viewmodel (PartViewModel), this can then listen for property updates in its constituent Part and then publish an update message through the event broker. You can then have a service that listens for (subscribes to) that message and saves the published payload (the payload can be the Part that was modified).

like image 35
slugster Avatar answered Oct 19 '22 11:10

slugster


You could handle the PropertyChanged event for all Part objects in the view model:

public class PartListViewModel
{
    private ObservableCollection<Part> _parts;
    public ObservableCollection<Part> Parts
    {
        get { return _parts; }
        set
        {
            if(_parts != null) _parts.CollectionChanged -= OnCollectionChanged;
            _parts = value;
            if (_parts != null) _parts.CollectionChanged += OnCollectionChanged;
            //OnPropertyChanged("Parts");
        }
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (object part in e.NewItems)
            {
                (part as INotifyPropertyChanged).PropertyChanged
                    += new PropertyChangedEventHandler(PartPropertyChanged);
            }
        }

        if (e.OldItems != null)
        {
            foreach (object part in e.OldItems)
            {
                (part as INotifyPropertyChanged).PropertyChanged
                    -= new PropertyChangedEventHandler(PartPropertyChanged);
            }
        }
    }

    private void PartPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if(e.PropertyName == "PartType")
        {
            //save to database...
        }
    }
}

This does the Part class to implement INotifyPropertyChanged of course.

like image 1
mm8 Avatar answered Oct 19 '22 11:10

mm8