CHAPTER 2

image

Data, Binding, and Pages

In this chapter, I show you how to define and use the data that forms the core of a Windows app. To do this, I will be loosely following the view model pattern, which allows me to cleanly separate the data from the parts of the app responsible for displaying that data and handling user interactions.

You may already be familiar with view models from design patterns such as Model-View-Controller (MVC) and Model-View-View Controller (MVVC). I am not going to get into the details of these patterns in this book. There is a lot of good information about MVC and MVVC available, starting with Wikipedia, which has some balanced and insightful descriptions.

I find the benefits of using a view model to be enormous and well worth considering for all but the simplest app projects, and I recommend you seriously consider following the same path. I am not a pattern zealot, and I firmly believe in taking the parts of patterns and techniques that solve real problems and adapting them to work in specific projects. To that end, you will find that I take a liberal view of how a view model should be used.

This chapter focuses on the behind-the-scenes plumbing in an app, creating a foundation that I can build to demonstrate different features. I start slowly, defining a simple view model, and demonstrate different techniques for bringing the data from the view model into the app display using data binding. I then show you how you can break down your app into multiple pages and bring those pages into the main layout, changing which pages are used to reflect the state of your app. Table 2-1 provides the summary for this chapter.

Table 2-1. Chapter Summary

Problem Solution Listing
Create an observable class. Implement the INotifyPropertyChanged interface. 1
Create an observable collection. Use the ObservableCollection class. 2
Change the Page loaded when an app starts. Change the type specified in the OnLaunched method in App.xaml.cs. 3
Set the source for data binding values. Use the DataContext property. 4
Create reusable styles and templates. Create a resource dictionary. 5, 6
Bind UI controls to the view model. Use the Binding keyword. 7
Add another Page to the app layout. Add a Frame to the main layout and use the Navigate method to specify the Page to display. 8–10
Dynamically insert pages into the app layout. Use the Frame.Navigate method, optionally passing a context object to the embedded Page. 11–14

Adding a View Model

At the heart of a good Windows app is a view model, the use of which lets me keep my app data separate from the way it is presented to the user. View models are an essential foundation for creating apps that are easy to enhance and maintain over time. It can be tempting to treat the data contained in a particular UI control as being authoritative, but you will soon reach a point where figuring out where your data is and how it should be updated is unmanageable.

To begin, I have created a new folder in the Visual Studio project called Data and have created a two new class files. The first defines the GroceryItem class, which represents a single item on the grocery list. You can see the contents of GroceryItem.cs in Listing 2-1.

Listing 2-1. TheGroceryItem Class

using System.ComponentModel;
 
namespace GrocerApp.Data {
 
    public class GroceryItem : INotifyPropertyChanged {
        private string name, store;
        private int quantity;
 
        public string Name {
            get { return name; }
            set { name = value; NotifyPropertyChanged("Name"); }
        }
 
        public int Quantity {
            get { return quantity; }
            set { quantity = value; NotifyPropertyChanged("Quantity"); }
        }
 
        public string Store {
            get { return store; }
            set { store = value; NotifyPropertyChanged("Store"); }
        }
 
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propName) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }
}

This class defines properties for the name and quantity of the item to be purchased and which store it should be bought from. The only noteworthy aspect of this class is that it is observable. One of the nice features about the Windows App UI controls is that they support data binding, which means they automatically update when the observable data they are displaying is changed.

You implement the System.ComponentModel.INotifyPropertyChanged interface to make classes observable and trigger the PropertyChangedEventHandler specified by the interface when any of the observable properties is modified.

The other class I added in the Data namespace is ViewModel, which is contained in ViewModel.cs. This class contains the user data and the app state, and you can see the definition of the class in Listing 2-2.

Listing 2-2. The ViewModel Class

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
 
namespace GrocerApp.Data {
 
    public class ViewModel : INotifyPropertyChanged {
        private ObservableCollection <GroceryItem> groceryList;
        private List <string> storeList;
        private int selectedItemIndex;
        private string homeZipCode;
 
        public ViewModel() {
            groceryList = new ObservableCollection <GroceryItem> ();
            storeList = new List <string> ();
            selectedItemIndex = -1;
            homeZipCode = "NY 10118";
        }
 
        public string HomeZipCode {
            get { return homeZipCode; }
            set { homeZipCode = value; NotifyPropertyChanged("HomeZipCode"); }
        }
 
