Академический Документы
Профессиональный Документы
Культура Документы
Overview
Do you find yourself struggling to grasp the concepts of the Model-View-ViewModel (MVVM) design pattern? Have you found that you can understand the
Why MVVM?
The whole point behind MVVM is to separate UI logic from your business and data
logic. You want to be able to test your business and data logic without having to
run your user interface. You can use MVVM to do this and still code your user
interface layer just like you are used to.
Before diving into MVVM it will be helpful to understand data binding in XAML.
Once you understand data binding in XAML you have the foundation you need to
apply a MVVM architecture to your WPF or Silverlight applications. Lets start our
discussion by taking a look at a sample WPF window that will be used to illustrate
the various concepts.
The Sample
Figure 1 shows a sample screen used to display product data, and also allow the
user to add and modify that product data. This is a WPF application, but the basic
data-binding concepts apply equally to Silverlight applications as well. This screen
will be used throughout this article to illustrate the various concepts of data binding
and ultimately an MVVM model.
14-2
The Sample
The sample application uses an XML file for the product data, but could just as
easily go against a database. This sample uses a class called ProductManager
for all data access routines. There is a Product entity class that contains one
property for each data field in your data store.
14-3
14-4
The Sample
RaiseEvent PropertyChanged(Me, New
PropertyChangedEventArgs(propertyName))
End Sub
#End Region
#Region "Private Variables"
Private mProductId As Integer = 0
Private mProductName As String = String.Empty
Private mPrice As Decimal = 0
Private mIsActive As Boolean = True
#End Region
#Region "Public Properties"
Public Property ProductId() As Integer
Get
Return mProductId
End Get
Set(ByVal value As Integer)
If mProductId <> value Then
mProductId = value
RaisePropertyChanged("ProductId")
End If
End Set
End Property
Public Property ProductName() As String
Get
Return mProductName
End Get
Set(ByVal value As String)
If mProductName <> value Then
mProductName = value
RaisePropertyChanged("ProductName")
End If
End Set
End Property
...
More properties here
#End Region
End Class
Listing 1: The Product Entity Class
14-5
You will use the Loaded event procedure of the WPF window to populate the list
view using the code shown in Listing 3. When you set the DataContext property
with the ObservableCollection of Product objects, the {Binding} in each
GridViewColumn shown in Listing 2 grabs the data from the Product objects
property to display in the appropriate column in the ListView.
C#
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ProductManager mgr = new ProductManager();
lstData.DataContext = mgr.GetProducts();
}
VB.NET
Private Sub Window_Loaded(ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Dim mgr As New ProductManager()
lstData.DataContext = mgr.GetProducts()
End Sub
Listing 3: Calling GetProducts to load the ListView
14-6
The code in Listing 4 should be fairly standard code that you are used to writing if
you have ever coded Windows Forms or ASP.NET. You also know that you need
to write code to take the data from the forms and move it back into the Product
object prior to passing this object to the Insert or Update methods on your data
class. You end up with a lot of code just to move data back and forth between your
UI and your entity class. When you use XAML data binding, you can completely
eliminate this code! Lets take a look at how to do that.
14-7
In the above XAML, the Grid named grdDetail contains one text box that has a
{Binding} in its Text property. If you set the DataContext of the Grid control to an
instance of a Product object, then the Text property of the TextBox will display the
ProductName property within that Product object as shown in the following code:
C#
Product entity = new Product();
entity.ProductName = "A New Product";
grdDetail.DataContext = entity;
VB.NET
Dim entity As New Product()
entity.ProductName = "A New Product"
grdDetail.DataContext = entity;
You can take advantage of this technique to eliminate the code shown in Listing 4
that moves the code from the Product object into the various controls. Replace the
code in the SelectionChanged event with the code shown in Listing 5.
C#
private void lstData_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
_Entity = (Product)lstData.SelectedItem;
grdDetail.DataContext = _Entity;
}
VB.NET
Private Sub lstData_SelectionChanged(ByVal sender As Object, _
ByVal e As SelectionChangedEventArgs)
mEntity = DirectCast(lstData.SelectedItem, Product)
grdDetail.DataContext = mEntity
End Sub
Listing 5: Wrap controls into containers to make data binding easier.
14-8
14-9
}
VB.NET
14-10
End Class
Listing 7: Add Properties to your Window to Control UI Elements
14-11
Now in the code-behind for your window if you set the IsAddEnabled property to
False, then the Add button will become disabled automatically. It is important that
you set the property IsAddEnabled, not the private variable that this property uses
as its backing data store. The Set procedure for the IsAddEnabled will raise the
property changed event which is how XAML is informed of the change in value and
then knows to refresh its controls UI state.
A Simple ViewModel
Now that you are familiar with data binding both in terms of data objects and UI
objects, you can now expand on your knowledge to create a simple ViewModel and
eliminate even more code-behind from your windows. If you download and look at
each of the samples illustrated in this article, you will find that each window has
about 200 lines of code-behind. When you start using a ViewModel you will cut this
amount by more than half!
Remember that a view model is just a class with code, there is no magic. In the
sample application that goes along with this article, there is a class called
ProductViewModel. You will create an instance of this class by creating it as a
resource in your XAML. First you will create an XML namespace and give that
namespace a name. In the XAML shown in Listing 9 the fourth line down creates a
namespace called local and assigns that namespace to the name of the assembly
where the ProductViewModel class is located. Next, in the <Window.Resources>
section of the XAML is where you create an instance of the ProductViewModel and
assign it a key called viewModel.
14-12
A Simple ViewModel
<Window x:Class="winSimpleMVVM"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVMSample"
Title="Simple View Model Sample" >
<Window.Resources>
<local:ProductViewModel x:Key="viewModel" />
</Window.Resources>
...
</Window>
Listing 9: Use XAML to create an instance of a ViewModel class
14-13
You use these two properties as the source of data for the ListView control (Listing
11) and as the source of data for the Grid that contains all of the detail text boxes
and the check box (Listing 12).
<ListView x:Name="lstData"
ItemsSource="{Binding Path=DataCollection}">
...
</ListView>
Listing 11: Bind the ListView to a property of the ViewModel class
14-14
A Simple ViewModel
<Grid DataContext="{Binding Path=DetailData}">
...
</Grid>
Listing 12: Bind the Detail Grid to a property of the ViewModel class
Since you are doing data access in the constructor it is very critical that you have
excellent exception handling in place when retrieving your data. The GetProducts
method contains all of the exception handling in this example.
Notice that the ProductViewModel class implements the INotifyPropertyChanged
interface. In the prior example, this interface was added to the Window class
14-15
VB.NET
Partial Public Class winSimpleMVVM
Inherits Window
Private mViewModel As ProductViewModel
Public Sub New()
InitializeComponent()
' Initialize the Product View Model Object
mViewModel = DirectCast(Me.FindResource("viewModel"), _
ProductViewModel)
End Sub
...
End Class
Listing 14: Grab the instance of the ViewModel class in your code-behind.
14-16
14-17
14-18
If you want, you can get rid of some of these events using the Commanding model
available in WPF. However, you end up writing a lot more code to support the
command model, and to me there is very little benefit. You have accomplished the
goal of moving all logic into the view model class so that you can unit test the view
model. There is no UI code at all in the view model class and thus can be tested
very easily using automated tools. Another problem with the command model is
that not all events can be hooked up to commands. So at some point you then
have to write some very complicated code to hook to all these events. I find the
simpler approach that I have laid out in this article a good compromise between
having everything in the code-behind and a pure view model approach. I have
accomplished the goals of MVVM but I have kept my programming model simple
and easy to understand.
14-19
Summary
Once you understand the basics of data binding in XAML, you can eliminate a lot
code that is otherwise needed to move data into controls and out of controls back
into an object. This forms the basis of a MVVM approach. You are used to writing
classes, you just need to get used to the idea of using properties on classes to
affect UI properties. In this article you saw a very simple and easy-to-use pattern
for MVVM. While the purists would disagree with this approach, those folks that like
things simple and easy to understand should be satisfied.
Chapter Index
A
An MVVM Sample, 14-2
B
Bind to UI Properties, 14-9
Binding using ElementName and Path, 149
D
Data Binding Basics in XAML, 14-7
DataCollection Property in MVVM Sample,
14-13
DetailData Property in MVVM Sample, 1413
I
INotifyPropertyChanged, 14-3
14-20
M
Model View View Model, 14-1
MVVM, 14-1
MVVM Made Simple, 14-1
P
Product Class in MVVM Sample, 14-3
ProductManager Class in MVVM Sample,
14-5
ProductViewModel Class in MVVM, 14-12
S
Simple View Model Class, 14-12
W
Why MVVM?, 14-2