- Introduction
- Portable Class Libraries
- Common Usages for Portable Class Libraries
- Creating a Library to Expose Models and ViewModels
- Creating a WPF Client
- Creating a Silverlight Client
- Creating a Windows 8 Store App Client
- Creating a Windows Phone Client
- Conclusions
Creating a Library to Expose Models and ViewModels
The first part of the example is made of a portable class library that exposes a list of customers, as well as the objects to work with such a list. Create a new project of type Portable Class Library called CustomersLibrary, leaving the default selection of target platforms unchanged. Once the project is created, remove the Class1.vb (or Class1.cs if you use Visual C#) and add a new class called Customer. Listing 1 shows the code of the new class.
Listing 1Implementing the Customer class.
Imports System.Collections.ObjectModel Imports System.Windows.Input Imports System.ComponentModel Public Class Customer Public Property CustomerID() As Integer Public Property CompanyName() As String Public Property EmailAddress As String Public Property PhysicalAddress As String Public Property Phone() As String End Class
The Customer class is a simplified representation of customer information. You don't need detailed information at this point; keeping things simple allows for writing cleaner and easier code.
Next, you need a collection that holds a list of customers. All the targeted platforms support the ObservableCollection class, which is appropriate for data-binding scenarios. Listing 2 shows how to implement a collection of customers, also generating some sample data when the collection is instantiated.
Listing 2Defining a collection of customers using the ObservableCollection class.
Public Class Customers Private _customers As ObservableCollection(Of Customer) Public Sub New() Me._customers = New ObservableCollection(Of Customer) 'Create some sample data Me._customers.Add(New Customer With {.CustomerID = 1, .CompanyName = "A Fabulous Company", .EmailAddress = _ "fable@fabulouscompany.com", .Phone = "111-111-1111", .PhysicalAddress = "Seattle, WA"}) Me._customers.Add(New Customer With {.CustomerID = 2, .CompanyName = "RM Consulting", .EmailAddress = _ "rm@rmconsulting.it", .Phone = "222-111-1111", .PhysicalAddress = "Varese, Italy"}) Me._customers.Add(New Customer With {.CustomerID = 3, .CompanyName = "Northwind Traders", .EmailAddress = _ "nwind@northwindtraders.com", .Phone = "222-222-1111", .PhysicalAddress = "San Francisco, CA"}) End Sub 'Return the full list of customers Public Function GetCustomers() As ObservableCollection(Of Customer) Return Me._customers End Function 'Remove the specified customer from the collection Public Sub DeleteCustomer(currentCustomer As Customer) Me._customers.Remove(currentCustomer) End Sub End Class
Methods of this class won't be used directly by client applications; instead, they're invoked by ViewModels. Most of the power of the MVVM pattern relies on the ICommand interface. This object represents a command that executes an action and that is data-bound to controls of the user interface. With this approach, your clients will invoke commands rather than handling click events, making the user interface totally independent from imperative code.
In the MVVM pattern, we need to define a class that implements the ICommand interface and relays requests about command execution and about checks on the command state. This class then sends back to the ViewModel the request of actually executing commands. This class, known as RelayCommand, is shown in Listing 3. The RelayCommand class provides a reusable command infrastructure and helps developers keep ViewModels simpler.
Listing 3Defining the RelayCommand class.
Public Class RelayCommand Implements ICommand Private _isEnabled As Boolean Private ReadOnly _handler As Action Public Sub New(ByVal handler As Action) _handler = handler End Sub Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged Public Property IsEnabled() As Boolean Get Return _isEnabled End Get Set(ByVal value As Boolean) If (value <> _isEnabled) Then _isEnabled = value RaiseEvent CanExecuteChanged(Me, EventArgs.Empty) End If End Set End Property Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute Return IsEnabled End Function Public Sub Execute(parameter As Object) Implements ICommand.Execute _handler() End Sub End Class
The next step is defining a base, reusable ViewModel so that specialized ViewModels can inherit from that base version. In this case, the base ViewModel only provides the infrastructure for change notifications over data, as demonstrated in Listing 4.
Listing 4Defining a base ViewModel.
'Generic ViewModel Public MustInherit Class ViewModelBase Implements INotifyPropertyChanged Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged 'Raise a property change notification Protected Overridable Sub OnPropertyChanged(propname As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname)) End Sub End Class
Another benefit of the portable class library is that it supports change notifications through the INotifyPropertyChanged interface implementation. This implies that the data-binding engine in WPF, the Windows Runtime, Windows Phone, and Silverlight works similarly and raises change notifications in the same way.
The final step for the portable class library is implementing a ViewModel that exposes to callers the customer information and commands to manage customers. Listing 5 provides this implementation.
Listing 5Implementing a view-model for customers information.
Public Class CustomerViewModel Inherits ViewModelBase Private _customers As ObservableCollection(Of Customer) Private _currentCustomer As Customer Private _customersList As Customers Private _deleteCustomerCommand As RelayCommand Public Sub New() Me._customersList = New Customers Me._customers = Me._customersList.GetCustomers SetupCommands() End Sub 'Set up commands by creating instances of RelayCommand 'and passing the appropriate delegate Private Sub SetupCommands() DeleteCustomerCommand = New RelayCommand(AddressOf DeleteCustomer) End Sub 'Expose the command Public Property DeleteCustomerCommand() As RelayCommand Get Return _deleteCustomerCommand End Get Private Set(value As RelayCommand) _deleteCustomerCommand = value End Set End Property 'Expose the list of contacts Public Property Customers() As ObservableCollection(Of Customer) Get Return Me._customers End Get Set(value As ObservableCollection(Of Customer)) Me._customers = value End Set End Property 'Represents the currently selected Customer 'Raise a property change notification 'Enable the command Public Property CurrentCustomer() As Customer Get Return _currentCustomer End Get Set(value As Customer) _currentCustomer = value OnPropertyChanged("CurrentCustomer") DeleteCustomerCommand.IsEnabled = True End Set End Property 'Delete the specified Customer Public Sub DeleteCustomer() Me._customersList.DeleteCustomer(CurrentCustomer) End Sub End Class
Notice that the list of customers is exposed as an ObservableCollection, which is the most appropriate way to expose data to XAML callers. Also, notice the availability of the CurrentCustomer property, which is also used to synchronize the current customer in the collection with the instance of the customer inside controls of the user interface.
At this point, you can compile the library and ensure that you get no errors.