        public int SelectedItemIndex {
            get { return selectedItemIndex; }
            set {
                selectedItemIndex = value;                 NotifyPropertyChanged("SelectedItemIndex");
            }
        }
 
        public ObservableCollection <GroceryItem> GroceryList {
            get {
                return groceryList;
            }
        }
 
        public List <string> StoreList {
            get {
                return storeList;
            }
        }
 
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propName) {
            if (PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }
}

The most important part of this simple view model is the collection of GroceryItem objects that represent the grocery list. I want the list to be observable so that changes to the list are automatically updated in the app UI. To do this, I use an ObservableCollection from the System.Collections.ObjectModel namespace. This class implements the basic features of a collection and emits events when list items are added, removed, or replaced. The ObservableCollection class doesn’t emit an event when the data values of one of the objects it contains are modified, but by creating an observable collection of observable GroceryList objects, I make sure that any change to the grocery list will result in an update in the UI.

The ViewModel class implements the INotifyPropertyChanged as well, because there are two observable properties in the view model. The first, HomeZipCode, is user data, and I’ll use that in Chapter 3 when I demonstrate how to create flyouts. The second observable property, SelectedItemIndex, is part of the app state and keeps track of which item in the grocery list the user has selected, if any.

This is a very simple view model, and as I mentioned, I take a liberal view of how I structure view models in my projects. That said, it contains all of the ingredients I need to demonstrate how to use data binding to keep my app UI controls automatically updated.

Adding the Main Layout

Now that I have defined the view model, I can start to put together the UI. The first step is to add the main page for the app. I understand why Visual Studio generates a page called MainPage, but I want to show you how I tend to structure my projects, so I am going to add a new page to the project.

image Tip   I won’t be using MainPage.xaml again, so you can delete it from your project.

I like to work with a lot of project structure, so I have added a new folder to the project called Pages. I added a new Blank Page called ListPage.xaml by right-clicking the Pages folder, selecting Add image New Item from the pop-up menu, and selecting the Blank Page template. Visual Studio creates the XAML file and the code-behind file, ListPage.xaml.cs.

image Tip   If you are new to building apps using XAML, then it is important to understand that you wouldn’t usually work in the order in which I described the example app. Instead, the XAML approach supports a more iterative style where you declare some controls using XAML, add some code to support them, and perhaps define some styles to reduce duplication in the markup. It is a much more natural process than I have made it appear here, but it is hard to capture the back-and-forth nature of XAML-based development in a book.

I want to make ListPage.xaml the page that is loaded when my app is started, which requires an update to App.xaml.cs, as shown in Listing 2-3.

Listing 2-3. Updating App.xaml.cs to Use the ListPage

using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
 
namespace GrocerApp {
 
    sealed partial class App : Application {
 
        public App() {
            this.InitializeComponent();
            this.Suspendin += OnSuspending;
        }
 
        protected override void OnLaunched(LaunchActivatedEventArgs args) {
            Frame rootFrame = Window.Current.Content as Frame;
 
 
            if (rootFrame == null) {
                rootFrame = new Frame();
 
                if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
                    //TODO: Load state from previously suspended application
                }
 
                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }
 
            if (rootFrame.Content == null) {
 
                if (!rootFrame.Navigate(typeof( Pages.ListPage ), args.Arguments)) {
                    throw new Exception("Failed to create initial page");
                }
            }
            // Ensure the current window is active
            Window.Current.Activate();
        }
 
        private void OnSuspending(object sender, SuspendingEventArgs e) {
            var deferral = e.SuspendingOperation.GetDeferral();
            //TODO: Save application state and stop any background activity
            deferral.Complete();
        }
    }
}
 

Don’t worry about the rest of this class for the moment. I’ll return to it in Chapter 5 when I explain how to respond to the life cycle for Windows apps.

Writing the Code

The easiest way for me to explain how I have created the example app is to present the content in reverse order to the way you would usually create it in a project. To this end, I am going to start with the ListPage.xaml.cs code-behind file. You can see the contents of this file, with my additions to the Visual Studio default content, in Listing 2-4.

Listing 2-4. The ListPage.xaml.cs File

using GrocerApp.Data;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
 
namespace GrocerApp.Pages {
 
