Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update Datagrid using MVVM?

I have searched and have come to a brick wall. There seems to be plenty of questions and answers on how to do this, but I can't find anything specifically that I can implement(Obviously an issue with my understanding).

I was hoping to be able to update several Datagrids and going off the premise that you should not be naming you controls, I am lost as to how to get this to work.

So what I have been able to come up with thus far is using the System.Windows.Interactivity assembly. But I have no Idea how to implement the code behind (I certainly could show all the code I have tried to get it to work, but unfortunately it would just clutter the post, so I am not including it). I have research as much as I can on implementing ICommand.

So I have the XAML:

<i:Interaction.Triggers>
     <i:EventTrigger EventName="RowEditEnding">
         <i:InvokeCommandAction Command="{Binding CanExecuteChanged}" />
     </i:EventTrigger>
</i:Interaction.Triggers>

But I can't seem to get the code behind to be able to notify when the RowEditEnding has finished and be able to update the database with the new data.

So with the MVVM model in mind, how do I go about getting an event to fire so I can update the database?

like image 962
KyloRen Avatar asked Dec 23 '22 20:12

KyloRen


1 Answers

EDIT2: Changed People to an ObservableCollection instead of List. Added CollectionChanged event to handle attaching/removing PropertyChanged events from Person objects in the collection.

EDIT: changed foreach in ViewModel for consistency.

First, Model-View-ViewModel(MVVM) means that you should have as little code behind as possible, preferably none in fact. Instead, logic for the UI should be done in xaml (the View), while logic for the data should be done in the Model and the organization of the data into presentable form should be done via the View Model, which is a separate file that knows nothing about WPF or the UI.

It also seems you have a misunderstanding as to how data binding works. You are referring to stuff you have done in code behind, but binding expressions usually point to a property on your view's DataContext, which in MVVM should be set to your ViewModel. There is a good tutorial here that can get you started with binding. I also recommend the followup posts to that one, they helped me a great deal when I was starting out WPF.

Now for the situation with the DataGrid. First, here is a good tutorial on the WPF DataGrid. Next, you state that you want to update the database when the row has been updated. Here is an example of how to do that in MVVM style:

Suppose you have a view with a DataGrid like this:

<UserControl x:Class="MyProject.MyView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid ItemsSource="{Binding People, Mode=OneWay}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
                <DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
                <DataGridTextColumn Header="Age" Binding="{Binding Age}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

Code behind:

namespace TestWPFApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new MyViewModel();
        }
    }
}

Note that the code behind is almost empty. Just the default code, plus DataContext = new MyViewModel();. As I mentioned earlier, the DataContext of your view should be your View Model.

MyViewModel looks like this:

public class MyViewModel : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Impl
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    private ObservableCollection<Person> m_people;
    public ObservableCollection<Person> People
    {
        get { return m_people; }
        private set
        {
            if (value == m_people)
                return;

            m_people = value;
            OnPropertyChanged();
        }
    }

    public MyViewModel()
    {
        m_people = new ObservableCollection<Person>();
        m_people.CollectionChanged += m_people_CollectionChanged;
        m_people.Add(new Person() { FirstName = "Bob", LastName = "Brown", Age = 45 });
        m_people.Add(new Person() { FirstName = "Sarah", LastName = "Smith", Age = 25 });
    }

    private void m_people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null && e.NewItems.Count > 0)
        {
            foreach (INotifyPropertyChanged item in e.NewItems.OfType<INotifyPropertyChanged>())
            {
                item.PropertyChanged += people_PropertyChanged;
            }
        }
        if (e.OldItems != null && e.OldItems.Count > 0)
        {
            foreach (INotifyPropertyChanged item in e.OldItems.OfType<INotifyPropertyChanged>())
            {
                item.PropertyChanged -= people_PropertyChanged;
            }
        }
    }
    //Property Changed will be called whenever a property of one of the 'Person'
    //objects is changed.
    private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var row = sender as Person;
        SaveData(row);
    }

    private void SaveData(Person row)
    {
        //Save the row to the database here.
    }
}

I have a property of type List<Person> in my view model. Person looks like this:

public class Person : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Impl
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    private string m_firstName;
    public string FirstName
    {
        get { return m_firstName; }
        set
        {
            if (value == m_firstName)
                return;

            m_firstName = value;
            OnPropertyChanged();
        }
    }

    private string m_lastName;
    public string LastName
    {
        get { return m_lastName; }
        set
        {
            if (value == m_lastName)
                return;

            m_lastName = value;
            OnPropertyChanged();
        }
    }

    private int m_age;
    public int Age
    {
        get { return m_age; }
        set
        {
            if (value == m_age)
                return;

            m_age = value;
            OnPropertyChanged();
        }
    }
}

The important thing to note here is INotifyPropertyChanged, this interface is very important to MVVM and WPF data binding in general. When implemented properly, it causes an object to publish a PropertyChanged event whenever one of its properties is changed. This tells any bound WPF controls that they should get the new value, and also allows your ViewModel to watch them for changes. So in the view model, we attach an event handler to the CollectionChanged event on People, which then takes care of attaching the PropertyChanged event handler for each item added to the collection.

The CollectionChanged event will be called whenever an item is added, removed, or replaced in the collection and will remove the PropertyChanged handler from the old items and add the handler to the new ones. It is important to remember to remove the handlers, or items removed from the collection may not be properly garbage collected.

The person_PropertyChanged method will be called each time a property of one of the Person objects changes. In person_PropertyChanged we then call the methods to update the database, passing the updated row (Person in this case), like below:

//Property Changed will be called whenever a property of one of the 'Person'
//objects is changed.
private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    var row = sender as Person;
    SaveData(row);
}

private void SaveData(Person row)
{
    //Save the row to the database here.
}

Each row in the grid I displayed above represents one person object. Whenever the user changes a value of one of the cells in the grid, the corresponding property of the Person object that the row represents will be updated as well, which will trigger a PropertyChanged event, and call person_PropertyChanged.

Suppose the user changes the Age column of "Bob" to 37. After the user hits enter or moves off the cell for Age the Age property of the Person object representing "Bob" will be changed to 37 from 45. This will cause that Person object to raise PropertyChanged, which will call the person_PropertyChanged method in MyViewModel. person_PropertyChanged will then call SaveData which is where you would put the code to save the updated Person row to the database.

If you have any questions, let me know!

like image 68
Brandon Kramer Avatar answered Dec 28 '22 00:12

Brandon Kramer