Using Portable Class Library with Model-View-View Model
You can use the .NET Framework Portable Class Library to implement the Model-View-View Model (MVVM) pattern and share assemblies across multiple platforms.
Importante
We strongly discourage the use of portable class libraries in new application development, because they target only a very specific subset of .NET implementations. If you're building a reusable library to share code between .NET Framework and other .NET implementations, the recommended replacement is a .NET Standard 2.0 library. Otherwise, use .NET 5 or later. For more information, see .NET Standard.
MVVM is an application pattern that isolates the user interface from the underlying business logic. You can implement the model and view model classes in a Portable Class Library project in Visual Studio 2012, and then create views that are customized for different platforms. This approach enables you to write the data model and business logic only once, and use that code from .NET Framework, Silverlight, Windows Phone, and Windows 8.x Store apps, as shown in the following illustration.
This topic does not provide general information about the MVVM pattern. It only provides information about how to use Portable Class Library to implement MVVM. For more information about MVVM, see the MVVM Quickstart Using the Prism Library 5.0 for WPF.
When you target the .NET Framework 4.5, .NET for Windows 8.x Store apps, Silverlight, or Windows Phone 7.5 for your Portable Class Library project, the following classes are available for implementing the MVVM pattern:
System.Collections.ObjectModel.ObservableCollection<T> class
System.Collections.ObjectModel.ReadOnlyObservableCollection<T> class
System.Collections.Specialized.INotifyCollectionChanged class
System.Collections.Specialized.NotifyCollectionChangedAction class
System.Collections.Specialized.NotifyCollectionChangedEventArgs class
System.Collections.Specialized.NotifyCollectionChangedEventHandler class
All classes in the System.ComponentModel.DataAnnotations namespace
To implement MVVM, you typically create both the model and the view model in a Portable Class Library project, because a Portable Class Library project cannot reference a non-portable project. The model and view model can be in the same project or in separate projects. If you use separate projects, add a reference from the view model project to the model project.
After you compile the model and view model projects, you reference those assemblies in the app that contains the view. If the view interacts only with the view model, you only have to reference the assembly that contains the view model.
The following example shows a simplified model class that could reside in a Portable Class Library project.
using System;
namespace SimpleMVVM.Model
{
public class Customer
{
public int CustomerID
{
get;
set;
}
public string FullName
{
get;
set;
}
public string Phone
{
get;
set;
}
}
}
Namespace SimpleMVVM.Model
Public Class Customer
Public Property CustomerID() As Integer
Public Property FullName() As String
Public Property Phone() As String
End Class
End Namespace
The following example shows a simple way to populate, retrieve, and update the data in a Portable Class Library project. In a real app, you would retrieve the data from a source such as a Windows Communication Foundation (WCF) service.
using System;
using System.Collections.Generic;
using System.Linq;
namespace SimpleMVVM.Model
{
public class CustomerRepository
{
private List<Customer> _customers;
public CustomerRepository()
{
_customers = new List<Customer>
{
new Customer(){ CustomerID = 1, FullName="Dana Birkby", Phone="394-555-0181"},
new Customer(){ CustomerID = 2, FullName="Adriana Giorgi", Phone="117-555-0119"},
new Customer(){ CustomerID = 3, FullName="Wei Yu", Phone="798-555-0118"}
};
}
public List<Customer> GetCustomers()
{
return _customers;
}
public void UpdateCustomer(Customer SelectedCustomer)
{
Customer customerToChange = _customers.Single(c => c.CustomerID == SelectedCustomer.CustomerID);
customerToChange = SelectedCustomer;
}
}
}
Namespace SimpleMVVM.Model
Public Class CustomerRepository
Private _customers As List(Of Customer)
Public Sub New()
_customers = New List(Of Customer) From
{
New Customer() With {.CustomerID = 1, .FullName = "Dana Birkby", .Phone = "394-555-0181"},
New Customer() With {.CustomerID = 2, .FullName = "Adriana Giorgi", .Phone = "117-555-0119"},
New Customer() With {.CustomerID = 3, .FullName = "Wei Yu", .Phone = "798-555-0118"}
}
End Sub
Public Function GetCustomers() As List(Of Customer)
Return _customers
End Function
Public Sub UpdateCustomer(SelectedCustomer As Customer)
Dim customerToChange = _customers.Single(Function(c) c.CustomerID = SelectedCustomer.CustomerID)
customerToChange = SelectedCustomer
End Sub
End Class
End Namespace
A base class for view models is frequently added when implementing the MVVM pattern. The following example shows a base class.
using System;
using System.ComponentModel;
namespace SimpleMVVM.ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}
Imports System.ComponentModel
Namespace SimpleMVVM.ViewModel
Public MustInherit Class ViewModelBase
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(propname As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
End Sub
End Class
End Namespace
An implementation of the ICommand interface is frequently used with the MVVM pattern. The following example shows an implementation of the ICommand interface.
using System;
using System.Windows.Input;
namespace SimpleMVVM.ViewModel
{
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
}
Imports System.Windows.Input
Namespace SimpleMVVM.ViewModel
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
End Namespace
The following example shows a simplified view model.
using System;
using System.Collections.Generic;
using SimpleMVVM.Model;
namespace SimpleMVVM.ViewModel
{
public class CustomerViewModel : ViewModelBase
{
private List<Customer> _customers;
private Customer _currentCustomer;
private CustomerRepository _repository;
public CustomerViewModel()
{
_repository = new CustomerRepository();
_customers = _repository.GetCustomers();
WireCommands();
}
private void WireCommands()
{
UpdateCustomerCommand = new RelayCommand(UpdateCustomer);
}
public RelayCommand UpdateCustomerCommand
{
get;
private set;
}
public List<Customer> Customers
{
get { return _customers; }
set { _customers = value; }
}
public Customer CurrentCustomer
{
get
{
return _currentCustomer;
}
set
{
if (_currentCustomer != value)
{
_currentCustomer = value;
OnPropertyChanged("CurrentCustomer");
UpdateCustomerCommand.IsEnabled = true;
}
}
}
public void UpdateCustomer()
{
_repository.UpdateCustomer(CurrentCustomer);
}
}
}
Imports System.Collections.Generic
Imports SimpleMVVM.Model
Namespace SimpleMVVM.ViewModel
Public Class CustomerViewModel
Inherits ViewModelBase
Private _customers As List(Of Customer)
Private _currentCustomer As Customer
Private _repository As CustomerRepository
Private _updateCustomerCommand As RelayCommand
Public Sub New()
_repository = New CustomerRepository()
_customers = _repository.GetCustomers()
WireCommands()
End Sub
Private Sub WireCommands()
UpdateCustomerCommand = New RelayCommand(AddressOf UpdateCustomer)
End Sub
Public Property UpdateCustomerCommand() As RelayCommand
Get
Return _updateCustomerCommand
End Get
Private Set(value As RelayCommand)
_updateCustomerCommand = value
End Set
End Property
Public Property Customers() As List(Of Customer)
Get
Return _customers
End Get
Set(value As List(Of Customer))
_customers = value
End Set
End Property
Public Property CurrentCustomer() As Customer
Get
Return _currentCustomer
End Get
Set(value As Customer)
If _currentCustomer.Equals(value) Then
_currentCustomer = value
OnPropertyChanged("CurrentCustomer")
UpdateCustomerCommand.IsEnabled = True
End If
End Set
End Property
Public Sub UpdateCustomer()
_repository.UpdateCustomer(CurrentCustomer)
End Sub
End Class
End Namespace
From a .NET Framework 4.5 app, Windows 8.x Store app, Silverlight-based app, or Windows Phone 7.5 app, you can reference the assembly that contains the model and view model projects. You then create a view that interacts with the view model. The following example shows a simplified Windows Presentation Foundation (WPF) app that retrieves and updates data from the view model. You could create similar views in Silverlight, Windows Phone, or Windows 8.x Store apps.
<Window x:Class="SimpleWPF.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:SimpleMVVM.ViewModel;assembly=SimpleMVVM.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModels:MainPageViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="0" Name="textBlock2"
Text="Select a Customer:" VerticalAlignment="Top" />
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Name="CustomersComboBox" VerticalAlignment="Top" Width="173"
DisplayMemberPath="FullName" SelectedItem="{Binding Path=CurrentCustomer, Mode=TwoWay}" ItemsSource="{Binding Path=Customers}" />
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="1" Name="textBlock4" Text="Customer ID" />
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="2" Name="textBlock5" Text="Name" />
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="3" Name="textBlock9" Text="Phone" />
<TextBlock Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="1" Name="CustomerIDTextBlock"
Text="{Binding ElementName=CustomersComboBox, Path=SelectedItem.CustomerID}" />
<TextBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="2" Width="219"
Text="{Binding Path=CurrentCustomer.FullName, Mode=TwoWay}" />
<TextBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="3" Width="219"
Text="{Binding Path=CurrentCustomer.Phone, Mode=TwoWay}" />
<Button
Command="{Binding UpdateCustomerCommand}"
Content="Update" Height="23" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="4"
Name="UpdateButton" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>