    public sealed partial class ListPage : Page {
        ViewModel viewModel;
 
        public ListPage() {
 
            viewModel=new ViewModel();
 
            viewModel.StoreList.Add("Whole Foods");
            viewModel.StoreList.Add("Kroger");
            viewModel.StoreList.Add("Costco");
            viewModel.StoreList.Add("Walmart");
 
            viewModel.GroceryList.Add(new GroceryItem {
                Name="Apples",
                Quantity=4, Store="Whole Foods"
            });
            viewModel.GroceryList.Add(new GroceryItem {
                Name="Hotdogs",
                Quantity=12, Store="Costco"
            });
            viewModel.GroceryList.Add(new GroceryItem {
                Name="Soda",
                Quantity=2, Store="Costco"
            });
            viewModel.GroceryList.Add(new GroceryItem {
                Name="Eggs",
                Quantity=12, Store="Kroger"
            });
 
            this.InitializeComponent();
 
            this.DataContext=viewModel;
        }
 
        protected override void OnNavigatedTo(NavigationEventArgs e) {
        }
 
        private void ListSelectionChanged(object sender,             SelectionChangedEventArgs e) {
            viewModel.SelectedItemIndex=groceryList.SelectedIndex;
        }
    }
}

image Caution   You will get an error if you compile the app at this point because I refer to a groceryList control that I have yet to add. You should wait until the “Running the App” section; that’s when everything will be in place.

The constructor for the ListPage class creates a new ViewModel object and populates it with some sample data. The most interesting statement in this class is this:

this.DataContext = viewModel;

At the heart of the Windows app UI controls is support for data binding through which I can display content from the view model in UI controls. To do this, I have to specify the source of my data. The DataContext property specifies the source for binding data for a UI control and all of its children. I can use the this keyword to set the DataContext for the entire layout because the ListPage class consists of the contents of the code-behind merged with the XAML content, meaning that this refers to the Page object that contains all of the XAML-declared controls.

The final addition I made is to define a method that will handle the SelectionChanged changed event from a ListView control. This is the kind of control that I will used to display the items in the grocery list. When I define the XAML, I will arrange things so that this method is invoked when the user selects one of those items. This method sets the SelectedItemIndex property in the view model based on the SelectedIndex property from the ListView control. Since the SelectedItemIndex property is observable, other parts of my app can be notified when the user makes a selection.

Adding a Resource Dictionary

In Chapter 1, I explained that the StandardStyles.xaml file created by Visual Studio defines some XAML styles and templates that are used in Windows apps. Defining styles like this is a good idea because it means you can apply changes in a single place, rather than having to track down all of the places you applied a color or font setting directly to UI controls. I need a few standard styles and templates for my example project. To this end, I created a new folder called Resources and a new file called GrocerResourceDictionary.xaml using the Resource Dictionary item template. You can see the contents of this file in Listing 2-5.

image Tip   As I explained in Chapter 1, Microsoft prohibits adding styles to StandardStyles.xaml. You must create your own resource dictionary if you want to create custom styles.

Listing 2-5. Defining a Resource Dictionary

<ResourceDictionary
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "using:GrocerApp.Resources">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source = "/Common/StandardStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
 
    <SolidColorBrush x:Key = "AppBackgroundColor" Color = "#3E790A"/>
 
    <Style x:Key = "GroceryListItem" TargetType = "TextBlock"
           BasedOn = "{StaticResource BasicTextStyle}" >
        <Setter Property = "FontSize" Value = "45"/>
        <Setter Property = "FontWeight" Value = "Light"/>
        <Setter Property = "Margin" Value = "10, 0"/>
        <Setter Property = "VerticalAlignment" Value = "Center"/>
    </Style>
 
    <DataTemplate x:Key = "GroceryListItemTemplate">
        <StackPanel Orientation = "Horizontal">
            <TextBlock Text = "{Binding Quantity}"
                       Style = "{StaticResource GroceryListItem}" Width = "50"/>
            <TextBlock Text = "{Binding Name}"
                       Style = "{StaticResource GroceryListItem}"  Width = "200"/>
            <TextBlock Text = "{Binding Store}"
                       Style = "{StaticResource GroceryListItem}"  Width = "300"/>
        </StackPanel>
    </DataTemplate>
</ResourceDictionary>

