Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fat Models, skinny ViewModels and dumb Views, the best MVVM approach?

Through generous help on this question, I put together the following MVVM structure which displays the changes of a model in real time in XAML (current date/time), very nice.

A cool advantage of this set up is that when you look at your view in design mode of Visual Studio or Blend, you see the time ticking by, which means that at design time you have access to live data from your model.

In the process of getting this to work, I was surprised to see most of the bulk move from my ViewModel into my Model, including implementation of INotifyPropertyChange. Another change is that I no longer bind to properties on the ViewModel but to methods.

So currently this is my favorite flavor of MVVM:

  1. View is dumb:

    • one ObjectDataProvider for each object you need from your model
    • each ObjectDataProvider maps to a method on the ViewModel (not a property)
    • no x:Name properties in XAML elements
  2. ViewModel is skinny:

    • the only thing in your ViewModel are the methods to which your view binds
  3. Model is fat:

    • the model implements INotifyPropertyChanged on each of its properties.
    • for every method on your ViewModel (e.g. GetCurrentCustomer) there is a corresponding singleton method in your Model (e.g. GetCurrentCustomer).
    • the model takes care of any real time threading functionality as in this example

Questions:

  1. Those of you who have been implementing MVVM in real scenarios, is this the basic structure you have also settled upon, and if not, how does yours vary?
  2. How would you extend this to include routed commands and routed events?

The following code will work if you just copy the XAML and code behind into a new WPF project.

XAML:

<Window x:Class="TestBinding99382.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestBinding99382"
    Title="Window1" Height="300" Width="300">

    <Window.Resources>
        <ObjectDataProvider 
             x:Key="DataSourceCustomer" 
             ObjectType="{x:Type local:ShowCustomerViewModel}" 
                        MethodName="GetCurrentCustomer"/>
    </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.Windows;
using System.ComponentModel;
using System;
using System.Threading;

namespace TestBinding99382
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }

    //view model
    public class ShowCustomerViewModel
    {
        public Customer GetCurrentCustomer() {
            return Customer.GetCurrentCustomer();
        }
    }

    //model
    public class Customer : INotifyPropertyChanged
    {
        private string _firstName;
        private string _lastName;
        private DateTime _timeOfMostRecentActivity;
        private static Customer _currentCustomer;
        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 Customer()
        {
            _timer = new Timer(UpdateDateTime, null, 0, 1000);
        }

        private void UpdateDateTime(object state)
        {
            TimeOfMostRecentActivity = DateTime.Now;
        }

        public static Customer GetCurrentCustomer()
        {
            if (_currentCustomer == null)
            {
                _currentCustomer = new Customer 
                     {  FirstName = "Jim"
                        , LastName = "Smith"
                        , TimeOfMostRecentActivity = DateTime.Now 
                     };
            }
            return _currentCustomer;
        }

        //INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
}
like image 516
Edward Tanguay Avatar asked May 12 '09 12:05

Edward Tanguay


1 Answers

Here's my opinion, for what it's worth :

I don't really agree with the approach you suggest (except for the dumb view). In real life, you will often have to use an existing model : it could be legacy code that you don't have the time (or will) to change, or even a library for which you don't have the code. In my opinion, the model should be completely unaware of the way it will be displayed, and should be easily usable in a non-WPF application. So it doesn't have to implement any specific interface like INotifyPropertyChanged of INotifyCollectionChanged to make it usable in MVVM. I think that all the logic related to UI should reside in the ViewModel.

Regarding RoutedEvents and RoutedCommands, they are not really suitable for use with the MVVM pattern. I usually try to use as little RoutedEvents as possible, and no RoutedCommands at all. Instead, my ViewModels expose RelayCommand properties that I bind to the UI in XAML (see this article by Josh Smith for details on RelayCommand). When I really need to handle events for some control, I use attached behaviors to map the events to ViewModel commands (have a look at Marlon Grech's implementation)

So, in summary :

  • Dumb View
  • Big and smart ViewModel
  • Any model you want or have to use

Of course it's just my approach, and it may not be the best, but I feel quite comfortable with it ;)

like image 149
Thomas Levesque Avatar answered Oct 16 '22 22:10

Thomas Levesque