The following code is a refactoring of my previous MVVM approach (Fat Models, skinny ViewModels and dumb Views, the best MVVM approach?) in which I moved the logic and INotifyPropertyChanged implementation from the model back up into the ViewModel. This makes more sense, since as was pointed out, you often you have to use models that you either can't change or don't want to change and so your MVVM approach should be able to work with any model class as it happens to exist.
This example still allows you to view the live data from your model in design mode in Visual Studio and Expression Blend which I think is significant since you could have a mock data store that the designer connects to which has e.g. the smallest and largest strings that the UI can possibly encounter so that he can adjust the design based on those extremes.
Questions:
- I'm a bit surprised that I even have to "put a timer" in my ViewModel since it seems like that is a function of INotifyPropertyChanged, it seems redundant, but it was the only way I could get the XAML UI to constantly (once per second) reflect the state of my model. So it would be interesting to hear anyone who may have taken this approach if you encountered any disadvantages down the road, e.g. with threading or performance.
The following code will work if you just copy the XAML and code behind into a new WPF project.
XAML:
<Window x:Class="TestMvvm73892.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMvvm73892"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider
x:Key="DataSourceCustomer"
ObjectType="{x:Type local:CustomerViewModel}"
MethodName="GetCustomerViewModel"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource DataSourceCustomer}">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=FirstName}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Path=LastName}"/>
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/>
</StackPanel>
</DockPanel>
</Window>
Code Behind:
using System;
using System.Windows;
using System.ComponentModel;
using System.Threading;
namespace TestMvvm73892
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
//view model
public class CustomerViewModel : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private DateTime _timeOfMostRecentActivity;
private Timer _timer;
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
this.RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
this.RaisePropertyChanged("LastName");
}
}
public DateTime TimeOfMostRecentActivity
{
get
{
return _timeOfMostRecentActivity;
}
set
{
_timeOfMostRecentActivity = value;
this.RaisePropertyChanged("TimeOfMostRecentActivity");
}
}
public CustomerViewModel()
{
_timer = new Timer(CheckForChangesInModel, null, 0, 1000);
}
private void CheckForChangesInModel(object state)
{
Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
MapFieldsFromModeltoViewModel(currentCustomer, this);
}
public static CustomerViewModel GetCustomerViewModel()
{
CustomerViewModel customerViewModel = new CustomerViewModel();
Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
MapFieldsFromModeltoViewModel(currentCustomer, customerViewModel);
return customerViewModel;
}
public static void MapFieldsFromModeltoViewModel
(Customer model, CustomerViewModel viewModel)
{
viewModel.FirstName = model.FirstName;
viewModel.LastName = model.LastName;
viewModel.TimeOfMostRecentActivity = model.TimeOfMostRecentActivity;
}
public static Customer GetCurrentCustomer()
{
return Customer.GetCurrentCustomer();
}
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
//model
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime TimeOfMostRecentActivity { get; set; }
public static Customer GetCurrentCustomer()
{
return new Customer
{ FirstName = "Jim"
, LastName = "Smith"
, TimeOfMostRecentActivity = DateTime.Now
};
}
}
}