I don’t get into the detail of styles and templates in this book, but I will explain what I have done in this file since it will provide some context for later listings. The simplest declaration is this one:

...
<SolidColorBrush x:Key = "AppBackgroundColor" Color = "#3E790A"/>
...

The default color scheme for apps is white on black, which I want to change. The first step in changing this is to define a different color, which is what this element does, associating a shade of green with the key AppBackgroundColor. You will see me apply this color using its key when I create the XAML layout in a moment.

The next declaration is for a style, which consists of values for multiple properties:

...
<Style x:Key = "GroceryListItem" TargetType = "TextBlock"
    BasedOn = "{StaticResource BasicTextStyle}" >
    <Setter Property = "FontSize" Value = "45"/>
    <Setter Property = "FontWeight" Value = "Light"/>
    <Setter Property = "Margin" Value = "10, 0"/>
    <Setter Property = "VerticalAlignment" Value = "Center"/>
</Style>
...

This style, which is called GroceryListItem, defines values for several properties: FontSize, FontWeight, and so on. But notice that I have used the BasedOn attribute when declaring the style. This allows me to inherit all of the values defined in another style. In this case, I inherit from the BasicTextStyle style that Microsoft defined in the StandardStyles.xaml file. I must bring other resource dictionary files into scope before I can derive new styles like this, which I do using this declaration:

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source = " /Common/StandardStyles.xaml " />
</ResourceDictionary.MergedDictionaries>

You can import as many files as you like in this manner, but the import must happen before you derive styles from the files’ content.

The final declaration is for a data template, with which I can define a hierarchy of elements that will be used to represent each item in a data source. As you might guess, my source of data will be the collection of grocery items in the view model. Each item in the collection will be represented by a StackPanel that contains three TextBlock controls. Notice the two attributes marked in bold:

...
<DataTemplate x:Key = "GroceryListItemTemplate">
    <StackPanel Orientation = "Horizontal">
        <TextBlockText = "{Binding Quantity}"
            Style = "{StaticResource GroceryListItem}"Width = "50"/>
        <TextBlock Text = "{Binding Name}"
            Style = "{StaticResource GroceryListItem}"  Width = "200"/>
        <TextBlock Text = "{Binding Store}"
            Style = "{StaticResource GroceryListItem}"  Width = "300"/>
    </StackPanel>
</DataTemplate>
...

The value of the Text attribute is important. The Binding keyword tells the runtime that I want the value of the Text attribute to be obtained from the DataContext for the control. In the previous section, I specified the view model as the source for this data, and specifying Quantity tells the runtime I want to use the Quantity property of the object that template is being used to display. By setting the DataContext property in the code-behind file, I specify the big picture (“use my view model of the source of binding data”), and the Binding keyword lets me specify the fine detail (“display the value of this particular property”). When I come to the main XAML file, I’ll be able to connect the two so that the runtime knows which part of the view model should be used to get individual property values.

The other attribute I marked is less interesting but still useful. For the Style attribute, I have specified that I want a StaticResource called the GroceryList item. The StaticResource keyword tells the runtime that the resource I am looking for has already been defined. I have used the GroceryListItem style I specified a moment ago. The benefit here is that I can change the appearance of my three TextBlock controls in one place and that I can easily derive new styles for controls that I want to have a similar appearance. The last step is to add the custom resource dictionary to App.xaml so that it becomes available in the app, which I do in Listing 2-6.

Listing 2-6. Adding the Custom Resource Dictionary to the App.xaml File

<Application
    x:Class = "GrocerApp.App"
    xmlns = " http://schemas.microsoft.com/winfx/2006/xaml/presentation "
    xmlns:x = " http://schemas.microsoft.com/winfx/2006/xaml "
    xmlns:local = "using:GrocerApp">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
               <!--
                    Styles that define common aspects of the platform look and feel
                    Required by Visual Studio project and item templates
                -->
                <ResourceDictionary Source = "Common/StandardStyles.xaml"/>
                <ResourceDictionary Source = "Resources/GrocerResourceDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Writing the XAML

Now that I have the code-behind file and the resource dictionary in place, I can turn to the XAML to declare the controls that will form the app layout. Listing 2-7 shows the content of the ListPage.xaml file.

Listing 2-7. The ListPage.xaml File

