Partager via


Implementing the Model-View-ViewModel pattern for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

 

In this walkthrough, you will build a simple app that implements the Model-View-ViewModel (MVVM) design pattern. MVVM is a way to separate your data from your user interface. MVVM allows developers to code data models, and designers to create user interfaces. In your app, the Model (data) will be a C# class, and the View (user interface) will be a XAML user control. The ViewModel, which is the link between the Model and the View, will also be a C# class. For more information about the Model-View-ViewModel design pattern, see the links at the end of this topic.

The app that you create in this walkthrough is a “game tracker” in which the user can keep track of their accomplishments in a video game. The user can store the number of items that they have collected and the levels that they have completed. To download a completed version of this app, see Model-View-ViewModel Sample.

In this walkthrough, you will perform the following tasks:

  • Create a Model, a ViewModel, and two Views.

  • Use XAML binding to connect the Views to the data.

  • Create a custom data converter.

  • Maintain page state when navigating to and from your app.

  • Save app data to isolated storage.

  • Use an app bar to expose the save functionality.

The completed solution contains the following components.

The completed app looks like the following.

Prerequisites

To complete this walkthrough, you must have Windows Phone SDK installed.

Creating the app project

First, you create a new Windows Phone app project named MVVMTestApp. In later steps, you will add code to your app that assumes the app name is MVVMTestApp. If you choose a different name for your app, you must change the namespace references in the code.

To create the app project

  1. In Visual Studio, on the File menu, point to New and then click Project.

    The New Project dialog appears.

  2. In the left pane, click Installed Templates, expand Visual C# or Visual Basic, and then click Windows Phone.

  3. In the list of project types, click **Windows Phone App **.

  4. In the Name box, type MVVMTestApp.

  5. Click OK.

    The new app project is created and opens in Visual Studio.

  6. On the File menu, click Save All. (Ctrl+Shift+S)

  7. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Adding the Model-View-ViewModel folders

For this implementation of the Model-View-ViewModel pattern, you organize the project files into folders. In this procedure, you create three new folders named Model, View, and ViewModel.

To organize the Model-View-ViewModel files

  1. In Solution Explorer, right-click the project MVVMTestApp, point to Add and then click New Folder. A new folder is added to the project with the name in edit mode. Type Model as the name of the folder and then press the Enter key.

  2. In Solution Explorer, right-click the project MVVMTestApp, point to Add and then click New Folder. A new folder is added to the project with the name in edit mode. Type View as the name of the folder and then press the Enter key.

  3. In Solution Explorer, right-click the project MVVMTestApp, point to Add and then click New Folder. A new folder is added to the project with the name in edit mode. Type ViewModel as the name of the folder and then press the Enter key.

Creating the data model

In this procedure, you create the data model. The model is a single class named Accomplishment. The Accomplishment class implements the INotifyPropertyChanged interface and the PropertyChanged event. This allows the class to notify its views when a property value changes, and then the views can update the user interface based on that change. In this app, the user can only change the Count and Completed properties, so you only need to raise the PropertyChanged event in those properties. You will test the data binding in the final procedure.

The Accomplishment class contains the following properties.

Property

Description

Name

The name of the accomplishment.

Type

The type of the accomplishment: “Item” or “Level”.

Count

For Items: How many of the item you have collected.

Completed

For Levels: Whether you have completed the level.

To create the data model

  1. In Solution Explorer, right-click the folder Model, point to Add and then click Class.

    The Add New Item dialog appears, with the class template selected.

Visual Basic Note:

For Visual Basic, you must select the Code File template manually.

  1. In the Name box, type Accomplishment.cs or Accomplishment.vb, and then click Add.

    A new class is added to the project in the Model folder, and opens in the code editor.

  2. Replace the code with the following code:

    using System;
    using System.ComponentModel;
    
    
    namespace MVVMTestApp.Model
    {
        public class Accomplishment : INotifyPropertyChanged
        {
            // The name of the accomplishment.
            public string Name { get; set; }
    
            // The type of the accomplishment, Item or Level.
            public string Type { get; set; }
    
            // The number of each item that has been collected.
            private int _count;
            public int Count
            {
                get
                {
                    return _count;
                }
                set
                {
                    _count = value;
                    RaisePropertyChanged("Count");
                }
            }
    
            // Whether a level has been completed or not
            private bool _completed;
            public bool Completed
            {
                get
                {
                    return _completed;
                }
                set
                {
                    _completed = value;
                    RaisePropertyChanged("Completed");
                }
            }
    
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void RaisePropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
    
            // Create a copy of an accomplishment to save.
            // If your object is databound, this copy is not databound.
            public Accomplishment GetCopy()
            {
                Accomplishment copy = (Accomplishment)this.MemberwiseClone();
                return copy;
            }
        }
    }
    
    Imports System.ComponentModel
    
    Public Class Accomplishment
        Implements INotifyPropertyChanged
    
        ' The name of the accomplishment.
        Public Property Name As String
    
        ' The type of the accomplishment, Item, or Level.
        Public Property Type As String
    
        ' The number of each item that has been collected.
        Private _count As Integer
    
        Public Property Count As Integer
            Get
                Return _count
            End Get
            Set(ByVal value As Integer)
                _count = value
                RaisePropertyChanged("Count")
            End Set
        End Property
    
    
        ' Whether a level has been completed or not.
        Private _completed As Boolean
    
        Public Property Completed As Boolean
            Get
                Return _completed
            End Get
            Set(ByVal value As Boolean)
                _completed = value
                RaisePropertyChanged("Completed")
            End Set
        End Property
    
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Private Sub RaisePropertyChanged(ByVal propertyName As String)
    
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    
    
        ' Create a copy of an accomplishment to save.
        ' If your object is databound, this copy is not databound.
        Public Function GetCopy() As Accomplishment
    
            Dim copy = CType(Me.MemberwiseClone(), Accomplishment)
            Return copy
        End Function
    End Class
    
  3. On the File menu, click Save All. (Ctrl+Shift+S)

  4. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Creating the ViewModel

In this procedure, you create the ViewModel, which is the link between the Model and the View. The ViewModel is a collection that contains objects from the Model, in this case Accomplishment objects. The ViewModel uses an ObservableCollection<(Of <(T>)>). This allows it to notify its views when items in the collection change and the views can update the user interface based on that change.

The ViewModel contains the code to get the collection of accomplishments. First, you check the isolated storage to see if the accomplishments are already saved. If yes, you use isolated storage to populate the collection. If not, you populate the collection with default accomplishments. In a later procedure, you will add the code to save the accomplishments to isolated storage.

To create the ViewModel

  1. In Solution Explorer, right-click the folder ViewModel, point to Add and then click Class.

    The Add New Item dialog appears, with the class template selected.

Visual Basic Note:

For Visual Basic, you must select the Code File template manually.

  1. In the Name box, type ViewModel.cs or ViewModel.vb, and then click Add.

    A new class is added to the project in the ViewModel folder, and opens in the code editor.

  2. Replace the code with the following code:

    using System;
    using System.Windows;
    using System.Collections.ObjectModel;
    using System.IO.IsolatedStorage;
    using MVVMTestApp.Model;
    
    namespace MVVMTestApp.ViewModelNamespace
    {
        public class ViewModel
        {
            public ObservableCollection<Accomplishment> Accomplishments { get; set; }
    
    
            public void GetAccomplishments()
            {
                if (IsolatedStorageSettings.ApplicationSettings.Count > 0)
                {
                    GetSavedAccomplishments();
                }
                else
                {
                    GetDefaultAccomplishments();
                }
            }
    
    
            public void GetDefaultAccomplishments()
            {
                ObservableCollection<Accomplishment> a = new ObservableCollection<Accomplishment>();
    
                // Items to collect
                a.Add(new Accomplishment() { Name = "Potions", Type = "Item" });
                a.Add(new Accomplishment() { Name = "Coins", Type = "Item" });
                a.Add(new Accomplishment() { Name = "Hearts", Type = "Item" });
                a.Add(new Accomplishment() { Name = "Swords", Type = "Item" });
                a.Add(new Accomplishment() { Name = "Shields", Type = "Item" });
    
                // Levels to complete
                a.Add(new Accomplishment() { Name = "Level 1", Type = "Level" });
                a.Add(new Accomplishment() { Name = "Level 2", Type = "Level" });
                a.Add(new Accomplishment() { Name = "Level 3", Type = "Level" });
    
                Accomplishments = a;
                //MessageBox.Show("Got accomplishments from default");
            }
    
    
            public void GetSavedAccomplishments()
            {
                ObservableCollection<Accomplishment> a = new ObservableCollection<Accomplishment>();
    
                foreach (Object o in IsolatedStorageSettings.ApplicationSettings.Values)
                {
                    a.Add((Accomplishment)o);
                }
    
                Accomplishments = a;
                //MessageBox.Show("Got accomplishments from storage");
            }
        }
    }
    
    Imports System.Collections.ObjectModel
    Imports System.IO.IsolatedStorage
    
    Public Class ViewModel
    
        Public Property Accomplishments As ObservableCollection(Of Accomplishment)
    
        Public Sub GetAccomplishments()
    
            If IsolatedStorageSettings.ApplicationSettings.Count > 0 Then
    
                GetSavedAccomplishments()
            Else
                GetDefaultAccomplishments()
            End If
        End Sub
    
    
        Public Sub GetDefaultAccomplishments()
    
            Dim a As New ObservableCollection(Of Accomplishment)
    
            ' Items to collect
            a.Add(New Accomplishment With {.Name = "Potions", .Type = "Item"})
            a.Add(New Accomplishment With {.Name = "Coins", .Type = "Item"})
            a.Add(New Accomplishment With {.Name = "Hearts", .Type = "Item"})
            a.Add(New Accomplishment With {.Name = "Swords", .Type = "Item"})
            a.Add(New Accomplishment With {.Name = "Shields", .Type = "Item"})
    
            ' Levels to complete
            a.Add(New Accomplishment With {.Name = "Level 1", .Type = "Level"})
            a.Add(New Accomplishment With {.Name = "Level 2", .Type = "Level"})
            a.Add(New Accomplishment With {.Name = "Level 3", .Type = "Level"})
    
            Accomplishments = a
            'MessageBox.Show("Got accomplishments from default")
        End Sub
    
    
        Public Sub GetSavedAccomplishments()
    
            Dim a As New ObservableCollection(Of Accomplishment)
    
            For Each o In IsolatedStorageSettings.ApplicationSettings.Values
    
                a.Add(CType(o, Accomplishment))
            Next o
    
            Accomplishments = a
            'MessageBox.Show("Got accomplishments from storage")
        End Sub
    End Class
    
  3. On the File menu, click Save All. (Ctrl+Shift+S)

  4. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Creating the first view

In this procedure, you create a view to display the item accomplishments. In a later procedure, you will display the view on a page and connect it to the ViewModel by setting its data context. Note that there is no code behind this view.

The view is a user control that contains a ListBox bound to the data. Each item in the ListBox has the following three columns.

Name

Binding Mode

Description

Name

One Way

The name of the item.

Count

Two Way

(Text box) How many of the item you have collected. The user enters this, and the model is updated through the data binding.

Check

One Way

This is the check to see that the data is updating correctly. When the user updates the Count in the previous column, the result appears in this column immediately.

