Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

February 7, 2011

Simple Silverlight MVVM Base Class

Filed under: ASP.NET RIA Services,C#,Silverlight,VB.NET @ 12:54 am

As you start to add more Views and associated View/Models to your Silverlight line of business (LOB) application, you will notice that the classes include some common and repeated code. Building a simple base class for your View/Models provides a single place for that common code.

NOTE: This post is part of a series that starts with this prior post. The example in this post uses the application that is built as part of that series.

NOTE: If you are new to MVVM, this prior post provides an introduction.

To keep things simple, the ViewModelBase class defined here supports two features that you will need in most of your View/Model classes:

  • Implementing INotifyPropertyChanged

When building an MVVM application, the View/Model includes a set of properties that expose the data for your user interface. The View (xaml) connects the user interface element properties, such as a TextBox property, to the View/Model properties using binding.

When the View/Model property is updated, the user interface must be notified that the property is changed so that the user interface is updated. This is done using the PropertyChanged event that is part of INotifyPropertyChanged.

  • Determining whether the view is running from the designer.

If you reference the View/Model in the View’s xaml, the Visual Studio designer creates an instance of the View/Model when you open the View in the designer. Any code you define in the View/Model constructor is then executed. This makes it easy to define design-time data and populate the controls, improving your design-time experience.

But you only want the design-time data at design time. Define a flag accessible in the constructor to determine whether to load the design-time data or the actual data.

NOTE: Code in your constructor that accesses data using WCF RIA Services must not execute at design-time or Visual Studio won’t open the designer.

The ViewModelBase class code is shown below in both VB and C# and then described in detail.

In C#:

using System;
using System.ComponentModel;

namespace InStepSM.SL.ViewModels
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        private static bool? _IsInDesignMode;
        public static bool IsInDesignModeStatic
        {
            get
            {
                if (! _IsInDesignMode.HasValue)
                    _IsInDesignMode =
                       DesignerProperties.IsInDesignTool;
                return _IsInDesignMode.Value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            // Send an event notification that the property changed
            // This allows the UI to know when one of the items changes
            if (!String.IsNullOrEmpty(propertyName) &&
                    PropertyChanged != null)
                PropertyChanged(this,
                     new PropertyChangedEventArgs(propertyName));
        }
    }
}

In VB:

Imports System.ComponentModel

Namespace ViewModels
    Public Class ViewModelBase
        Implements INotifyPropertyChanged

        Private Shared _IsInDesignMode As Boolean?
        Public Shared ReadOnly Property IsInDesignModeStatic As Boolean
            Get
                If Not _IsInDesignMode.HasValue Then
                    _IsInDesignMode = DesignerProperties.IsInDesignTool
                End If
                Return _IsInDesignMode.Value
            End Get
        End Property

        Public Event PropertyChanged(ByVal sender As Object, 
            ByVal e As System.ComponentModel.PropertyChangedEventArgs) 
         Implements
         System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Protected Sub OnPropertyChanged(ByVal propertyName As String)
            ‘ Send an event notification that the property changed
            ‘ This allows the UI to know when one of the items changes
            If Not String.IsNullOrEmpty(propertyName) Then
                RaiseEvent PropertyChanged(Me,
                    New PropertyChangedEventArgs(propertyName))
            End If
        End Sub
    End Class
End Namespace

The IsInDesignModeStatic property defines whether the application is currently in design mode. If this flag is true, then the View is currently open in the Visual Studio (or Expression Blend) designer. If it is false, then the View is opened at runtime.

[For more information on detecting design mode, see this post from Laurent Bugnion.]

The PropertyChanged event defines the event for the INotifyPropertyChanged interface. The OnPropertyChanged method raises the PropertyChanged event. Code in the View/Model calls OnPropertyChanged in the setter for any property that could be bound to the View.

For example of how to use this ViewModelBase class, here is a StudentsViewModel for a Students View that inherits from the ViewModelBase.

In C#:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel.DomainServices.Client;
using InStepSM.SL.Web;

namespace InStepSM.SL.ViewModels
{
    public class StudentsViewModel : ViewModelBase
    {
        private StudentContext StudentContextInstance;

        private ObservableCollection<Student> _StudentsList;
        public ObservableCollection<Student> StudentsList
        {
            get
            {
                return _StudentsList;
            }
            set
            {
                if (_StudentsList != value)
                {
                    _StudentsList = value;
                    OnPropertyChanged("StudentsList");
                }
            }
        }