<Page
    x:Class = "GrocerApp.Pages.ListPage"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "using:GrocerApp.Pages"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable = "d">
    <Grid Background = "{StaticResourceAppBackgroundColor}">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
 
        <StackPanel Grid.RowSpan = "2">
 
            <TextBlock Style = "{StaticResource HeaderTextStyle}" Margin = "10"
                       Text = "Grocery List"/>
            <ListView x:Name = "groceryList" Grid.RowSpan = "2"
                ItemsSource = "{Binding GroceryList}"
                ItemTemplate = "{StaticResource GroceryListItemTemplate}"
                SelectionChanged = "ListSelectionChanged" />
        </StackPanel>
 
        <StackPanel Orientation = "Vertical" Grid.Column = "1">
            <TextBlock Style = "{StaticResource HeaderTextStyle}" Margin = "10"
                       Text = "Item Detail"/>
        </StackPanel>
 
        <StackPanel Orientation = "Vertical" Grid.Column = "1" Grid.Row = "1">
            <TextBlock Style = "{StaticResource HeaderTextStyle}" Margin = "10"
                       Text = "Store Detail"/>
        </StackPanel>
    </Grid>
</Page>

You can see that I have set the Background attribute for the Grid control to the color I specified in the resource dictionary. In addition, I configured the Grid control that was defined by Visual Studio and divided it into two equal-sized columns, each of which has two equal-sized rows using the Grid.RowDefinitions and Grid.ColumnDefinitions elements.

For the left side of the layout, I have added a StackPanel that I have configured so that it spans two rows and fills the left half of the layout:

...
<StackPanel Grid.RowSpan = "2" >
...

This StackPanel contains a TextBlock, which I use to display a header, and a ListView, which I’ll come back to shortly. For the right side of the screen, I have added a pair of StackPanels, one in each row. I have specified which row and column each belongs in using the Grid.Row and Grid.Column attributes. These attributes use a zero-based index, and controls that don’t have these attributes are put in the first row and column. You can see how the layout appears on the Visual Studio design surface in Figure 2-1.

9781430250340_Fig02-01.jpg

Figure 2-1.   The static parts of the XAML layout displayed on the Visual Studio design surface

The design surface isn’t able to display content that is generated dynamically, which is why you can’t see the view model data in the figure. The dynamic content will be displayed in a ListView control. As its name suggests, ListView displays a set of items in a list. There are three XAML attributes that set out how this will be done:

...
<ListView x:Name = "groceryList" Grid.RowSpan = "2"
    ItemsSource = "{Binding GroceryList}"
    ItemTemplate = "{StaticResource GroceryListItemTemplate}"
    SelectionChanged = "ListSelectionChanged"/>
...

I use these properties to close the gap between the macro-level DataContext property and the micro-level properties in the template. The ItemSource attribute tells the ListView control where it should get the set of items it must display. The Binding keyword with a value of GroceryList tells the ListView that it should display the contents of the GroceryList property of the DataContext object I set in the code-behind file.

The ItemTemplate attribute tells the ListView how each item from the ItemSource should be displayed. The StaticResource keyword and the GroceryListItemTemplate value mean that the data template I specified in the resource dictionary will be used, meaning that a new StackPanel containing three TextBlock elements will be generated for each item in the ViewModel.GroceryList collection.

The final attribute is associated with the event handler method I defined in the code-behind file with the SelectionChanged event emitted by the ListView control.

image Tip   You can get a list of the events that the controls define using the Visual Studio Properties window or by consulting the Microsoft API documentation. The easiest way to create handler methods with the right arguments is to let the XAML editor create them for you as part of the autocomplete process.

Running the App

The Visual Studio design surface can display only the parts of the layout that are static. The only way to see the dynamic content is to run the app. So, to see the way that the XAML and the C# come together, select Start Debugging from the Visual Studio Debug menu. Visual Studio will build the project and push the app to the simulator. You can see the result in Figure 2-2.

9781430250340_Fig02-02.jpg

Figure 2-2.  Running the example app in the simulator

You can see how the data binding adds items from the view model to the ListView control and how the template I defined in the resource dictionary has been used to format them.

There is a lot of built-in behavior with the Windows app UI controls. For example, items are shown in a lighter shade when the mouse moves over them (which you can see in Figure 2-2) and in a different shade when the user clicks to select an item. All of the styles used by a control can be changed, but I am going to stick with the default settings for simplicity.

