Anda di halaman 1dari 41

ADOPTING MVVM

John Cumming
Senior Engineer CM Group Ltd http://www.cmgroup.co.uk http://sputnikdev.wordpress.com

Overview
A bit about my experience of MVVM. What problems adopting MVVM addresses. An overview of the MVVM pattern. Adopting MVVM. A simple example.

About me

PROBLEMS MVVM ADDRESSES

Typical Development Problems


Tightly coupled brittle code that is difficult to maintain and extend. It can be difficult to separate UI state and logic from the UI presentation. Difficult to test UI state and logic. The designer is tied to the UI components written by the developer.

MVVM
Tightly coupled brittle code that is difficult to maintain and extend. It can be difficult to separate UI state and logic from the UI presentation. Difficult to test UI state and logic. The designer is tied to the UI components written by the developer.

Separation of Concerns

View UI Layout & User Interaction View Model UI State and Logic Model Business Logic and Data

Natural XAML Pattern

Data Binding DataContext Data Binding Dependency Properties Commands Executing Actions & Operations

Increased Testability

UI State & Logic independent of UI technology.

Developer Designer Workflow

Designer has freedom to design. Designer can work independently. Designer does not need to touch code. Designer can create design time sample data.

MVVM OVERVIEW

MVVM View

The View defines the structure and appearance of the UI. The Views DataContext is a View Model. Minimal, if any, code behind. View updated by notification events from the View Model. UI Gestures are passed to the View Model, usually via Commands.

MVVM View Model

The View Model is responsible for UI logic, data and state. The View Model exposes bindable properties. The View Model exposes ICommand properties for UI controls with Command properties. The View Model exposes public methods that can be invoked by the View. The View Model is completely testable.

MVVM Model

The Model is responsible for business logic and data, the client side Domain Model. The Model can expose bindable properties for use by the View directly or via the View Model as an Adapter. Model objects can implement IDataErrorInfo to provide the View with validation state information.

MVVM Commands

Invoked in the View by user interaction. Bound to ICommand properties in the View Model. Differ from WPF Routed Commands as they are not delivered via the UI logical tree. Equally, Routed Commands will not get delivered to the View Model as it is outside the UI logical tree.

Data Validation / Error Reporting

Implement IDataErrorInfo or INotifyDataErrorInfo in View Model and Model classes. IDataErrorInfo presents basic indexed error information. INotifyDataErrorInfo presents multiple errors per property, asynchronous data validation and notification of error state changes (In WPF 4/ Silverlight 4).

ADOPTING MVVM

A Typical MVVM Application


D:DataContext d:DesignSource Objects or Methods Param-less Contructors
IDateErrorInfo INotifyDataErrorInfo

Order of Construction

Dependency Injection

Construction and Wire Up


One-to-One loosely coupled relationship between View and View Model. Create View as a Data Template. The View Model is instantiated first. Data Templates are lightweight and flexible. No code behind is available for Data Templates.

Construction and Wire Up


Create the View Model in the Views XAML. Requires a parameterless ctor in the View Model. Works well in design tools. The View must have knowledge of the View Model type. Instantiate the View Model in the Views code behind. The View must have knowledge of the View Model type. Loose coupling can be achieved by use of a dependency injection container such as Unity.

Provision of Design Time Data


Expression Blend XML Sample Data.
Simple to create in Blend. Not rendered in Visual Studio. Sample data not compiled in, but schema is.

XAML Sample Data.


Uses d:DesignData XAML markup extension. Supported in Blend 4 and VS2010. XAML sample data is not compiled into assemblies.

Provision of Design Time Data


XAML Resource.
Provide a simple resource instantiating desired types. Useful for quick throw away data when editing a template.

Code.
Uses d:DataContext and d:DesignInstance XAML markup extensions. May need to be maintained by developers.

Key Decisions
Construct Views or View Models first and use of a dependency injection container. Expose Commands from the View Model as Command objects or Command methods. Use of IDataErrorInfo / INotifyDataErrorInfo. Provision of design time data for use in Expression Blend.

AN MVVM EXAMPLE

In the Box MVVM Training


http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/

Basic WPF Application

Pre MVVM
Bootstrapping StartupUri for App
<Application x:Class="WpfMediaPlayer.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml">

UI Interactions implemented as code behind


private void MediaList_MouseDoubleClick( object sender, MouseButtonEventArgs e) { MediaFile mediaFile = MediaList.SelectedItem as MediaFile; if (mediaFile != null) { _mediaFolder.Play(mediaFile); CurrentItem.DataContext = mediaFile; } }

UI Binding to MediaFile Properties


_mediaFolder.Load(this.MediaFolderPath.Text); this.MediaList.ItemsSource = _mediaFolder.MediaFiles;

Pre MVVM - Comments


Low Concept Count. It is a simple app after all! Minimal code required for implementation. Testability - All UI logic and state is implemented as code behind and is difficult to test. Coupling - The MainWindow class needs knowledge of the data types. Extensibility / Maintainability Code rapidly becomes complex and potentially confusing as new UI features are added.

MVVM
Bootstrapping Dependency Injection Separate View Components in UI