        public StudentsViewModel()
        {
            LoadData();
        }

        /// <summary>
        /// Loads the data for the application.
        /// </summary>
        private void LoadData()
        {
            if (IsInDesignModeStatic)
            {
                LoadDesignData();
            }
            else
            {
                // Load the student data asynchronously
                StudentContextInstance = new StudentContext();
                var loadop = StudentContextInstance.Load
                       (StudentContextInstance.GetStudentsQuery(),
                                         OnStudentsLoaded, null);
            }
        }

        /// <summary>
        /// Loads temporary data for use in the designer.
        /// </summary>
        /// <remarks></remarks>
        private void LoadDesignData()
        {
            //Design mode data
            ObservableCollection<Student> temp =
                               new ObservableCollection<Student>();
            temp.Add(new Student {LastName = "Baggins",
                                  FirstName = "Bilbo",
                                  Age = 111,
                                  Email = "testemail@live.com",
                                  RegistrationDate = DateTime.Now});
            temp.Add(new Student {LastName = "Baggins",
                                  FirstName = "Frodo",
                                  Age = 32,
                                  Email = "testemail@yahoo.com",
                                  RegistrationDate = DateTime.Now});
            StudentsList = temp;
        }

        private void OnStudentsLoaded(LoadOperation lo )
        {
            StudentsList = 
   new ObservableCollection<Student>(StudentContextInstance.Students);
        }
    }
}

In VB:

Imports System.Collections.ObjectModel
Imports System.ServiceModel.DomainServices.Client
Imports InStepSM.SL.Web

Namespace ViewModels
    Public Class StudentsViewModel
        Inherits ViewModelBase

        Private StudentContextInstance As StudentContext

        Private _StudentsList As ObservableCollection(Of Student)
        Public Property StudentsList() As
                                 ObservableCollection(Of Student)
            Get
                Return _StudentsList
            End Get
            Set(ByVal value As ObservableCollection(Of Student))
                If _StudentsList IsNot value Then
                    _StudentsList = value
                    OnPropertyChanged("StudentsList")
                End If
            End Set
        End Property

        Public Sub New()
            LoadData()
        End Sub

        ”’ <summary>
        ”’ Loads the data for the application.
        ”’ </summary>
        Private Sub LoadData()
            If IsInDesignModeStatic Then
                LoadDesignData()
            Else
                ‘ Load the student data asynchronously
                StudentContextInstance = New StudentContext
                Dim loadop =
                  StudentContextInstance.Load(StudentContextInstance.
                             GetStudentsQuery(),
                             AddressOf OnStudentsLoaded, Nothing)
            End If
        End Sub

        ”’ <summary>
        ”’ Loads temporary data for use in the designer.
        ”’ </summary>
        Private Sub LoadDesignData()
            ‘ Design mode data
            Dim temp As New ObservableCollection(Of Student)
            temp.Add(New Student With {.LastName = "Baggins",
                                       .FirstName = "Bilbo",
                                       .Age = 111,
                                       .Email = "testemail@live.com",
                                       .RegistrationDate = Now()})
            temp.Add(New Student With {.LastName = "Baggins",
                                       .FirstName = "Frodo",
                                       .Age = 32,
                                       .Email = "testemail@yahoo.com",
                                       .RegistrationDate = Now()})
            StudentsList = temp
        End Sub

        Private Sub OnStudentsLoaded(ByVal lo As LoadOperation)
            StudentsList = New ObservableCollection(Of Student)
                                  (StudentContextInstance.Students)
        End Sub
    End Class
End Namespace

This code begins by declaring an instance of a StudentContext. The StudentContext, Student, and Students class referenced by the above code were all generated by Visual Studio as part of WCF RIA Services. See this prior post for details on setting up the database, Entity Framework (EF) model, and associated WCF RIA Services code.

NOTE: This same code also works if you use WCF RIA Services with something other than EF, such as LinqToSql or your own business objects.

The StudentsList property of the ViewModel class defines the list of students. The View binds to this property to display the list of students in a DataGrid. Notice the OnPropertyChanged call. This ensures that the user interface is notified when the StudentsList property changes.

The constructor calls the LoadData method to load the data. The LoadData method uses the IsInDesignModeStatic property from the base class to determine whether the View is in design mode or run mode.

If in design mode, the application loads some hard-coded sample data. This data appears in the designer as shown below:

image

Having design-time data makes it easier to see what the user interface will look like. This is especially useful when you style the DataGrid.