Inserting Other Pages into the Layout

You don’t have to put all of your controls and code in a single XAML file and its code-behind file. To make a project easier to manage, you can create multiple pages and bring them together in your app. As a simple demonstration, I have created a new Blank Page called NoItemSelected.xaml in my Pages project folder. Listing 2-8 shows the content of this file with my additions to the default content shown in bold.

image Tip   You will have to stop the debugger before Visual Studio will let you add new items to the project.

Listing 2-8. The NoItemSelected.xaml File

<Page
    x:Class = "GrocerApp.Pages.NoItemSelected"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "using:GrocerApp.Pages"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable = "d">
    <Grid Background = "{StaticResourceAppBackgroundColor}">
        <TextBlock Style = "{StaticResource HeaderTextStyle}"
                   FontSize = "30" Text = "No Item Selected"/>
    </Grid>
</Page>

This is a very simple page—so simple that you wouldn’t usually need to create a page like this because it just displays some static text. But it is helpful to demonstrate an important app feature, allowing me to break up my app into manageable pieces. The key to adding pages to my main app layout is the Frame control, which I have added to the ListPage.xaml layout, as shown in Listing 2-9.

Listing 2-9. Adding a Frame Control to the App Layout

<Page
    x:Class = "GrocerApp.Pages.ListPage"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "using:GrocerApp.Pages"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable = "d">
    <Grid Background = "{StaticResource AppBackgroundColor}">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.RowSpan = "2">
            <TextBlock Style = "{StaticResource HeaderTextStyle}" Margin = "10"
                       Text = "Grocery List"/>
            <ListView x:Name = "groceryList" Grid.RowSpan = "2"
                ItemsSource = "{Binding GroceryList}"
                ItemTemplate = "{StaticResource GroceryListItemTemplate}"
                SelectionChanged = "ListSelectionChanged" />
        </StackPanel>
        <StackPanel Orientation = "Vertical" Grid.Column = "1">
            <TextBlock Style = "{StaticResource HeaderTextStyle}" Margin = "10"
                       Text = "Item Detail"/>
            <Frame x:Name = "ItemDetailFrame"/>
        </StackPanel>
        <StackPanel Orientation = "Vertical" Grid.Column = "1" Grid.Row = "1">
            <TextBlock Style = "{StaticResource HeaderTextStyle}" Margin = "10"
                       Text = "Store Detail"/>
        </StackPanel>
    </Grid>
</Page>

A Frame is a placeholder for a Page, and I use the Navigate method in my ListPage.xaml.cs code-behind file to tell the Frame which page I want it to display, as Listing 2-10 shows.

Listing 2-10. Specifying the Page Shown by a Frame in the ListPage.xaml.cs File

using GrocerApp.Data;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace GrocerApp.Pages {
    public sealed partial class ListPage : Page {
        ViewModel viewModel;
        public ListPage() {
            viewModel = new ViewModel();
            // ... test data removed for brevity
            this.InitializeComponent();
            this.DataContext = viewModel;
            ItemDetailFrame.Navigate(typeof(NoItemSelected));
        }
        protected override void OnNavigatedTo(NavigationEventArgs e) {
        }
        private void ListSelectionChanged(object sender,             SelectionChangedEventArgs e) {
            viewModel.SelectedItemIndex = groceryList.SelectedIndex;
        }
    }
}

The argument to the Navigate method is a System.Type representing the Page class you want to load, and the easiest way to get a System.Type is with the typeof keyword. The Navigate method instantiates the Page object (remember that XAML and the code-behind file are combined to create a single subclass of Page), and the result is inserted into the layout, as shown in Figure 2-3.

9781430250340_Fig02-03.jpg

Figure 2-3.  Inserting another page into the layout

Dynamically Inserting Pages into the Layout

You can also use a Frame to dynamically insert different pages into your layout based on the state of your app. To demonstrate this, I have created a new Blank Page in the Pages folder called ItemDetail.xaml, the contents of which are shown in Listing 2-11. (This file relies on styles from my custom dictionary; you can see how I defined them in the source code download for this chapter.)

Listing 2-11. The ItemDetail.xaml Page

