Setting up an app to use the MVVM pattern is relatively straightforward, but can involve a lot of boiler plate code. Let's illustrate this so that you can see all of the steps and the way things work exactly:
Chapter2
.Model.cs
, as shown here:Model
class:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Chapter2 { public class Model { private string searchText = "Lancaster"; private string basemapLayerUri = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer"; private string usaLayerUri = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer"; public Model() { } public string SearchText { get { return this.searchText; } set { if (value != this.searchText) { this.searchText = value; } } } public string BasemapLayerUri { get {return this.basemapLayerUri; } set { if (value != this.basemapLayerUri) { this.basemapLayerUri = value; } } } public string USALayerUri { get { return this.usaLayerUri; } set { if (value != this.usaLayerUri) { this.usaLayerUri = value; } } } } }
Most of this is a pretty basic .NET code. It simply stores our layer's URIs and a search string. Now that the Model class has been created, it's time to create the ViewModel class:
System.ComponentModel
and System.Runtime.CompilerServices
namespaces.ViewModel.cs
) to the project and enter this code:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using System.ComponentModel; using System.Runtime.CompilerServices; namespace Chapter2 { public class ViewModel : INotifyPropertyChanged { public Model myModel { get; set; } public event PropertyChangedEventHandler PropertyChanged; public ViewModel() { myModel = new Model(); } public string BasemapUri { get { return myModel.BasemapLayerUri; } set { this.myModel.BasemapLayerUri = value; OnPropertyChanged("BasemapUri"); } } public string USAUri { get { return myModel.USALayerUri; } set { this.myModel.USALayerUri = value; OnPropertyChanged("USAUri"); } } public string SearchText { get { return myModel.SearchText; } set { this.myModel.SearchText = value; OnPropertyChanged("SearchText"); } } protected void OnPropertyChanged([CallerMemberName] string member = "") { var eventHandler = PropertyChanged; if (eventHandler != null) { PropertyChanged(this, new PropertyChangedEventArgs(member)); } } } }
Our ViewModel
class looks a little more involved. It contains an event (PropertyChangedEventHandler
) and method (OnPropertyChanged
). The OnPropertyChanged
method is really the method that makes binding work in WPF apps. When this method is called, a WFP element updates itself based on firing an event, such as a text change in a textbox. The
CallerMemberName
attribute will pass the property name that executed OnPropertyChanged
. Whenever a property is changed, OnPropertyChanged
is called.
MainWindow.xaml
and add the following line along with the other namespaces:xmlns:local="clr-namespace:Chapter2"
Note that your namespace will be different if you used a different project name.
Window
. This resource informs Window
to set its DataContext
class in the ViewModel
class and use it throughout the entire Window
resource:<Window.Resources> <local:ViewModel x:Key="VM"/> </Window.Resources>
These lines of code instruct the View class to use the ViewModel
class and name it VM
.
DataContext
class to the Grid
tag, which is the root element:<Grid DataContext="{StaticResource VM}">
This line of code is pretty powerful. It's basically telling the View (XAML) class to use this ViewModel
class as the data source. In reality, the ViewModel
class is actually relying on the Model
class to handle the data, but the DataContext
class of View has no idea that this is occurring (remember SoC?).
ViewModel
class. Make the changes as in the following code:<esri:ArcGISTiledMapServiceLayer ID="Basemap" ServiceUri="{Binding Source={StaticResource VM}, Path=BasemapUri}"/> <esri:ArcGISDynamicMapServiceLayer ID="USA" ServiceUri="{Binding Source={StaticResource VM}, Path=USAUri}"/>
<TextBox Name="SearchTextBox" Text="{Binding SearchText}"></TextBox>
These changes bind the elements to the properties of ViewModel
. Also, two ways of binding to the ViewModel
class have been shown. The ServiceUri
properties are referring to the ViewModel
class, while SearchTextBox
is just referring to the property without specifying the ViewModel
class. This is possible because the property is looking up to the root element, which is our ViewModel
class.
The XAML code is shown here in its entirety:
<Window x:Class="Chapter2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013" xmlns:local="clr-namespace:Chapter2" Title="MainWindow" Height="600" Width="800"> <Window.Resources> <local:ViewModel x:Key="VM"/> </Window.Resources> <Grid DataContext="{StaticResource VM}"> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <esri:MapView x:Name="MyMapView" Grid.Row="0" LayerLoaded="MyMapView_LayerLoaded" > <esri:Map> <esri:ArcGISTiledMapServiceLayer ID="Basemap" ServiceUri="{Binding Source={StaticResource VM}, Path=BasemapUri}"/> <esri:ArcGISDynamicMapServiceLayer ID="USA" ServiceUri="{Binding Source={StaticResource VM}, Path=USAUri}"/> </esri:Map> </esri:MapView> <TextBlock Grid.Row="0" Name="Search" Background="#77000000" HorizontalAlignment="Center" VerticalAlignment="Top" Padding="5" Foreground="White" > <Run>Search for </Run> <TextBox Name="SearchTextBox" Text="{Binding SearchText}"> </TextBox> <Run> in the Cities, Counties or States layer. </Run> <Button Content="Find" Width="30" Click="Button_Click"> </Button> </TextBlock> <DataGrid Name="MyDataGrid" Grid.Row="2" Height="200" ></DataGrid> </Grid> </Window>
Chapter2
.So, with a few changes, we satisfied SoC to a certain degree. We now have Model
, View
, and ViewModel
classes. While this may seem like a lot of changes to achieve the same app which we built in the previous chapter, you will find that as your apps become more complex, this pattern will allow multiple people to work on the app. And, as we will see, this pattern allows us to test our app.
While this is a huge improvement, there is actually a lot of boilerplate code. Imagine if your app had 20 Models, 10 ViewModels, and several UIs. We didn't even deal with the button here because it would have resulted in even more boilerplate code. In effect, you'd end up repeating a lot of this code, such as OnPropertyChanged
and PropertyChangedEventHandler
. You could roll a base class to handle this of course, and that would help, but the good news is that this has been dealt with by others. Enter MVVM Light.