Introduction
In this article, we'll see some basic concepts about MVVM pattern (Model View Viewmodel), trying to stick as much as possible to its paradigms, namely a strong reduction (aiming at the complete suppression) of the code behind controls and graphics in general. We'll do that through XAML, and some classes prepared for that mean, and for data presentation.
MVVM pattern was made to keep an app graphical part separated from the business logic. Its fundamental paradigm is therefore the introduction, by the developer's efforts, of an intermediate layer between the View (namely, everything in the program which can be traced back to its UI) and the Model (or, the program logic, its flow, independently from graphical context. In this definition is also included the data management of data for which we desire to produce a graphical representation). I will put more emphasis on clarity compared to excessive technicality, aiming to be as simple as possible.
A first example
In this section we'll see two primary concepts about MVVM pattern. First, we must look briefly at DataContext and Binding concepts. DataContext is, in short, the source, or the origin, of the elements on the base of which we could use the Binding, which is the link between the value of a certain property and a visual control.
Let's suppose, for example, to have a WPF window with a TextBox, and in the latter we want to visualize the window's title. As a second thing, we want to be able to modify the window's title by modifying the content of TextBox. In other words, we desire to bind the two control in a bidirectional way. Therefore, we need to tell to the TextBox that its DataContext is the window, and the Binding to be executed on the Text property must lay on the Title property of the window. In the XAML of our window, we can realize such a thing with the following code:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Name="MainWindow"
Title="MainWindow" Height="111.194" Width="295.149">
<TextBox Name="TB1" Text="{Binding Title, ElementName=MainWindow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="248"/>
</Window>
In short, we assigned to the window a Name (MainWindow in the example). After that, we have binded the Text property of TextBox to the Title property, which belongs to MainWindow (which is, in such a case, DataContext of TextBox), in a bidirectional mode (TwoWay), indicating the property modification as the event which will launch the counterparts'update operations (PropertyChanged). For additional information on the syntax relative to Binding operations, see Basic Examples on WPF Data Binding.
Running this small example, we'll notice that the windows'title will be modified in function of which is digited in the TextBox. In the same way, if we modify our code to intervene on the windows'title, we'll see and update of the TextBox content.
Binding of a DataModel
Let's suppose we're in the need of managing a binding toward an external class, relatively to the context in which we'll show it. A class example follows, useful for an hypothetical product representation, with properties as a product's code and a description:
Public Class ItemData
Public _code As String
Public _des As String
Public Property Code As String
Get
Return _code
End Get
Set(value As String)
_code = value
End Set
End Property
Public Property Description As String
Get
Return _des
End Get
Set(value As String)
_des = value
End Set
End Property
Public Sub New(ByVal code As String, ByVal des As String)
_code = code
_des = des
End Sub
End Class
Being a class which can define new products and entities, equipped with a constructor which initializes its fundamental properties, that class can't be directly used as a DataContext as long as it isn't referenced in a variable. That means we must operate on the code-behind, if we wish to execute a Binding - let's say, of Code property - indicating the TextBox's DataContext only after we've successfully initialized an ItemData type variable.
For example, if we decide to manage the window's Loaded event, we could write:
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs)
Dim item As New ItemData("PRDCODE01", "TEST PRODUCT")
TB1.DataContext = item
End Sub
Where the corresponding XAML will be:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation [This link is external to TechNet Wiki. It will open in a new window.] "
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml [This link is external to TechNet Wiki. It will open in a new window.] "
xmlns:local="clr-namespace:WpfApplication1"
Name="MainWindow" Loaded="MainWindow_Loaded"
Title="MainWindow" Height="111.194" Width="295.149">
<TextBox Name="TB1" Text="{Binding Code}"
HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="248"/>
</Window>
Here, we'll indicate in the TextBox's Text property a more concise syntax, compared to its predecessor. Beyond the fact that we are not managing here the data bidirectionality, note the presence alone of Code property, without any hint to the element (or DataContext) from which it must be derived. This is because the DataContext is specified code-side, initializing an ItemData-type variable, and subsequently indicating as the TextBox data context the brand new variable. Running this example, we'll note that the TextBox exposes a value equal to PR_CODE_01, namely the string we've used to initialize the Code property of our ItemData.
As described in the opening, that kind of approach - although working - does not meet fully MVVM paradigms. In the above example, we have the View (our window) and the Model (item variable, referenced as ItemData). In the window's code-behind, we've created a reference to the model, creating an interdependence between the two: if we delete our code from the Loaded event, the TextBox binding will naturally cease to work. To keep separated the two entities, it is necessary an intermediate layer introduction, or the ViewModel: that will be a class through which we'll expose the Model to the View, making the two layers completely mutually independent. Let's see how it can be done.
A simple ViewModel
A ViewModel encapsulate a model, exposing all the properties which can be useful for the View to access underlying data. In general, it implements the INotifyPropertyChanged [This link is external to TechNet Wiki. It will open in a new window.] interface, which will be used, as an event, to trace all the changes of a specific property. In our case, assuming we want to make the more minimal of possible ViewModels, we could write a class like the following:
Imports System.ComponentModel
Public Class ItemDataView
Implements INotifyPropertyChanged
Dim item As New ItemData("PR_CODE_01", "TEST PRODUCT")
Public Property Code
Get
Return item.Code
End Get
Set(value)
If Not (item.Code = value) Then
item.Code = value
NotifyPropertyChanged("Code")
End If
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
This class initializes an ItemData, creating a new instance of it, and exposing its Code property. At the same time, it allows the modification of such property, raising a call to the PropertyChanged event, to produce a notification of the occurred change.
To make this ItemDataView visible and usable in the entire MainWindow context, we could indicate in the window's XAML the DataContext as global for all the controls contained in it. The TextBox Binding property will continue to be Code, exposed by the ViewModel:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Name="MainWindow"
Title="MainWindow" Height="111.194" Width="295.149">
<Window.DataContext>
<local:ItemDataView x:Name="MyItemView"/>
</Window.DataContext>
<TextBox Name="TB1" Text="{Binding Code}"
HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="248"/>
</Window>
Alternately, if we wish to indicate the specific TextBox DataContext, we could write:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Name="MainWindow" Title="MainWindow" Height="111.194" Width="295.149">
<TextBox Name="TB1" DataContext="{Binding Source=ItemDataView}" Text="{Binding Code}"
HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="248"/>
</Window>
In both cases, at runtime we'll see the TextBox Text property with a value of PR_CODE_01, obtained from the exposed ItemDataView's ItemData.
History
- 2015-01-05: First Release for CodeProject