<Page
    x:Class = "GrocerApp.Pages.ItemDetail"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "using:GrocerApp.Pages"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable = "d">
    <Grid Background = "{StaticResourceAppBackgroundColor}">
        <Grid.RowDefinitions>
            <RowDefinition Height = "Auto" />
            <RowDefinition Height = "Auto"/>
            <RowDefinition Height = "Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width = "Auto"/>
            <ColumnDefinition Width = "*"/>
        </Grid.ColumnDefinitions>
 
        <TextBlock Text = "Name:" Style = "{StaticResource ItemDetailText}" />
        <TextBlock Text = "Quantity:" Style = "{StaticResource ItemDetailText}"
                   Grid.Row = "1"/>
        <TextBlock Text = "Store:" Style = "{StaticResource ItemDetailText}"
                   Grid.Row = "2"/>
 
        <TextBox x:Name = "ItemDetailName"
                 Style = "{StaticResource ItemDetailTextBox}"
                 TextChanged = "HandleItemChange"
                 Grid.Column = "1"/>
 
        <TextBox x:Name = "ItemDetailQuantity"
                 Style = "{StaticResource ItemDetailTextBox}"
                 TextChanged = "HandleItemChange"
                 Grid.Row = "1" Grid.Column = "1"/>
 
        <ComboBox x:Name = "ItemDetailStore"
                  Style = "{StaticResource ItemDetailStore}"
                  Grid.Column = "1" Grid.Row = "2"
                  ItemsSource = "{Binding StoreList}"
                  SelectionChanged = "HandleItemChange"
                  DisplayMemberPath = "" />
    </Grid>
</Page>

The layout for this Page is based around a grid, with text labels and input controls to allow the user to edit the details of an item on the grocery list, the details of which I’ll set in the code-behind file shortly.

image Tip   I use a ComboBox control to present the list of stores to the user, which I populate directly from the view model. I have set the ItemSource attribute so that the control binds to the StoreList property in the view model, but the ComboBox control expects to be told which property should be displayed from the collection of objects it is working with. Since my list of stores is just an array of strings, I have to work around this by setting the DisplayMemberPath attribute to the empty string.

I need to define some new styles in my Resources/GrocerResourceDictionary.xaml file, which you can see in Listing 2-12.

Listing 2-12. Adding Styles to the Resource Dictionary

<ResourceDictionary
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local = "using:GrocerApp.Resources">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source = "/Common/StandardStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <!-- ...previous styles ommitted for brevity...-->
    <Style x:Key = "ItemDetailText" TargetType = "TextBlock"
           BasedOn = "{StaticResource GroceryListItem}" >
        <Setter Property = "FontSize" Value = "35"/>
        <Setter Property = "HorizontalAlignment" Value = "Right"/>
    </Style>
 
    <Style x:Key = "ItemDetailTextBox" TargetType = "TextBox" >
        <Setter Property = "FontSize" Value = "30"/>
        <Setter Property = "Margin" Value = "10"/>
    </Style>
 
    <Style x:Key = "ItemDetailStore" TargetType = "ComboBox">
        <Setter Property = "Foreground" Value = "Black"/>
        <Setter Property = "Height" Value = "55"/>
        <Setter Property = "FontSize" Value = "30"/>
        <Setter Property = "VerticalAlignment" Value = "Top"/>
        <Setter Property = "Margin" Value = "10"/>
    </Style>
</ResourceDictionary>

Switching Between Pages

Now that I have two pages, I can switch between them as the state of my app changes. I am going to display the NoItemSelected page when the user hasn’t selected an item from the ListView and the ItemDetail page when the user has made a selection. Listing 2-13 shows the additions to the ListPage.xaml.cs file that make this happen.

Listing 2-13. Showing Pages Based on App State

...
public ListPage() {
    viewModel = new ViewModel();
    // ...test data removed for brevity
    this.InitializeComponent();
    this.DataContext = viewModel;
    ItemDetailFrame.Navigate(typeof(NoItemSelected));
 
    viewModel.PropertyChanged + = (sender, args) = >{
        if (args.PropertyName == "SelectedItemIndex") {
            if (viewModel.SelectedItemIndex == -1) {
                ItemDetailFrame.Navigate(typeof(NoItemSelected));
            } else {
                ItemDetailFrame.Navigate(typeof(ItemDetail), viewModel);
            }
        }
    };
}
...

