Being new to WPF & MVVM I struggling with some basic functionality.
Let me first explain what I am after, and then attach some example code...
I have a screen showing a list of users, and I display the details of the selected user on the right-hand side with editable textboxes. I then have a Save button which is DataBound, but I would only like this button to display when data has actually changed. ie - I need to check for "dirty data".
I have a fully MVVM example in which I have a Model called User:
namespace Test.Model
{
class User
{
public string UserName { get; set; }
public string Surname { get; set; }
public string Firstname { get; set; }
}
}
Then, the ViewModel looks like this:
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Test.Model;
namespace Test.ViewModel
{
class UserViewModel : ViewModelBase
{
//Private variables
private ObservableCollection<User> _users;
RelayCommand _userSave;
//Properties
public ObservableCollection<User> User
{
get
{
if (_users == null)
{
_users = new ObservableCollection<User>();
//I assume I need this Handler, but I am stuggling to implement it successfully
//_users.CollectionChanged += HandleChange;
//Populate with users
_users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"});
_users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"});
}
return _users;
}
}
//Not sure what to do with this?!?!
//private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
//{
// if (e.Action == NotifyCollectionChangedAction.Remove)
// {
// foreach (TestViewModel item in e.NewItems)
// {
// //Removed items
// }
// }
// else if (e.Action == NotifyCollectionChangedAction.Add)
// {
// foreach (TestViewModel item in e.NewItems)
// {
// //Added items
// }
// }
//}
//Commands
public ICommand UserSave
{
get
{
if (_userSave == null)
{
_userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute);
}
return _userSave;
}
}
void UserSaveExecute()
{
//Here I will call my DataAccess to actually save the data
}
bool UserSaveCanExecute
{
get
{
//This is where I would like to know whether the currently selected item has been edited and is thus "dirty"
return false;
}
}
//constructor
public UserViewModel()
{
}
}
}
The "RelayCommand" is just a simple wrapper class, as is the "ViewModelBase". (I'll attach the latter though just for clarity)
using System;
using System.ComponentModel;
namespace Test.ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
public void Dispose()
{
this.OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
Finally - the XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Test.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:UserViewModel/>
</Window.DataContext>
<Grid>
<ListBox Height="238" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top"
Width="197" ItemsSource="{Binding Path=User}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Firstname}"/>
<TextBlock Text="{Binding Path=Surname}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="232,16,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,21,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/UserName}" />
<Label Content="Surname" Height="28" HorizontalAlignment="Left" Margin="232,50,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,52,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Surname}" />
<Label Content="Firstname" Height="28" HorizontalAlignment="Left" Margin="232,84,0,0" Name="label3" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,86,0,0" Name="textBox3" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Firstname}" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="368,159,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=UserSave}" />
</Grid>
</Window>
So basically, when I edit a surname, the Save button should be enabled; and if I undo my edit - well then it should be Disabled again as nothing has changed.
I have seen this in many examples, but have not yet found out how to do it.
Any help would be much appreciated! Brendan
In my experience, if you implement IsDirty
in your view model, you probably also want the view model to implement IEditableObject
.
Assuming that your view model is the usual sort, implementing PropertyChanged
and a private or protected OnPropertyChanged
method that raises it, setting IsDirty
is simple enough: you just set IsDirty
in OnPropertyChanged
if it isn't already true.
Your IsDirty
setter should, if the property was false and is now true, call BeginEdit
.
Your Save
command should call EndEdit
, which updates the data model and sets IsDirty
to false.
Your Cancel
command should call CancelEdit
, which refreshes the view model from the data model and sets IsDirty
to false.
The CanSave
and CanCancel
properties (assuming you're using a RelayCommand
for these commands) just return the current value of IsDirty
.
Note that since none of this functionality depends on the specific implementation of the view model, you can put it in an abstract base class. Derived classes don't have to implement any of the command-related properties or the IsDirty
property; they just have to override BeginEdit
, EndEdit
, and CancelEdit
.