Overview:
MVVM stands for Model-View-ViewModel. This is a variation of the MVC pattern, and although MVVM has a controller like component called the ViewModel, I personally find it easier to still call this the controller.
The basic premise of any derivative of the MVC Pattern.
The main idea of MVVM is to take full advantage of WPF Data-Binding to allow very loose coupling between the Controller and the View. This means the Controller does not have a reference to the View and the View only has an 'object' typed reference to the controller using its DataContext property. This makes the Controller a POCO that can easily be tested and its dependencies mocked/stubbed (ie it doesn't have a reference to a Window/UserControl/Page object that is difficult to mock).
More Info Links:
Here's a quick reference list of resources on the WPF MVVM pattern.
2) Microsoft Patterns And Practises for Composite Application Design (Ms Patterns and Practise Dev Center)
Here's a diagram from Karl Shifflett giving a good overview of what each component of the MVVM model contains:
Coupling Between Views and Controllers
There are two schools of thought on how exactly to link your views to your controllers. It depends on how purist you are on maintaining loose coupling between views and controllers, and how reusable you would like your views (xaml) to be. Most of the time, despite best efforts, I have found that reusing Xaml occurs pretty infrequently, except of course in the case of properly designed controls, for example a customised Drop-Down-List is highly reusable, but a User-Control isn't that reusable.
The Purist Loose Couple Approach
Following the loose coupling purist path neither the views nor the controllers have links to each other. Its only the use case that links them together. The downside of this, is that instantiating the view and controller can be a wordy process. This can be simplified a little however.
I found the process of instantiating the views and controllers in pairs and setting close handlers a little tedious; and possibly forgetting a step results in annoying but obvious bugs. The process of constructing the pairs and initializing can be simplified with a Builder Pattern.
The code before:
9 var window = new MainWindow();
10 var viewModel = new MainWindowViewModel("Data/customers.xml");
11
12 EventHandler handler = null;
13 handler = delegate {
14 viewModel.RequestClose -= handler;
15 window.Close();
16 };
17 viewModel.RequestClose += handler;
18
19 window.DataContext = viewModel;
20 window.Show();
The code after:
9 var builder = new ViewControllerPairBuilder(() => new MainWindow(), () => newMainWindowViewModel("Data/customers.xml"));
10 var viewModel = builder.BuildViewAndController();
11 builder.ShowView();
The Simplistic Approach
This involves creating less than loose coupling between the view and the controller. There are several ways to do this depending on what is easy in the use case.
In the constructor of the view it instantiates the controller and sets its own DataContext.
7 public partial class Shell : IDisposable {
8 private readonly IViewController controller;
9 private CommandBinding applicationCloseBinding;
10
11 /// <summary>
12 /// Initializes a new instance of the <see cref="Shell"/> view class.
13 /// </summary>
14 public Shell() {
15 this.InitializeComponent();
16 this.controller = new ShellController();
17 this.DataContext = this.controller;
18 this.Closing += this.MainWindowClosingHandler; // Notify controller of closing
19 this.Loaded += (s, e) =>this.SetWindowSize(Properties.Settings.Default.WindowPosition);
20 this.controller.RequestClose += this.ControllerRequestClose;
21 }
22 public void Dispose() {
23 var disposable = this.controller as IDisposable;
24 if (disposable != null) {
25 disposable.Dispose();
26 }
27 }
Or better yet, the DataContext can be set with binding from a parent control. This is better because the coupling between view and controller is on a per-use-case basis. Therefore in the case of the BannerView view there will be no reference to the controller.
20 <local:BannerView
21 x:Uid="ContentPresenter_1"
22 DataContext="{Binding Path=TopBannerRegion}"
23 DockPanel.Dock="Top" />
Or, let Xaml create the instance for you...
20 <local:BannerView x:Uid="ContentPresenter_1">
21 <local:BannerView.DataContext>
22 <local:BannerController />
23 </local:BannerView.DataContext>
24 </local:BannerView>
Or, let Wpf choose a DataTemplate appropriate for the underlying bound property, which is an instance of a controller.
19 <ContentPresenter
20 x:Uid="ContentPresenter_4"
21 Content="{Binding Path=TopBannerRegion}" />
22 <DataTemplate
23 DataType="{x:Type local:BannerController}">
24 <local:BannerView />
25 </DataTemplate>
This approach means you can show any kind of controller in that part of the UI as long as you have taken the time to define a DataTemplate to tell Wpf how to show something of that type. This technique is great for showing any content in a "Main" panel within a Wpf Application
Generally it is acceptable to maintain a link to the controller within the view, because the views generally are not unit tested. A reference to the controller may be required if events are subscribed to and unsubscription needs to occur during the view shutdown. When a case arises to reuse the view in a completely different use case that requires a different controller, a common interface should be constructed and the view only contains an interface reference.
I personally believe the simplistic approach is more preferable.
Controlling Setting Keyboard Focus
Today I read an excellent article on Josh Smith's site describing a very tidy technique for overcoming triggering a change of focus from within a controller (remember I like to call the ViewModel a controller). Ordinarily I have solved this issue with a rather cumbersome technique of exposing an interface either specific to the view or a general interface that passes a use case token to tell the view where to set the focus. This requires an interface to be implemented by the view and this to be consumed by the controller. This is clearly polluting the Mvvm pattern intention.
Josh's legendary idea is to override/extend the binding mechanism to check for an implementation of IFocusMover on the DataContext. If it detects one, it subscribes to the MoveFocus Event. This event is then caught by the new binding subclass and is moved on request.
Another competing strategy here by Anvaka. The crux of it is that Josh's above method could be accused of being a little opaque and not clear how it works and how to debug when things don't work as they should. A similar extension would need to be written for Multi-Binding as well. Anvaka's solution requires a little more code to work, but is much more clear. The downside is that the bound bool properties that indicate focus could all be true.
I think I personally still prefer Josh's method.
No comments:
Post a Comment