I still display the NoItemSelected page by default, because that reflects the initial state of my app. The user won’t have made a selection when the app first starts.

The additional code adds a handler for the PropertyChanged event defined by the view model. This is the same event that is used by the XAML controls data binding feature, and by registering a handler directly, I am able to respond to property changes in the view model in my code-behind file. The event arguments I receive when I handle this event tell me which property has changed through the PropertyName property. If the SelectedItemIndex property has changed, then I use the Frame.Navigate method to show either the NoItemSelected or ItemDetail page.

Notice that I pass the view model object as an argument to the Navigate method when I display the ItemDetail page:

ItemDetailFrame.Navigate(typeof(ItemDetail), viewModel);

This argument is the means by which you can pass context to the page that is being displayed. I’ll show you how to use this object in the next section.

Implementing the Embedded Page

Of course, it isn’t enough just to define the XAML for my ItemDetail page. I also have to write the code that will display the details of the selected item and allow the user to make changes. Listing 2-14 shows the ItemDetail.xaml.cs code-behind file, which does just this.

Listing 2-14. The ItemDetail.xaml.cs Code-Behind File

using GrocerApp.Data;
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace GrocerApp.Pages {
    public sealed partial class ItemDetail : Page {
        private ViewModel viewModel;
        public ItemDetail() {
            this.InitializeComponent();
        }
        protected override void OnNavigatedTo(NavigationEventArgs e) {
 
            viewModel=e.Parameter as ViewModel;
            this.DataContext=viewModel;
 
            viewModel.PropertyChanged + = (sender, eventArgs) = >{
                if (eventArgs.PropertyName == "SelectedItemIndex") {
                    if (viewModel.SelectedItemIndex == -1) {
                        SetItemDetail(null);
                    } else {
                        SetItemDetail(viewModel.GroceryList
                            [viewModel.SelectedItemIndex]);
                    }
                }
            };
            SetItemDetail(viewModel.GroceryList[viewModel.SelectedItemIndex]);
        }
        private void SetItemDetail(GroceryItem item) {
            ItemDetailName.Text = (item == null) ? "" : item.Name;
            ItemDetailQuantity.Text = (item == null) ? ""
                : item.Quantity.ToString();
            if (item != null) {
                ItemDetailStore.SelectedItem = item.Store;
            } else {
                ItemDetailStore.SelectedIndex = -1;
            }
        }
        private void HandleItemChange(object sender, RoutedEventArgs e) {
            if (viewModel.SelectedItemIndex > -1) {
                GroceryItem selectedItem = viewModel.GroceryList
                    [viewModel.SelectedItemIndex];
                if (sender == ItemDetailName) {
                    selectedItem.Name = ItemDetailName.Text;
                } else if (sender == ItemDetailQuantity) {
                    int intVal;
                    bool parsed = Int32.TryParse(ItemDetailQuantity.Text,
                        out intVal);
                    if (parsed) {
                        selectedItem.Quantity = intVal;
                    }
                } else if (sender == ItemDetailStore) {
                    string store = (String)((ComboBox)sender).SelectedItem;
                    if (store != null) {
                        viewModel.GroceryList
                            [viewModel.SelectedItemIndex].Store = store;
                    }
                }
            }
        }
    }
}

The SetItemDetail method sets the content of the UI controls to display the details of the selected grocery list item, and the HandleItemChange method updates that item when the user changes the contents of one of the controls.

The interesting part of this listing is the OnNavigatedTo method, which I have marked in bold in the listing. This method is called when the ItemDetail page is displayed, and the object that I passed to the Frame.Navigate model is available through the Parameter property of the event arguments. You can see the ItemDetail page in the layout in Figure 2-4.

9781430250340_Fig02-04.jpg

Figure 2-4.  Displaying the ItemDetail page

By passing the view model object around in this way, I am able to ensure that all of my pages are working with the same data, while being able to break down the app into manageable portions.

Summary

In this chapter, I showed you how to create a view model in a Windows app and make it observable so that you can use data binding to keep the layout of your app synchronized with the view model data. This is an essential technique for creating robust and easily maintained apps. I also showed you how you can break down your app into manageable chunks by creating pages and how you can use a Frame to display those pages as part of your main layout. In the next chapter, I’ll show you how to create some of the most important controls for a Windows app: the AppBar, the NavBar, and flyouts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.144.170