To create the first view

  1. In Solution Explorer, right-click the folder View, point to Add and then click New Item.

    The Add New Item dialog appears.

  2. In the list of file types, click Windows Phone User Control.

  3. In the Name box, type ItemView.xaml, and then click Add.

    A new XAML file is added to the project in the View folder, and opens in the designer. By default, there is an empty GRID element in the XAML file.

  4. In the GRID element, add the following code:

    <ListBox ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="80"/>
                        <ColumnDefinition Width="100"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock x:Name="Item" Text="{Binding Path=Name, Mode=OneWay}" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
                    <TextBox x:Name="Count" Text="{Binding Path=Count, Mode=TwoWay}" Grid.Column="1" TextAlignment="Center" InputScope="Number"/>
                    <TextBlock x:Name="Check" Text="{Binding Path=Count, Mode=OneWay}" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    
  5. On the File menu, click Save All. (Ctrl+Shift+S)

  6. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Creating the second view

In this procedure, you create a view to display the level accomplishments. In a later procedure, you will display the view on a page and connect it to the ViewModel by setting its data context.

The view is a user control that contains a ListBox bound to the data. Each item in the ListBox has the following three columns.

Name

Binding Mode

Description

Level

One Way

The name of the level.

Completed

Two Way

(Check box) Whether you have completed the level or not. The user checks this, and the model is updated through the data binding.

Check

One Way

This is the check to see that the data is updating correctly. When the user updates the Completed data in the previous column, the result appears in this column immediately.

In this View, you will do one additional thing. If a user has not completed a level yet, you want the check box to be enabled. When the user selects the check box to indicate that they have completed a level, you want the check box to become disabled. In other words, the enabled state of the check box is the opposite of the value of the check box. You can set the enabled state of the check box automatically through the data binding, but first you must create a custom data converter that returns the opposite of a Boolean value. A custom data converter is a class that implements the IValueConverter interface, and implements the Convert and ConvertBack methods. You will test the data converter in the final procedure.

To create the second view

  1. In Solution Explorer, right-click the folder View, point to Add and then click New Item.

    The Add New Item dialog appears.

  2. In the list of file types, click Windows Phone User Control.

  3. In the Name box, type LevelView.xaml, and then click Add.

    A new XAML file is added to the project in the View folder, and opens in the designer.

To create the custom data binding converter

  1. In Solution Explorer, right-click LevelView.xaml and then click View Code.

    The code-behind file opens in the code editor.

  2. Replace the code with the following code:

    using System;
    using System.Windows.Controls;
    using System.Globalization;
    
    namespace MVVMTestApp.View
    {
        public partial class LevelView : UserControl
        {
            public LevelView()
            {
                InitializeComponent();
            }
        }
    
    
        public class BoolOpposite : System.Windows.Data.IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                bool b = (bool)value;
                return !b;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                string s = value as string;
                bool b;
    
                if (bool.TryParse(s, out b))
                {
                    return !b;
                }
                return false;
            }
        }
    }
    
    Imports System.Globalization
    
    Partial Public Class LevelView
        Inherits UserControl
    
        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
    
    
    Public Class BoolOpposite
        Implements System.Windows.Data.IValueConverter
    
        Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object _
            Implements System.Windows.Data.IValueConverter.Convert
    
            Dim b = CBool(value)
            Return Not b
        End Function
    
    
        Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object _
            Implements System.Windows.Data.IValueConverter.ConvertBack
    
            Dim s = TryCast(value, String)
            Dim b As Boolean
    
            If Boolean.TryParse(s, b) Then
    
                Return Not b
            End If
    
            Return False
        End Function
    End Class
    
  3. On the File menu, click Save All. (Ctrl+Shift+S)

  4. On the Build menu, click Build Solution. (Ctrl+Shift+B)

To create the user interface for the second view

  1. In Solution Explorer, double-click LevelView.xaml to open it in the designer.

  2. In the XAML editor, in the <UserControl> tag, with the other namespace declarations, add the following code:

    C# version:

    xmlns:src="clr-namespace:MVVMTestApp.View"
    

    Visual Basic version:

    xmlns:src="clr-namespace:MVVMTestApp"
    
  3. After the USERCONTROL element and before the GRID element, add the following code:

    <UserControl.Resources>
        <src:BoolOpposite x:Key="BoolOpposite"/>
    </UserControl.Resources>
    
  4. In the GRID element, add the following code:

    <ListBox ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="80"/>
                        <ColumnDefinition Width="100"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock x:Name="Level" Text="{Binding Path=Name, Mode=OneWay}" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
                    <CheckBox x:Name="Completed" IsChecked="{Binding Path=Completed, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center" IsEnabled="{Binding Path=Completed, Converter={StaticResource BoolOpposite}}"/>
                    <TextBlock x:Name="Check" Text="{Binding Path=Completed, Mode=OneWay}" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    
  5. On the File menu, click Save All. (Ctrl+Shift+S)

  6. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Creating the main app page

In this procedure, you create the main app page. The main page contains the two views that you created previously. The first view contains the items and the second view contains the levels. In the code, first you create a new instance of the ViewModel and use it to get the data. Then you connect each View to the ViewModel, choosing the data from the ViewModel that you want to see in the View. In a later procedure, you will add the code to maintain the page state when the user navigates away from your app and back.

To create the user interface for the main app page

  1. In Solution Explorer, double-click MainPage.xaml to open it in the designer.

  2. In the design view, click the text block MY APPLICATION.

  3. In the Properties window change the Text property to MVVM Test App.

  4. In the design view, click the text block page name.

  5. In the Properties window change the Text property to game tracker.

  6. In the XAML editor, in the <phone> tag, with the other namespace declarations, add the following code:

    C# version:

    xmlns:views="clr-namespace:MVVMTestApp.View"
    

    Visual Basic version:

    xmlns:src="clr-namespace:MVVMTestApp"
    
  7. Locate the ContentPanelGRID element, which is added to your project by default. It looks like the following:

    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>
    
  8. Replace the default ContentPanelGRID element with the following code:

Warning

Be careful not to delete the end tag of the LayoutRootGRID element that immediately follows the ContentPanelGRID element.

    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel>
            <TextBlock Text="Items Collected" Foreground="{StaticResource PhoneAccentBrush}" Style="{StaticResource PhoneTextLargeStyle}" />
            <views:ItemView x:Name="ItemViewOnPage" Height="200"/>
    
            <TextBlock Text="Levels Completed" Foreground="{StaticResource PhoneAccentBrush}" Style="{StaticResource PhoneTextLargeStyle}" />
            <views:LevelView x:Name="LevelViewOnPage" Height="200"/>
        </StackPanel>
    </Grid>
  1. On the File menu, click Save All. (Ctrl+Shift+S)

  2. On the Build menu, click Build Solution. (Ctrl+Shift+B)

To add the code for the main app page

  1. In Solution Explorer, right-click MainPage.xaml and then click View Code.

    The code-behind file opens in the code editor.

  2. Replace the code with the following code:

    using System;
    using System.Linq;
    using System.Windows;
    using Microsoft.Phone.Controls;
    using MVVMTestApp.ViewModelNamespace;
    
    namespace MVVMTestApp
    {
        public partial class MainPage : PhoneApplicationPage
        {
            private ViewModel vm;
    
            // Constructor
            public MainPage()
            {
                InitializeComponent();
                vm = new ViewModel();
            }
    
    
            protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                // Later, you will replace this next line with something better.
                vm.GetAccomplishments();
    
    
                // There are two different views, but only one view model.
                // So, use LINQ queries to populate the views.
    
                // Set the data context for the Item view.
                ItemViewOnPage.DataContext = from Accomplishment in vm.Accomplishments where Accomplishment.Type == "Item" select Accomplishment;
    
                // Set the data context for the Level view.
                LevelViewOnPage.DataContext = from Accomplishment in vm.Accomplishments where Accomplishment.Type == "Level" select Accomplishment;
    
                // If there is only one view, you could use the following code
                // to populate the view.
                //AccomplishmentViewOnPage.DataContext = vm.Accomplishments;
            }
        }
    }
    
    Partial Public Class MainPage
        Inherits PhoneApplicationPage
    
        Private vm As ViewModel
    
        ' Constructor
        Public Sub New()
    
            InitializeComponent()
            vm = New ViewModel
        End Sub
    
    
        Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
    
            MyBase.OnNavigatedTo(e)
    
            ' Later, you will replace this next line with something better.
            vm.GetAccomplishments()
    
            ' There are two different views, but only one view model.
            ' So, use LINQ queries to populate the views.
    
            ' Set the data context for the Item view.
            ItemViewOnPage.DataContext = From Accomplishment In vm.Accomplishments Where Accomplishment.Type = "Item" Select Accomplishment
    
            ' Set the data context for the Level view.
            LevelViewOnPage.DataContext = From Accomplishment In vm.Accomplishments Where Accomplishment.Type = "Level" Select Accomplishment
    
            ' If there is only one view, you could use the following code
            ' to populate the view.
            ' AccomplishmentViewOnPage.DataContext = vm.Accomplishments
        End Sub
    End Class
    
  3. On the File menu, click Save All. (Ctrl+Shift+S)

  4. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Maintaining page state

