Model, View, ViewModel (MVVM)

  1. Overview
    1. MVVM is a design pattern often used in UWP apps that makes use of data binding. Composed of three parts:
      1. Model - Same as MVC: the data, state, and business logic
      2. View - UI; binds the observable variables and actions exposed by the ViewModel to the UI
      3. ViewModel - Wraps the Model and prepares observable data needed by the View; manipulates the Model due to actions in the View; not tied to the View

      MVVM diagram

    2. Example
      1. The Model implements Theater and Movie classes for managing a theater's movies
      2. The ViewModel exposes properties that wrap the Theater and Movie classes and are bound to the View
      3. The user enters a new movie into the View, and the View adds the new movie to the ViewModel
      4. Changes to the ViewModel cause the ViewModel to update the Theater class
      5. Changes to the ViewModels are automatically propogated to the View because of data binding, so the new movie is automatically shown in the UI
    3. MVVM is easier to test than MVC and reduces the amount of code necessary to connect the View and Model
    4. Disadvantage: Very large applications using MVVM may consume considerable amounts of memory
    5. Minimal MVVM UWP example on Microsoft blog (my example is even more minimal)
  2. Models
    1. Classes that know nothing of the ViewModels or Views
    2. Usually stored in a project directory called "Models"
    3. Movie
      public class Movie
      {
      	public string Title { get; set; }
      	public string Rating { get; set; }
      }
      
    4. Theater
      public class Theater
      {
      	public string Name { get; set; }
      	public List<Movie> Movies { get; set; }
      
      	public Theater(string name)
      	{
      		Name = name;
      		
      		// Load some initial movies
      		Movies = new List<Movie>
      		{
      			new Movie { Title = "Wonder Woman", Rating = "PG-13" },
      			new Movie { Title = "Spider-Man", Rating = "PG-13" },
      			new Movie { Title = "Justice League", Rating = "PG-13" }
      		};
      	}
      }
      
  3. ViewModels
    1. Implement INotifyPropertyChanged so View can be notified when the Model changes
    2. Usually stored in a project directory called "ViewModels"
    3. Encapsulates Models
    4. MovieViewModel
      public class MovieViewModel : INotifyPropertyChanged
      {
      	public event PropertyChangedEventHandler PropertyChanged;
      
      	private Movie movie;
      
      	public MovieViewModel(Movie movie)
      	{
      		this.movie = movie;
      	}
      
      	public string Title
      	{
      		get { return movie.Title; }
      
      		set
      		{
      			movie.Title = value;
      			OnPropertyChanged("Title");
      		}
      	}
      
      	public string Rating 
      	{
      		get	{ return movie.Rating; }
      
      		set
      		{
      			movie.Rating = value;
      			OnPropertyChanged("Rating");
      		}
      	}
      
      	private void OnPropertyChanged(string property)
          {
      		// Notify any controls bound to the ViewModel that the property changed
      		PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
          }
      }
      
    5. TheaterViewModel uses ObservableCollection that provides notifications to ListView when items are added or removed
      public class TheaterViewModel : INotifyPropertyChanged
      {
      	public event PropertyChangedEventHandler PropertyChanged;
      
      	private Theater theater;
      
      	public ObservableCollection<MovieViewModel> Movies { get; set; }
      
      	public string Name
      	{
      		get { return theater.Name; }
      		set
      		{
      			theater.Name = value;
      			OnPropertyChanged(this, new PropertyChangedEventArgs("Name"));
      		}
      	}
      
      	public TheaterViewModel(Theater theater)
      	{
      		this.theater = theater;
      
      		Movies = new ObservableCollection<MovieViewModel>();
      
      		// Create ViewModels for each Movie
      		foreach (var movie in theater.Movies)
      		{
      			var newMovie = new MovieViewModel(movie);
      			newMovie.PropertyChanged += OnPropertyChanged;
      			Movies.Add(newMovie);
      		}
      	}
      
      	private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
      	{
      		// Theater name or MovieViewModel changed, so let UI know 
      		PropertyChanged?.Invoke(sender, e);
      	}
      }
      
  4. View
    1. XAML uses x:Bind markup extension, which is converted to code at compile time and sets a property to the specified markup
    2. MainPage.xaml
      <Page
          x:Class="UwpMovieMvvm.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:local="using:UwpMovieMvvm"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d">
      
          <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      		<TextBox x:Name="titleTextBox" Text="{x:Bind Movie.Title, Mode=TwoWay}" />
      		<TextBox x:Name="ratingTextBox" Text="{x:Bind Movie.Rating, Mode=TwoWay}" />
      		<Button x:Name="addButton" Content="Add Movie" Click="addButton_Click" />
      				
      		<ListView x:Name="movieListView" Height="155" Width="216" HorizontalAlignment="Left"
      						  ItemsSource="{x:Bind Theater.Movies, Mode=OneWay}">
      			<ListView.ItemTemplate>
      				<DataTemplate x:DataType="local:MovieViewModel">
      					<StackPanel>
      						<TextBlock Text="{x:Bind Title, Mode=OneWay}" FontWeight="Bold"/>
      						<TextBlock Text="{x:Bind Rating, Mode=OneWay}"/>
      					</StackPanel>
      				</DataTemplate>
      			</ListView.ItemTemplate>
      		</ListView>				
      
      		<Button x:Name="deleteButton" Content="Delete Movie" Click="deleteButton_Click" />
          </StackPanel>
      </Page>
      
    3. Code-behind creates public properties for ViewModels so XAML can bind to them
      public sealed partial class MainPage : Page
      {
      	// XAML binds to these properties
      	public TheaterViewModel Theater { get; set; }
      	public MovieViewModel Movie { get; set; }
      
      	public MainPage()
      	{
      		this.InitializeComponent();
      
      		// Create ViewModels
      		Movie = new MovieViewModel(new Movie());
      		Theater = new TheaterViewModel(new Theater("Rialto"));
      	}
      
      	private void addButton_Click(object sender, RoutedEventArgs e)
      	{
      		// Add new movie to list
      		Theater.Movies.Add(new MovieViewModel(
      			new Movie { Title = Movie.Title, Rating = Movie.Rating }));
      	}
      	
      	private void deleteButton_Click(object sender, RoutedEventArgs e)
      	{
      		// Delete selected movie from ObservableCollection to update UI
      		if (movieListView.SelectedIndex > -1)
      		{
      			Theater.Movies.RemoveAt(movieListView.SelectedIndex);
      			
      			// WARNING: Deleting directly from ListView throws an exception!
      			// movieListView.Items.RemoveAt(movieListView.SelectedIndex);			
      		}
      	}
      }
      
    4. Note that adding and removing movies does not affect Theater model!