onScope
This article explains some basic concepts about the MVVM pattern (Model View ViewModel) with an attempt to remain as much as possible on its paradigms, namely a strong reduction (aiming at the complete suppression) of the code behind controls and graphics in general. We'll do that using XAML and some classes prepared for that and for data presentation.
Introduction
The MVVM pattern was designed to keep an app's 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 that can be traced back to its UI) and the Model. (Or the program logic, its flow, independently from the graphical context. This definition also includes the management of the data for which we desire to produce a graphical representation.) I will place more emphasis on clarity rather than excessive technicality, aiming to be as simple as possible.
A first example
In this section we'll see two primary concepts of the 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 that 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 contents of the TextBox. In other words, we desire to bind the two controls in a bidirectional way. Therefore, we need to tell the TextBox that its DataContext is the window and the Binding to be executed on the Text property must placed 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 assign a name to the window (MainWindow in the example). Then, we have bound the Text property of TextBox to the Title property that belongs to MainWindow (that is, in such a case, DataContext of TextBox), in a bidirectional mode (TwoWay), indicating the property modification as the event that will launch the counterpart's 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 see that the window's title will be modified in the function of which it is digited in the TextBox. In the same way, if we modify our code to intervene on the window's title , we'll see and update the TextBox content.
Binding of a DataModel
Let's suppose we need to manage a binding toward an external class, relatively to the context in which we'll show it. A class example follows, useful for a 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
A a class that can define new products and entities, equipped with a constructor that 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 the 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"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- 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 here we are not managing the data bidirectionality, note the presence alone of the Code property, without any hint of 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 fully meet the MVVM paradigms. In the preceding 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 the two entities separated, it is necessary to create 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 encapsulates a model, exposing all the properties that can be useful for the View to access the underlying data. In general, it implements the INotifyPropertyChanged. This link is external to TechNet Wiki. It will open in a new window. An interface 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 a property, raising a call to the PropertyChanged event, to produce a notification of the change that has occurred. 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.
Conclusion
We saw here some basic features of the MVVM pattern implementation. We saw how it is possible to present and modify simple data using it, omitting further themes to point out more the general theoretical issue. In a future article, we'll see the MVVM approach applied to commands. As usual, my hope is to have been able to provide useful material for those who approach the topic. Happy coding!