UWP Basics
- 10/11/2018
Implementing Data Binding
Let’s see how MVVM works in practice. To begin, create a blank UWP app, UWP.Basics, that references the MixedReality.Common library (as explained in Chapter 3). Then declare the main view of the app as shown in Figure 4-3. (You can find the complete XAML declaration in the companion code for this book in this folder: Chapter_04/UWP.Basics/MainPage.xaml.)
FIGURE 4-3 UI of the UWP.Basics app.
To declare the view, you proceed as you did in Chapter 3. As shown in the right pane in Figure 4-4, you use the MainPage.xaml file to declare all your controls. (The full code listing is omitted here, but you can obtain it from companion code or in Listing 4-6.)
FIGURE 4-4 Visual Studio showing the view and corresponding XAML declarations of the UWP.Basics app developed in this chapter.
Notice that the view is composed of three labels, three text boxes, and two buttons. The user employs the text boxes to enter personal data (first and last name and age) and clicks the Store button to persist these values into the app settings. When this data is stored in the settings, it will be restored whenever the app is run. (The other button, Change, will be used later.) So, in this example, you can treat the app settings as the model. This model is updated through the ViewModel associated with the view whenever the user types data in the text boxes and confirms his or her changes by clicking the Store button.
Conforming to the MVVM pattern, values from text boxes are bound to properties of the ViewModel. In the UWP.Basics app, this is implemented through the PersonViewModel class. (See the companion code in Chapter_04/UWP.Basics/PersonViewModel.cs.) As Listing 4-5 shows, this class has three public auto-implemented properties: FirstName, LastName, and Age.
LISTING 4-5 A fragment of the PersonViewModel definition
public class PersonViewModel { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } // The rest of the class definition }
The properties of the ViewModel are two-way bound with the text boxes of the UI. As Listing 4-6 shows, to define the binding, you use an {x:Bind} markup extension. (See http://bit.ly/x-bind for more information.) This requires you to provide the path to the source property. Here, this path points to the corresponding property of the ViewModel. You can also specify the binding mode with a Mode attribute. There are three available binding modes, each represented by the appropriate value declared in the Windows.UI.Xaml.Data.BindingMode enumeration:
OneTime This indicates that the target property is updated when the binding is created. All subsequent updates of the source property will not modify the target property.
OneWay This specifies that the target property is updated whenever the value of the source property changes.
TwoWay This indicates that the binding is bidirectional, which means that the source property is modified by updating the target property.
LISTING 4-6 Binding control properties with the ViewModel using the {x:Bind} markup extension
<!--First name--> <TextBlock Text="First name:" /> <TextBox Text="{x:Bind ViewModel.FirstName, Mode=TwoWay}" Grid.Column="1"/> <!--Last name--> <TextBlock Text="Last name:" Grid.Row="1" /> <TextBox Text="{x:Bind ViewModel.LastName, Mode=TwoWay}" Grid.Row="1" Grid.Column="1" /> <!--Age--> <TextBlock Text="Age:" Grid.Row="2" /> <TextBox Text="{x:Bind ViewModel.Age, Mode=TwoWay}" Grid.Row="2" Grid.Column="1" />
Listing 4-6 uses two-way binding so that any text the user types in the text boxes (the target property) updates the source property of the ViewModel. After such a binding is established, you do not need to manually rewrite properties. Clearly, without such a binding, you would need to implement a handler for the TextBox.TextChanged event as follows:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { if (e.OriginalSource is TextBox textBox) { ViewModel.FirstName = textBox.Text; } }
Importantly, using an {x:Bind} markup extension ensures that these operations are performed automatically whenever the binding is defined. Moreover, you can replace the source properties with methods invoked after a particular user action is performed. For instance, to handle a Click event of the Store button, you can use the StoreInSettings method:
<Button Content="Store" Grid.Row="3" Click="{x:Bind ViewModel.StoreInSettings}" />
A definition of the StoreInSettings method appears in Listing 4-7. To access the app settings, the ApplicationDataContainer class is used. To get its instance, the LocalSettings property of ApplicationData.Current is read. Once the instance of the ApplicationDataContainer class is achieved, you can write values to the app settings by updating the Values property, which implements a key-value pair container. Listing 4-7 declares three keys, which are stored in corresponding private fields: firstNameKey, lastNameKey, and ageKey. These keys are then used to store values from the FirstName, LastName, and Age members of PersonViewModel.
LISTING 4-7 Storing data in app settings
private ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings; private string firstNameKey = nameof(FirstName); private string lastNameKey = nameof(LastName); private string ageKey = nameof(Age); public void StoreInSettings() { localSettings.Values[firstNameKey] = FirstName; localSettings.Values[lastNameKey] = LastName; localSettings.Values[ageKey] = Age; }
To retrieve data from settings during runtime, you proceed similarly, but instead of writing to the Values collection, you read it. Typically, before reading a value, you first check if that collection contains a specified key:
if(localSettings.Values.ContainsKey(firstNameKey)) { FirstName = localSettings.Values[firstNameKey].ToString(); }
I used such an approach to retrieve personal data from the app settings in the PersonViewModel class constructor:
public PersonViewModel() { if (IsPersonDataStoredInSettings()) { FirstName = localSettings.Values[firstNameKey].ToString(); LastName = localSettings.Values[lastNameKey].ToString(); Age = (int)localSettings.Values[ageKey]; } }
In this code block, I use the IsPersonDataStoredInSettings helper method. (See Listing 4-8.) This checks whether the local application settings contain all three keys.
LISTING 4-8 Checking whether personal data is stored in local settings
private bool IsPersonDataStoredInSettings() { string[] keys = { firstNameKey, lastNameKey, ageKey }; var isPersonDataStoredInSettings = true; foreach(var key in keys) { if(!localSettings.Values.ContainsKey(key)) { isPersonDataStoredInSettings = false; break; } } return isPersonDataStoredInSettings; }
Run the app, type any values in the text boxes, and click the Store button. Your values will be persisted in the app settings. Afterward, try closing the app and running it again. You will see that the app restores previously typed values.
This example illustrates how to start using the MVVM pattern in your UWP apps. However, there are several other aspects of data binding you need to know. I cover them next.