In this procedure, you add the code to maintain the page state. If the user navigates away from your app and then back, you want to maintain the page state. If the user starts a new instance of the app from the app list, you do not want to maintain the page state.

First, you create a utility class that has one property named IsLaunching. You use the property to track if the user has started a new instance of the app. The only state that you need to maintain in this app is the ViewModel. Windows Phone provides a page state object, and you can store the entire ViewModel in the state as one object. You do not need to store the items from the ViewModel collection individually. When the user returns to the app, you can retrieve the ViewModel from the state and bind the views to it. You will test the page state in the final procedure.

To maintain page state

  1. In Solution Explorer, right-click the project MVVMTestApp, point to Add and then click Class.

    The Add New Item dialog appears, with the class template selected.

Visual Basic Note:

For Visual Basic, you must select the Code File template manually.

  1. In the Name box, type StateUtilities.cs or StateUtilities.vb, and then click Add.

    A new class is added to the project, and opens in the code editor.

  2. Replace the code with the following code:

    using System;
    
    namespace MVVMTestApp
    {
        public static class StateUtilities
        {
            private static Boolean isLaunching;
    
            public static Boolean IsLaunching
            {
                get { return isLaunching; }
                set { isLaunching = value; }
            }
        }
    }
    
    Public NotInheritable Class StateUtilities
    
        Private Sub New()
        End Sub
    
        Private Shared _isLaunching As Boolean
    
        Public Shared Property IsLaunching As Boolean
            Get
                Return _isLaunching
            End Get
            Set(ByVal value As Boolean)
                _isLaunching = value
            End Set
        End Property
    End Class
    
  3. In Solution Explorer, right-click App.xaml and then click View Code.

    The code-behind file opens in the code editor.

  4. Locate the Application_Launching method and replace it with the following code:

    private void Application_Launching(object sender, LaunchingEventArgs e)
    {
        StateUtilities.IsLaunching = true;
    }
    
    Private Sub Application_Launching(ByVal sender As Object, ByVal e As LaunchingEventArgs)
    
        StateUtilities.IsLaunching = True
    End Sub
    
  5. Locate the Application_Activated method and replace it with the following code:

    private void Application_Activated(object sender, ActivatedEventArgs e)
    {
        StateUtilities.IsLaunching = false;
    }
    
    Private Sub Application_Activated(ByVal sender As Object, ByVal e As ActivatedEventArgs)
    
        StateUtilities.IsLaunching = False
    End Sub
    
  6. In Solution Explorer, right-click MainPage.xaml and then click View Code.

    The code-behind file opens in the code editor.

  7. In the MainPage class, in the OnNavigatedTo method, locate the call to GetAccomplishments. It looks like the following:

    // Later, you will replace this next line with something better.
    vm.GetAccomplishments();
    
    ' Later, you will replace this next line with something better.
    vm.GetAccomplishments()
    

    Replace it with the following code:

    if (!StateUtilities.IsLaunching && this.State.ContainsKey("Accomplishments"))
    {
        // Old instance of the application
        // The user started the application from the Back button.
    
        vm = (ViewModel)this.State["Accomplishments"];
        //MessageBox.Show("Got data from state");
    }
    else
    {
        // New instance of the application
        // The user started the application from the application list,
        // or there is no saved state available.
    
        vm.GetAccomplishments();
        //MessageBox.Show("Did not get data from state");
    }
    
    If (Not StateUtilities.IsLaunching) AndAlso Me.State.ContainsKey("Accomplishments") Then
    
        ' Old instance of the application
        ' The user started the application from the Back button.
    
        vm = CType(Me.State("Accomplishments"), ViewModel)
        'MessageBox.Show("Got data from state")
    
    Else
        ' New instance of the application
        ' The user started the application from the application list,
        ' or there is no saved state available.
    
        vm.GetAccomplishments()
        'MessageBox.Show("Did not get data from state")
    End If
    
  8. In the MainPage class, after the OnNavigatedTo method, add the following code:

    protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);
    
        if (this.State.ContainsKey("Accomplishments"))
        {
            this.State["Accomplishments"] = vm;
        }
        else
        {
            this.State.Add("Accomplishments", vm);
        }
    
        StateUtilities.IsLaunching = false;
    }
    
    Protected Overrides Sub OnNavigatedFrom(ByVal e As System.Windows.Navigation.NavigationEventArgs)
    
        MyBase.OnNavigatedFrom(e)
    
        If Me.State.ContainsKey("Accomplishments") Then
    
            Me.State("Accomplishments") = vm
    
        Else
            Me.State.Add("Accomplishments", vm)
        End If
    
        StateUtilities.IsLaunching = False
    End Sub
    
  9. On the File menu, click Save All. (Ctrl+Shift+S)

  10. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Saving data to isolated storage

