共用方式為


Introduction to Model/View/ViewModel pattern for building WPF apps

Model/View/ViewModel is a variation of Model/View/Controller (MVC) that is tailored for modern UI development platforms where the View is the responsibility of a designer rather than a classic developer.  The designer is generally a more graphical, artistic focused person, and does less classic coding than a traditional developer..  The design is almost always done in a declarative form like HTML or XAML, and very often using a WYSIWYG tool such as Dreamweaver, Flash or Sparkle.  In short, the UI part of the application is being developed using different tools, languages and by a different person than is the business logic or data backend.  Model/View/ViewModel is thus  a refinement of MVC that evolves it from its Smalltalk origins where the entire application was built using one environment and language, into the very familiar modern environment of Web and now Avalon development.

Model/View/ViewModel also relies on one more thing:  a general mechanism for data binding.   More on that later.

The Model is defined as in MVC; it is the data or business logic, completely UI independent, that stores the state and does the processing of the problem domain.  The Model is written in code or is represented by pure data encoded in relational tables or XML. 

The View in Model/View/ViewModel consists of the visual elements, the buttons, windows, graphics and more complex controls of a GUI.  It encodes the keyboard shortcuts and the controls themselves manage the interaction with the input devices that is the responsibility of Controller in MVC (what exactly happened to Controller in modern GUI development is a long digression...I tend to think it just faded into the background.  It is still there, but we don't have to think about it as much as we did in 1979).  The View is almost always defined declaratively, very often with a tool.  By the nature of these tools and declarative languages some view state that MVC encodes in its View classes is not easy to represent.  For example, the UI may have multiple modes of interaction such as "view mode" and "edit mode" that change the behavior of the controls or the look of the visuals, but these modes can't always be expressed in XAML (though triggers are a great start).  We will solve this problem later.

At this point data binding comes into play.  In simple examples, the View is data bound directly to the Model.  Parts of the Model are simply displayed in the view by one-way data binding.  Other parts of the model can be edited by directly binding controls two-way to the data.  For example, a boolean in the Model can be data bound to a CheckBox, or a string field to a TextBox.

In practice however, only a small subset of application UI can be data bound directly to the Model, especially if the Model is a pre-existing class or data schema over which the application developer has no control.  The Model is very likely to have a data types that cannot be mapped directly to controls.  The UI may want to perform complex operations that must be implemented in code which doesn't make sense in our strict definition of the View but are too specific to be included in the Model (or didn't come with the pre-existing model).  Finally we need a place to put view state such as selection or modes.

The ViewModel is responsible for these tasks.  The term means "Model of a View", and can be thought of as abstraction of the view, but it also provides a specialization of the Model that the View can use for data-binding.  In this latter role the ViewModel contains data-transformers that convert Model types into View types, and it contains Commands the View can use to interact with the Model. 

I will develop these ideas, and describe in particular how to bind the View to Commands on the ViewModel in later posts.  But the quickest way to clarify this pattern is to provide some examples:

 

 

 

The above picture shows three editing panels in the Sparkle UI.  Each has been developed using the Model/View/ViewModel pattern.   The simplest is the Library Panel at the top.  The Model is a list of assemblies (each an instance of System.Reflection.Assembly), and associated with each list of assemblies is a list of controls.  The View is one of our Panel chrome controls and a series of Styles and DataTemplates that expose the assembly list in a ComboBox and the list of controls in a ListBox.  We data bind the caption of the ComboBox directly to the name of the Assembly and the items of the listbox take their text from the name of the Control.  The ViewModel has as state the currently selected Assembly and exposes Commands for inserting a control into the scene.   Selection is one of the most common components of a ViewModel.  But since controls have selection, you may wonder why selection isn't left in the View instead.  This is done because often many controls in the view need to coordinate based on a single selection.  It is easier to bind to a single representation of selection in the ViewModel than coordinate all the different controls in the view.  In the Library, the selected Assembly determines what is selected by the ComboBox and also what list data is displayed by the ListBox.  Additionally, the designer could decide to switch to using a ListBox for the assemblies and a ComboBox for the control list without copying over selection coordination logic from his original view.

The Appearance panel has as its Model the selected shapes or controls in Sparkle's editing area.  The View has a ListBox that displays the interesting properties on the selection (basically all the Pen and Brush properties), buttons that determine whether the Brush or Pen is solid, gradient etc. and a color spectrum for editing color components.  The ViewModel includes what property is selected, what gradient stop is selected when editing a gradient, data converters for mapping colors to text values and to positions in the color spectrum, and commands for changing the Pens and Brushes being edited.  In this case the Model was given to us by Avalon, the View could easily be changed to something radically different, and the ViewModel provides an abstract representation for the reusable parts of this UI.

The final example is our Project panel.  Here the Model is an MSBuild Project...again a Model class that was pre-existing.  The View is a tree control, scrolling area, and contains context menus.  The ViewModel adapts MSBuild concepts that were designed without Avalon in mind (and work perfectly fine from the command line) so we can data bind to them, and again contains selection and commands. 

Once you've grokked the Model/View/ViewModel pattern any UI problem is quickly stated in its terms.  In fact, the entire Sparkle UI is defined using the pattern.  The "selected shapes or controls in the editing area" that is the Model for the Appearance panel is itself a ViewModel concept from our Scene editor.  The layout of Panels inside Sparkle has a Model consisting of a list of all registered panels, a View made up of a Grid with splitters that position the views, and a ViewModel that includes what panels are currently visible and what logical containers they are in (editing area, right, left, bottom trays).

More later...

Comments