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!