In this procedure, you will add the code to save the app data to isolated storage. In this app, you will save the data in the isolated storage settings dictionary, not a text file or a local database. For more information about the types of isolated storage, see Data for Windows Phone 8. For more information about the local settings dictionary, see the IsolatedStorageSettings class. You will test the isolated storage in the final procedure.

The data that you save to isolated storage are the Accomplishment objects, which are data-bound. When the user exits your app, Windows Phone automatically calls Save to save the isolated storage. Because you want to save the data only when the user explicitly clicks the Save button, not when the user exits the app, you must break the connection between the isolated storage and the data-bound object. To do that, you save a copy of the Accomplishment object instead of the actual object itself.

To save data to isolated storage

  1. In Solution Explorer, double-click ViewModel.cs or ViewModel.vb to open the file in the code editor.

  2. In the ViewModel class, after the three GetAccomplishments methods, add the following code:

    public void SaveAccomplishments()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
    
        foreach (Accomplishment a in Accomplishments)
        {
            if (settings.Contains(a.Name))
            {
                settings[a.Name] = a;
            }
            else
            {
                settings.Add(a.Name, a.GetCopy());
            }
        }
    
        settings.Save();
        MessageBox.Show("Finished saving accomplishments");
    }
    
    Public Sub SaveAccomplishments()
    
        Dim settings As IsolatedStorageSettings = IsolatedStorageSettings.ApplicationSettings
    
        For Each a In Accomplishments
    
            If settings.Contains(a.Name) Then
    
                settings(a.Name) = a
            Else
                settings.Add(a.Name, a.GetCopy())
            End If
        Next a
    
        settings.Save()
        MessageBox.Show("Finished saving accomplishments")
    End Sub
    
  3. On the File menu, click Save All. (Ctrl+Shift+S)

  4. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Adding the app bar

In this app, the user chooses when they want to save their data by clicking a Save button. In this procedure, you add a Save button to the app bar. For the button’s icon, you will use one of the standard Windows Phone icons. For more information, see App bar for Windows Phone.