Implement View Models


Bindable Properties Command Properties Decouple View Models and Model

MVVM View Components


<<View>> MediaListView <<View>> SelectedDirectoryView

<<View>> NowPlayingView

MVVM View Components


MainWindow.xaml
<Grid VerticalAlignment="Stretch" Margin="5"> <Grid.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Views/MediaListView.xaml"/> <ResourceDictionary Source="Views/NowPlayingView.xaml"/> <ResourceDictionary Source="Views/SelectedDirectoryView.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Grid.Resources> <Grid.RowDefinitions> </Grid.RowDefinitions> <ContentControl x:Name="SelectedDirectoryView" Grid.Row="0" Content="{Binding}" ContentTemplate="{StaticResource SelectedDirectoryViewTemplate}"/> <ContentControl x:Name="MediaListView" Grid.Row="1" Content="{Binding}" ContentTemplate="{StaticResource MediaListViewTemplate}"/> <ContentControl x:Name="NowPlayingView" Grid.Row="2" Content="{Binding}" ContentTemplate="{StaticResource NowPlayingViewTemplate}"/> </Grid> </Window>

MVVM View Components


SelectedDirectoryView.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:WpfMediaPlayer.ViewModels"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="..\Styles.xaml"/> </ResourceDictionary.MergedDictionaries> <DataTemplate x:Key="SelectedDirectoryViewTemplate" DataType="{x:Type vm:ISelectedDirectoryViewModel}" > <!-- File Picker --> <Grid Grid.Row="0" HorizontalAlignment="Stretch" Margin="0,0,0,5"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBox Grid.Column="0" IsReadOnly="True" IsEnabled="False" Text="{Binding DirectoryPath, Mode=OneWay}"/> <Button Grid.Column="1" Margin="5,0,0,0" Command="{Binding OpenCommand}"> Select Directory </Button> </Grid> </DataTemplate> </ResourceDictionary>

MVVM View Components


A little excessive for a small app? Extensible We can easily extend the individual Views if we wish without affecting the other views. Implement Views as Data templates

MVVM View Model Components


<<View>> MediaListView <<View>> SelectedDirectoryView <<ViewModel>> IMediaListViewModel
Commands: + ICommand PlayCommand Properties: + ObsCol<IMediaFile> MediaFiles

<<ViewModel>> ISelectedDirectoryViewModel
Commands: + ICommand OpenCommand Properties: + String DirectoryPath

<<ViewModel>> INowPlayingViewModel
Commands: + ICommand PauseResumeCommand Properties: +

<<View>> NowPlayingView

MVVM View Model Components


ISelectedDirectoryViewModel.cs
interface ISelectedDirectoryViewModel : INotifyPropertyChanged { String DirectoryPath { get; } ICommand OpenCommand { get; } }

MVVM View Model Components


Decoupling UI actions using Commands. NowPlayingViewModel provides an adapter for the IMediaFile. Other Views bind directly to IMediaFile. UI behaviour is now completely testable as there is no code behind any of the Views.

Model Components
<<View>> MediaListView
<<Model>> MediaFile MediaFolder MediaPlayer

<<View>> SelectedDirectoryView

<<ViewModel>> IMediaListViewModel
Commands: + ICommand PlayCommand Properties: + ObsCol<IMediaFile> MediaFiles

IMediaFile IMediaFolder

<<ViewModel>> ISelectedDirectoryViewModel
Commands: + ICommand OpenCommand Properties: + String DirectoryPath

<<ViewModel>> INowPlayingViewModel
Commands: + ICommand PauseResumeCommand Properties: +

<<View>> NowPlayingView

MVVM Bootstrapping
App.xaml.cs
protected override void OnStartup(StartupEventArgs e) { using (IUnityContainer container = new UnityContainer()) { // register models container.RegisterType<IMediaFolder, MediaFolder> (new ContainerControlledLifetimeManager()); // register view models container.RegisterType<ISelectedDirectoryViewModel, SelectedDirectoryViewModel> (new ContainerControlledLifetimeManager()); container.RegisterType<IMediaListViewModel, MediaListViewModel> (new ContainerControlledLifetimeManager()); container.RegisterType<INowPlayingViewModel, NowPlayingViewModel> (new ContainerControlledLifetimeManager()); MainWindow window = container.Resolve<MainWindow>(); window.SelectedDirectoryView.DataContext = container.Resolve<ISelectedDirectoryViewModel>(); window.MediaListView.DataContext = container.Resolve<IMediaListViewModel>(); window.NowPlayingView.DataContext = container.Resolve<INowPlayingViewModel>(); window.Show(); } }

MVVM - Comments
Testability UI Logic and state is independent of UI layout. The layers have been successfully decoupled to assist in independent testing and development of components. The code is potentially simpler to extend into a larger application and will be simpler to maintain. High Concept Count, especially for a simple app.

Summary
The MVVM pattern is well suited for WPF / Silverlight development. Adopting MVVM can improve maintainability. Adopting MVVM can improve quality. Adopting MVVM frees up designer creativity. Adopting MVVM can improve the product.

Questions