05 August, 2010

Now Playing with MVVM - Getting Started: M-V-VM breakdown

This is part 2 of a series of posts in which I attempt to reproduce the Zune Now Playing interface using the MVVM pattern within WPF. An introduction to the series and table of contents can be found in the first post of this series.

When broken down to its most basic elements, the Zune Now Playing interface is a collection of album art images of varying sizes, arranged in a particular order. In order to replicate the UI, my first step will be to try to look at the interface through MVVM goggles. How can I break it down into a domain model, a view template, and a mapping layer between the two?

Model: The domain revolves around Albums, Artists, and Songs. I have already done some domain modeling with this same set of resources, so I’ll borrow existing plumbing from that. I’ll also need a worker to go out and fetch the album data from an external resource.

View:
The Zune Now Playing interface is built from a collection of three different sized user controls. I’ll create small, medium, and large cover views and arrange them in a parent container view that will allow me to lay them out the same way that the Zune software does.

ViewModel:
The three cover views will bind to a common cover viewmodel which will represent the properties associated to an album, and the parent view will bind to a viewmodel that maintains the master list of all available albums. In addition to the bindable properties, the parent viewmodel will also be responsible for randomizing the album collection, reducing the number of duplicate covers, and ensuring that all covers have something to bind to regardless of the number of available albums. The parent viewmodel will also be responsible for shuffling album covers on the UI by kicking off animations that transition from one cover to the next.

Quick Implementation

I’d like to quickly get something up and running to familiarize myself with the WPF binding infrastructure, so for now I’ll just jump in and start coding. I’ll start by creating three user controls – one for each of the three sizes of covers that are found in the Now Playing UI. The album cover views contain mostly the same XAML, the most notable difference being the size of the container. A grid serves as the parent object for Image and Label entities, which are bound to properties on the corresponding DataContext, which for now will be an Album. 

   1:  <Grid x:Name="LayoutRoot" Width="280" Height="280" Background="Red">
   2:      <Image Source="{Binding Path=ArtworkUrl}" Stretch="Fill" Width="280" Height="280"/>
   3:      <Label Content="{Binding Path=Name}" Height="25" Background="#96030303" />
   4:  </Grid>



I’ve given each of the cover views different background colors to provide visual cues when laying them out. I’ve arranged five of these cover views on my MainWindow inside a parent grid, as shown below

CoverArray

Now that the covers are in place, I need a way to provide each of them with an album to bind to. I’ll make these available by creating a viewmodel that the MainWindow can bind to. This MainWindowViewModel will provide an ObservableCollection of Albums. In the interest of time, I’ll manually populate the ObservableCollection in the viewmodel’s constructor for now, rather than fetching the data from an outside source at runtime.


   1:  public class MainWindowViewModel : ViewModelBase {
   2:    public MainWindowViewModel() {
   3:        Albums = new ObservableCollection<Album>();
   4:        Albums.Add(new Album {
   5:            Name = "Waking Up",
   6:            Artist = new Artist { Name = "OneRepublic" },
   7:            ArtworkUrl = "http://image.listen.com/img/170x170/3/3/9/0/1850933_170x170.jpg"
   8:        });
   9:        ....
  10:    }
  11:   
  12:    public ObservableCollection<Album> Albums { get; set; }
  13:  }



To make use of the MainWindowViewModel in the parent container, I’ll add a local StaticResource reference to the MainWindowViewModel in the MainWIndow XAML, and bind the DataContext for the parent grid to it.


   1:  <Window x:Class="NowPlayingMVVM.MainWindow"
   2:          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:          Title="Zune Wall" 
   5:          xmlns:my="clr-namespace:NowPlayingMVVM.View"
   6:          xmlns:vm="clr-namespace:NowPlayingMVVM.ViewModel" Width="515" Height="325"
   7:          >
   8:      <Window.Resources>
   9:          <vm:MainWindowViewModel x:Key="MainVM"/>
  10:      </Window.Resources>
  11:      <Grid Height="280" Width="490" DataContext="{Binding Source={StaticResource MainVM}}">
  12:          <my:LargeCover  HorizontalAlignment="Left" VerticalAlignment="Top" />
  13:          ....



By binding the parent grid to an instance of the MainWindowsViewModel, the grid’s child elements can now bind to public properties of the viewmodel. The final step is to bind the DataContext of each cover to one of the Albums in the MainWindowViewModel’s Albums collection.


   1:  <my:LargeCover  Margin="0,0,0,0"     DataContext="{Binding Path=Albums[0]}" />
   2:  <my:MediumCover Margin="280,0,0,0"   DataContext="{Binding Path=Albums[1]}" />
   3:  <my:SmallCover  Margin="280,210,0,0" DataContext="{Binding Path=Albums[2]}" />
   4:  <my:SmallCover  Margin="350,210,0,0" DataContext="{Binding Path=Albums[3]}" />
   5:  <my:SmallCover  Margin="420,210,0,0" DataContext="{Binding Path=Albums[4]}" />



Yes, this is a quick and dirty approach to the problem, but it validates that our model, views, and viewmodel are working together nicely:

CoverArrayBound


What Next?


I’m still new to MVVM, but even without a lot of experience, this approach doesn’t feel right. A couple of objections right off the top of my head:



  • The data is hard-coded and will need to come from a live source in the future. What will that mean for the UI as data is collected in a background thread?
  • The views are all bound to a model entity, not a viewmodel. I need to break that connection – MVVM purists say that views shouldn’t know about the underlying model
  • The album covers are about to a specific index inside the ObservableCollection. I don’t think this will be efficient when I want to update the bound album, as I’ll have to fire a PropertyChanged event on the whole collection if I want my bound element to be notified. But what is the best approach going to be? Should my viewmodel have a property for each album that is bound? That might get out of hand when my view is made up of dozens of album covers.
  • How will I shuffle the bound collection so that the UI isn’t the same every time? I want to be able to write unit tests when I start working on my shuffle algorithm, which I need to consider as I approach this.

Download Project

No comments:

Post a Comment