If the View is not in design mode, the code calls the Load method of the StudentContext. This method is generated as part of the WCF RIA Services feature. The first parameter to the Load method is the name of the query to call, which is also in the generated code. Since Silverlight data access is asynchronous, this method call will not wait for the data to be retrieved.

The second parameter to the Load method is the callback function. This callback function is executed when the load is complete, allowing you to work with the data as needed.

In this case, the OnStudentsLoaded method simply assigns the resulting data to the StudentsList property. The Setter of the StudentsList property then raises the OnPropertyChanged event and the grid is updated to display the list of students.

Here is the associated xaml:

<navigation:Page x:Class="InStepSM.SL.Views.Students"       
xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:d="
http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation=
       "clr-namespace:System.Windows.Controls;
       assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="Students Page" 
xmlns:riaControls=
       "clr-namespace:System.Windows.Controls;
        assembly=System.Windows.Controls.DomainServices"
xmlns:sdk=
    "
http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
 xmlns:vms="clr-namespace:InStepSM.SL.ViewModels">

    <!– Reference the View/Model –>
    <navigation:Page.DataContext>
        <vms:StudentsViewModel />
    </navigation:Page.DataContext>

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBlock x:Name="HeaderText"
                   Grid.Row="0"
                   Grid.Column="0" Grid.ColumnSpan="2"
                    Style="{StaticResource HeaderTextStyle}"
                    Text="Students" />
             
        <sdk:DataGrid AutoGenerateColumns="False"
                Grid.Row="1" Grid.Column="1"
                Height="200"
                HorizontalAlignment="Stretch"
                ItemsSource="{Binding StudentsList}"
                Name="studentDataGrid"
                RowDetailsVisibilityMode="VisibleWhenSelected"
                VerticalAlignment="Top"
                Width="457">
        <sdk:DataGrid.Columns>
        <sdk:DataGridTextColumn x:Name="firstNameColumn"
                                Binding="{Binding Path=FirstName}"
                                Header="First Name"
                                Width="SizeToHeader" />
        <sdk:DataGridTextColumn x:Name="lastNameColumn"
                                Binding="{Binding Path=LastName}"
                                Header="Last Name"
                                Width="SizeToHeader" />
        <sdk:DataGridTextColumn x:Name="ageColumn"
                                Binding="{Binding Path=Age}"
                                Header="Age" Width="SizeToHeader" />
        <sdk:DataGridTextColumn x:Name="emailColumn"
                                Binding="{Binding Path=Email}"
                                Header="Email" Width="120" />
        <sdk:DataGridTemplateColumn x:Name="registrationDateColumn"
                                    Header="Registration Date"
                                    Width="SizeToHeader">
            <sdk:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <sdk:DatePicker SelectedDate="{Binding
                        Path=RegistrationDate,
                        Mode=TwoWay,
                        NotifyOnValidationError=true,
                        ValidatesOnExceptions=true,
                        TargetNullValue=”}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellEditingTemplate>
            <sdk:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding
               Path=RegistrationDate, StringFormat=\{0:d\}}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellTemplate>
        </sdk:DataGridTemplateColumn>
        </sdk:DataGrid.Columns>
        </sdk:DataGrid>

        <Grid Grid.Row="2"
              Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

        <Button x:Name="SaveButton"
                Grid.Column="1"
                Margin="10,10,0,10"
                Content="Save"
                HorizontalAlignment="Right"
                Width="60" />
        </Grid>
    </Grid>
</navigation:Page>

The result looks something like this:

image

NOTE: The resulting page is currently read-only because there is no Save implementation defined in the above code. Handling the Save command is information for a later post.

The ViewModelBase class defined in this post provides a starting point for building your View/Model base class.

Enjoy!

2 Comments

  1.   Luciano Evaristo Guerche — February 7, 2011 @ 4:44 am    Reply

    Deborah,

    I’d rather rename OnPropertyChanged to RaisePropertyChanged.

    Great post by the way.

  2.   Lee Robie — July 22, 2011 @ 11:07 am    Reply

    Thanks a million for this series .. I’m still struggling with the connections between the projects, so this all helps. I’ve been waiting for someone to dissect the Business App template .. it is very confusing.

    One thing about the implementation above: INotifyPropertyChanged is defined so that if you pass null or “”, it means “all properties changed”. So, just remove the first part of the if and only check for a null event. cheers,

    – lee
    lrobie@cinci.rr.com

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2023 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs