Introducing Model-View-ViewModel

advertisement
Designing Engaging User Interfaces in Autodesk®
3ds Max® with WPF and Microsoft® Expression
Blend®
David Cunningham - Autodesk
Kelcey Simpson - Autodesk
CP5309
You have heard about the Autodesk 3ds Max .NET software development kit (SDK). You might now be
wondering how to design elegant and engaging user interface (UI) experiences using .NET that tie in
with core 3ds Max concepts, such as scene manipulation. In this class, we will walk through an example
plug-in that exposes an interface to the user through a 3ds Max extended viewport. David Cunningham
will demonstrate the SDK exposure and build a ViewModel for our .NET control. He will then implement
a first-draft UI using Windows® Presentation Foundation (WPF). He will hand this draft to Kelcey
Simpson, who will make the UI more engaging and visually attractive in Microsoft Expression Blend. By
the end of the class, developer and designer together will have created a useful tool that stands out from
the crowd.
Learning Objectives
At the end of this class, you will be able to:

Create elegant, flexible user interfaces with WPF technology

Describe potential developer/designer collaboration with WPF technology

Describe the .NET SDK in Autodesk 3ds Max

List and describe .NET framework development tools including Visual Studio® and Expression
Blend
About the Speaker
David Cunningham is the user interface feature lead for Autodesk® 3ds Max®. He has been working on
the Max Team for nearly nine years. He helped to integrate the cross-product Autodesk Ribbon
component into 3ds Max, and has worked on the original versions of the Asset Tracking System, as well
as the Scene Explorer. Along with several UI-related projects, he has worked to extend the 3ds Max
architecture to take advantage of the .NET platform. David is well versed in several UI platforms,
including .NET WPF and Forms, Win32 Windows programming, and Java Swing.
Insert Class Title as per Title Page
Introduction
Autodesk 3ds Max recently celebrated its 20th anniversary. A lot has changed in 20 years of
software development. We now live in a world of multiple ‘post-PC’ devices, and, in the
meantime, the internet has become ubiquitous. Users have become accustomed to using
fluent, elegant User Interfaces (UI) when interacting with all of the software they use on a dayto-day basis. Whether it is using an iPad to check the local news, browsing facebook on a
phone, or using Google Maps to plan a trip on a desktop browser, users have come to expect
refined software products. The Look & Feel matters as much as the result.
These products are built with newer technologies. In some cases, it’s Apple’s Cocoa
Framework. In others, it’s sophisticated Javascript that runs in a browser, or Adobe Flash, or
Microsoft Silverlight, or more recently HTML5. The commonality among these technologies is
that they allow rapid iteration over the visual design. In most cases, a User Experience (UX)
designer can work with the UI directly to experiment and refine workflows.
3ds Max Plug-ins are traditionally written in C++, and make use of the classic Microsoft Win32
API for exposing UI. Though functional, it certainly does not provide the flair that some of the
more modern toolkits available offer as a baseline.
The course will introduce techniques illustrating how 3ds Max plug-ins can introduce dynamic UI
into the product. During the presentation, we will discuss a recommended Software
Architecture for accomplishing this goal, then walk through an example of the development
process of a simple plug-in using this approach, and finally illustrate how this can lead to a
much more efficient and desirable collaboration between the plug-in developer and its designer.
Our example will demonstrate this technique using the .NET Windows Presentation Foundation
(WPF) technology, in conjunction with the Microsoft Expression Blend design tool.
This handout delves into the theory that the course presentation is based on. More specific and
pragmatic examples of the architectures discussed here will be demonstrated in the course.
Target Audience
This course is for software developers who are keen to learn how to structure their plug-in so
that they can develop a modern-looking user experience for their clients. User Experience
Designers could benefit from seeing what type of software development workflow are possible
for desktop application.
2
Insert Class Title as per Title Page
Recommended Reading
For more information on the topic of creating .NET plug-ins for 3ds Max, I recommend reading
my handout from a previous course taught at Autodesk University 2010.
http://au.autodesk.com/?nd=class&session_id=7173
3
Insert Class Title as per Title Page
Model-View-Controller
Originally described in 1979 by Trygve Reenskaug and introduced into the core of the SmallTalk
programming language, Model-View-Controller (MVC) is the original modern software
architecture for properly implemented application user interfaces.
This pattern breaks an application into three main parts: the Model, where the system and
business data is stored; the View, which is the representation of the data as seen by the user
(normally the Graphical User Interface (GUI)); and finally, the Controller, which acts as a
moderator, handling user input and feeding (committing) changes down to the Model.
The problem that MVC addresses is data synchronization, and to a certain extent scalability and
extensibility.
A classic example of an MVC application is Microsoft Excel. In a given spreadsheet set, you
might have one sheet that represents raw data entered in columns and rows. Another sheet
might have a pie-graph representation of that data. If the numbers are tweaked on the first
sheet, the user expects to see the graph automatically update on the second sheet. It’s
tempting to think of first sheet as the model and the second sheet as a View. This is incorrect;
the first sheet is a View of the Model of data which is held within the program as the current
Document, while the second sheet is also a View which reflects and represents the data held by
the Document. The application user, in an MVC application, never interacts with the model
directly.
Model
The Model is the representation of your business logic and data. This concept is well
understood today; previously, it was hard to conceptualize how there was a difference between
the logic that drove your data, and the logic that drove your entire application. Programs were
4
Insert Class Title as per Title Page
self-contained entities that rarely interacted with anything beyond the files they saved and
loaded. Nowadays the lines are not nearly as well defined.
For example, I use Microsoft OneNote, the excellent note keeping Office application, which in
the most recent version introduced “Cloud” notebooks. I can now take notes on this computer,
and on my home computer, and expect everything to synchronize. I recently installed the
iPhone version of the program which also provides the same functionality, but with a completely
different interface. The Model in this case is the notebooks themselves (both local and cloudbased) and the business logic is how actions from each interface get ‘remembered’ by the
model. It probably also encapsulates the function of synchronization of the notebooks.
There is a common assumption that a Model needs to map to a database. This is not always
the case. More appropriately, the Model is represented by an abstract interface for which data
can be retrieved, queried, and committed. How the Model is implemented is a detail that the
Controller and Views should not be unaware of.
View
The View is a representation of the model. It is not the only representation, since the
architecture intrinsically supports multiple varying Views that accurately reflect the Model. For
OneNote, the primary user-facing View which represents notebooks as a set of tabs in a nice,
easy-to-use interface, with an editing canvas for note sheets.
Sometimes you need to move a note sheet to a different section. If you choose the Move
function, you are shown a topological representation of all of your notebooks, and all of their
sections and note sheets, in a dialog. This is a different View of the same Model, one
specifically crafted to select an item from the Model, in response to a modal action: an action
that requires a response from the user and that blocks other interactive actions (i.e. Any Ok /
Cancel dialog box is a Modal action).
5
Insert Class Title as per Title Page
Controller
This is the logical layer that handles user input and provides a synchronization mechanism
between the Model and the View. Strictly speaking, the View is an Observer on the Model,
meaning that is receives notification any time the Model changes. The controller is what
interprets raw user input data and converts it to actions on the Model.
Going back to the OneNote example, the controller would be the behind-the-scenes code which
handles typing and user actions in the editing canvas, and converts that to ‘commits’ on the
Notebook Model.
The Controller concept is rather abstract and is the least intuitive part of this architecture. It is
not entirely clear why the Controller needs to exist outside of the View, and in fact many
implementations often roll the interactions into a single implementation (Controller-View). Often
the split doesn’t match up well with the interfaces and tools available for a particular UI
technology. For instance, Controller logic is not directly supported by Win32 in any way, shape
or form.
Why MVC?
Before the advent of GUI-based Operating Systems, there was not much need for the handling
of complex user input beyond what a command-prompt could give you. Even more complicated
programs such as word processors were really just extensions of the command-prompt
paradigm. The transition to Windows-based Operating Systems and multiple-window
application resulted in a need for more formalized approach to implementing User Interfaces.
Windows interfaces fundamentally changed the interaction patterns between the user and the
program: whereas before the user input could be handled by pure decision branches, now
programs were required to simply respond to the user input as it came in. Multiple-window
applications begat multiple synchronized views on program data. Having the program data live
in the same place as the logic that interpreted user commands was no longer scalable or
flexible enough for modern programming scenarios. MVC attempts to tackle that problem.
Why not MVC?
Though MVC is a fine architecture that has stood the test of time, it does have its complications.
The Controller is still a bit mysterious. What, exactly, is its role? If the View is an Observer on
the Model, then why do we really need a separate Controller object? What are the advantages
of splitting the user-interaction logic away from the View?
6
Insert Class Title as per Title Page
Also, it isn’t always clear what should go in the Model. Business data is the obvious candidate,
and is correctly represented, but what about other types of data? Should user settings go in the
Model? What about data that is actually only relevant within the context of certain types of
Views? What about data that represents the state of one or many Views (such as the current
user selection)?
Introducing Model-View-ViewModel
In 2005, Microsoft unveiled Windows Presentation Foundation (WPF), and at the same time
effectively defined a new software architecture for which this technology is well suited: ModelView-ViewModel (MVVM). MVVM is a modification of MVC that takes into account the need to
split development of UI into two major components: the back-end business logic and data
management (handled by software developers) and the front-end User Experience (handled by
User Experience (UX) designers).
WPF broke away from traditional desktop UI development, and presented a model that was
much closer to modern Web Application development. By providing XAML (an XML-based
declarative language, pronounced za-mel, like ‘camel’) as a markup language that binds to
back-end data, Microsoft was tipping its hat to web development and recognizing that this is a
very effective way of splitting the View from the Model, and provides tremendous benefits when
developing and maintaining the application.
The major difference between MVVM and MVC is that with former, the View is never meant to
interact directly with the Model layer. Instead, we define a ViewModel layer which acts as a
façade to the Model, to which the View binds itself. The ViewModel assumes the Controller role
of managing the display logic, though the View itself handles incoming user input (classically
handled by the Controller according to MVC). This actually makes a lot of sense, and is the
confusing aspect of MVC… why would the controller actually care about how input was handled
by the View? It shouldn’t; it should only really care about the user intent.
7
Insert Class Title as per Title Page
Data Binding
WPF introduces a very sophisticated Data Binding engine which allows a user to declare
binding intentions between a control and the data it is representing. Every UI control in WPF
has a DataContext property, which in turn is scoped so that all of the children of that control
inherit the context, unless specified otherwise.
The DataContext property is the primary mechanism by which a developer binds a ViewModel
to a View, but it is not the only mechanism. Conventional approaches would call for setting up
handlers on Events exposed by the UI and writing binding code which transfers the results of
these events down into the ViewModel layer. As a rule of thumb, this is a mechanism to avoid if
possible while working with WPF, because handlers suffer from all the detriments that active
code suffers from. Mainly, it is far more likely that the control will not be design-friendly.
The WPF exposes a augmented property system (called the DependencyProperty system)
which is far more complex than what traditional .NET properties provide. Discussing
DependencyProperties in-depth is beyond the scope of this article. WPF intrinsically declares
all UI controls in a parent-child relationship, which is directly reflected by the XML-structure in
the XAML declarative language. One very interesting feature of DependencyProperties is that
they can be declared as Inherited, meaning that if a parent’s property is set, all children of that
parent node will inherit the change automatically, unless specifically set on one or more of the
child nodes. This is how the DataContext property of a parent node (declared as Inherited) is
8
Insert Class Title as per Title Page
automatically accepted in the scope of all of its children. WPF Bindings, by default, set the
target of a Binding to the DataContext property. This information is unique to WPF, but similar
mechanisms exist in other UI frameworks, such as Apple’s Cocoa and Cocoa Touch
technologies.
A binding represents a link between a UI property and a path on the DataContext. WPF is
particular powerful because it doesn’t make any assumptions about what the DataContext
should be; It can be any kind of object.
<ListBox x:Name="PanelContainer" ItemsSource="{Binding Path=Panels}" />
This XAML-snippet is a simple ListBox declaration. The extension property Name is a signal to
the compiler to generate a local variable under the hood that can be accessed from code-behind
(normally, C# or VB.NET code in WPF). The second property, ItemsSource, is bound (using the
XAML markup shorthand brace syntax) to an arbitrary .NET property called Panels. What’s
really interesting is that the binding is non-type-safe, but fail-safe. The Binding gets created as
an entity, but it won’t panic if a) there is no DataContext, or b) the DataContext doesn’t actually
have an appropriate property named Panels. In both cases, the binding engine shrugs and
does nothing. In fact, if the DataContext changes at any time, the binding will automatically try
to re-establish itself against the new context. Often times the real DataContext is only supplied
at runtime by an application bootstrapper. At design-time, a design-only mock DataContext can
be used.
It’s the fail-safe approach to bindings that is the key to implementing a development process
which supports Designer tools such as Expression Blend. Most desktop UI is hooked up with
active code. Often it involves event handlers, or Windows callbacks (known as “procs”), or
some relatively straight-forward approach of translating a mouse-click or keyboard press into “a
thing that does something in the program”. It is an inherently coupled approach. A common
tenant of good software design is to decouple the concerns of two pieces of code. One piece
does one thing while the other does something else, but they should never concern themselves
with the order of things or how the other piece accomplishes that thing. Data Binding is a
formalized mechanism for implementing Separation of Concerns.
By being inherently fail-safe, binding allows the UI to be instantiated in an environment outside
of the program it is meant to run in. Why is this useful? Because a tool such as Expression
Blend can create your UI in its Designer sandbox without the fear of activating code which
9
Insert Class Title as per Title Page
implicitly expects a very different context (that it runs within the context of your application, for
instance).
How is this possible? How can you realistically design a User Inteface that has no knowledge of
the application it represents? The answer is lies below.
Command Binding
The Binding engine does not just allow for binding of data… it allows for the arbitrary binding of
any object. This means that we can bind in polymorphic behavior. In other words, we can
make use of Object-Oriented Programming’s (OOP) most powerful mechanism: abstraction.
All WPF controls that are meant to initiate some action actually expose a Command property
that only expects an object that implements the ICommand interface. This property can be
bound to an object in the ViewModel. We can also bind an arbitrary object as the Command’s
parameter (the CommandParameter property).
This is design by composition, rather than inheritance. Here is a simple example to illustrate: a
naïve approach to doing OOP design is to declare a base class called Button, and then derive
from that class to implement all the types of buttons you’d want (SaveButton, LoadButton,
BakeButton, etc.) Maybe Button has a virtual method that can be overridden to supply the
command part of the button. So the SaveButton has a Command method that does everything
required to “Save”. And so on and so forth.
The problem with this paradigm is that to instantiate a SaveButton, your UI implicitly has to
know how to “Save”. But if you want your UI to live outside of your application, then this is going
to be a problem, because it likely has to bind to some interfaces that require a certain set of
libraries to be loaded. Sometimes loading those libraries is not trivial, and really requires the
bootstrapping of a larger application (say, Autodesk 3ds Max).
A design by composition would reframe the problem. It would acknowledge that Save, Load
and Bake all have a common ancestor (they are all Commands), but it would argue that they
have nothing to do with a Button. In fact, a single Button definition that has a slot in it where you
10
Insert Class Title as per Title Page
put the Command behavior would be far more flexible. You could even imagine that the same
Save command could be re-applied to a menu item, or a toolbar button, or could even be called
from a scripting language.
In fact, most modern UI toolkits already work this way. Actions and Commands are ubiquitous
amongst all modern technologies (Java Swing, Apple Cocoa and Cocoa Touch, all .NET
toolkits, and QT, to name a few.) In my experience, WPF is the first language which promotes
as a top-level concept the idea that the relationship between a control and its executing
command should be loosely coupled (via a Binding). QT exposes Signals and Slots, which is
actually type-safe, meaning that you don’t get the same flexibility and fail-safeness that Bindings
provide. You do get better compile-time checking, which is certainly a problem with WPF. I
suspect that later versions of QT have a form of loose bindings via XML-defined interfaces
generated through the QT Designer, though I personally do not know enough about the
technology to comment on it.
This fail-safe behavior allows the developer to late bind the logic needed to perform operations,
meaning that the logic is only supplied when the UI control exists in a context where it could
reasonably execute that logic. So in a design-scenario, we never supply the Commands, and
the button just sits there dumbly. This is fine, since we are only interested in the superficial
Look & Feel details at this point:





Visual appearance (default visuals, themed visuals)
State transition appearance (mouse over -> mouse left button down -> mouse left button
up)
Resizing behavior (what if the canvas UI is resized – how does the layout manager
respond?)
Navigation controls (page flipping or ‘drilling down’ on a system menu in an iPhone app,
for example)
Visual cues (mouse over behavior, drag & drop)
These points are all key to the user experience of an application, and yet they almost certainly
have nothing to do with the business logic of the application. They are the interface to the logic
and the functionality it provides. But keep in mind that for many users, the User Interface is the
program.
Only when the UI is run within the context of the target application are the actual Commands
and real bindings to data established, via the application bootstrapping process. In MVVM, we
accomplish this magic trick by substituting in a ViewModel that matches the running context.
11
Insert Class Title as per Title Page
The ViewModel
The role of ViewModel is simply to expose a façade of data and commands that a View can bind
to. In WPF and Silverlight, a ViewModel can be, and often is, a straightforward .NET object that
does not derive from any specific base class. It will, however, implement a very specific
interface: INotifyPropertyChanged. This interface exposes a single event only:
PropertyChanged. The contract of this interface is that the event is raised whenever a property
in the ViewModel changes. The event is raised with a typed argument which specifies the name
of the property that changed. That is the singular requirement of a ViewModel in WPF.
By abiding by this requirement, the simple .NET object, often called a CLR (Common Language
Runtime) object, can participate in the Data Binding mechanism as a Source of a Binding (but
not a target).
12
Insert Class Title as per Title Page
This notification interface is the front-facing requirement. The ViewModel must also hook itself
up as an Observer of the Model that it is representing. How it does this depends heavily on the
notification system that the Model supplies. Event handlers might be the order of the day, or
perhaps callback methods. In the case of 3ds Max, for example, you may want to create a
plugin that exposes parameters on a parameter block, or alternatively on one of the
application’s internal tools. You may have to use Max’s Broadcast Notification system, or listen
for Node-Changed events. In either case, Max’s data becomes the Model, and your job as a
plugin implementer would be to ‘hide’ those details behind a relatively simple, but informative,
ViewModel object.
When implementing a ViewModel, it is hard not to notice the redundancies in the design. By its
very nature, a ViewModel duplicates properties that are directly available on the model. An
over-eager optimizer might wonder why we simply don’t just bind a View directly to the Model.
In some cases, this is a sound decision to make if the overhead of the notification system for the
Model is too high, or if transferring the data up to a second layer would be too expensive.
However, it makes the design more rigid and error prone. Changes to the model can now
directly break functionality in a View. This should not be the case in this type of architecture,
and we can in fact validate against this type of breakage by supplying Unit Tests for the
ViewModel.
Unit Tests
A ViewModel must be agnostic of the View that is bound to it. This should be a very strict rule in
any MVVM application. This is important is because we should be able to create a ViewModel
without creating its associated View(s). By doing this, we introduce the possibility of unit testing
the ViewModel without even needing to create the View normally associated with it.
Interestingly, you could argue that in this scenario, a suite of Unit Tests becomes just another
View bound to the ViewModel. The ViewModel can be seen as a glorified state machine. It
represents the application in an abstract sense, but that doesn’t make the logic that it
implements any less important. By supplying unit tests for the ViewModel, we can detect how
changes to the Model might potentially break the UI, without even creating the UI itself! Unit
testing the ViewModel won’t detect every UI bug, of course, but it can at least validate the core
View-related logic, and the conceptual bindings from the Model to the ViewModel.
Design-Time ViewModel
13
Insert Class Title as per Title Page
The loose coupling between the View and ViewModel is a key feature of the architecture which
leads to a strong developer/designer collaboration. Since there are no hard connections, it
should be trivial for a developer to create a “dummy” ViewModel with some sample data that the
designer can design against. Most UI of any complexity needs to represent data in some way,
whether it be a list of runtime determined options in a dropdown, the arrangement of the buttons
on a toolbar (the buttons representing actions in the application), or even a pick-list of
application objects in a listbox. Any non-trivial UI will need to be designed within the context of
a data source.
WPF, and in particular Blend, supports this concept directly by allowing you to specify a
DataContext as an attached designer property. Blend introduces many properties that are
meant to be design-time only, such as the component’s visibility. It is possible to flip a
component’s visibility on and off without fear that you are somehow affecting the visibility in the
finished product, which is tremendously useful for a designer. Using the same principles, you
can supply data that Blend will only consider at design-time. This concept will be explored and
demonstrated further in the actual presentation.
Where do we go from here?
During the presentation, we will look at specific examples of MVVM architectures. We will
illustrate the implementation of such an architecture in a short but useful 3ds Max plug-in. My
colleague Kelcey Simpson will illustrate how properly laid out code can be effectively and nondestructively manipulated by a user experience designer using the appropriate design tools,
even without much knowledge of the underlying development framework.
14
Download