To add the icon file

  1. In Solution Explorer, right-click the project MVVMTestApp, point to Add and then click Existing Item.

    The Add Existing Item dialog appears.

  2. Browse to the following location to locate the standard icons:

    C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0\Icons\dark

  3. Click the file appbar.save.rest.png, and then click Add.

  4. In Solution Explorer, select the new file.

  5. In the Properties window, set the following properties for the new file:

Property

Value

Build Action

Content

Copy to Output Directory

Copy if newer

File Name

AppBarSave.png

  1. On the File menu, click Save All. (Ctrl+Shift+S)

  2. On the Build menu, click Build Solution. (Ctrl+Shift+B)

To add the app bar

  1. In Solution Explorer, right-click MainPage.xaml and then click View Code.

    The code-behind file opens in the code editor.

  2. In the MainPage class, after the OnNavigatedFrom method, add the following code:

    private void AppBarSave_Click(object sender, EventArgs e)
    {
        vm.SaveAccomplishments();
    }
    
    Private Sub AppBarSave_Click(ByVal sender As Object, ByVal e As EventArgs)
    
        vm.SaveAccomplishments()
    End Sub
    
  3. In Solution Explorer, double-click MainPage.xaml to open the XAML in the designer.

  4. Locate the sample app bar element, which is added to your project by default. It looks like the following:

    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
    
    …
    
    </phone:PhoneApplicationPage.ApplicationBar>-->
    
  5. Replace the sample app bar element with the following code:

    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True" >
            <shell:ApplicationBarIconButton IconUri="AppBarSave.png" Text="Save"  Click="AppBarSave_Click" />
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>
    
  6. On the File menu, click Save All. (Ctrl+Shift+S)

  7. On the Build menu, click Build Solution. (Ctrl+Shift+B)

Testing the app

In the following procedures, you test the app.

To run the app in the emulator

  1. On the standard toolbar, set the deployment target of the app to Windows Phone Emulator (not Windows Phone Device).

  2. On the Debug menu, click Start Without Debugging.

    The emulator opens, and then the app starts.

To test that the View is bound to the Model

  1. In the Items Collected text boxes, enter numbers.

    The underlying model is updated. Because the model uses the INotifyPropertyChanged interface, when you change the Count property, the PropertyChanged event is fired. The View is notified, and the check column to the right of the text box updates and displays the new value that you entered.

  2. In the Levels Completed check boxes, select the first level.

    The underlying model is updated. Because the model uses the INotifyPropertyChanged interface, when you change the Completed property, the PropertyChanged event is fired. The View is notified, and the check column to the right of the text box updates and displays the value True.

To test the data converter

  • In the Levels Completed check boxes, select one of the levels.

    The check box is selected, and the check column to the right of the text box updates and displays the value True. Notice that the check box becomes disabled. The enabled property of the check box is set to False, the opposite of the value of the check box, by using the custom data converter that you created.

To test the page state

  1. Enter data in the text boxes and check boxes.

  2. Click the Start button, and then click the Back button.

    The app resumes, and the page state is maintained.

To test the data in isolated storage

  1. Enter data in the text boxes and check boxes.

  2. Click the Save button.

    The message “Finished saving accomplishments” appears.

  3. Enter new data in the text boxes and check boxes.

  4. Click the Start button, and then click the right arrow.

    The app list appears.

  5. Click MVVMTestApp to launch a new instance of the app.

    The app loads the data that you saved to isolated storage, not the data that you entered after that.

Next Steps

You can expand the functionality of the app in the following ways:

  • Allow the user to enter the names of the items to collect.

  • Change the Save functionality to an MVVM command.

See Also

Other Resources

WPF Apps With The Model-View-ViewModel Design Pattern

Windows Phone 7, Reactive Extensions, OData, MVVM

MVVM Light Toolkit

Quickstart: Data binding to controls for Windows Phone