
Existem várias formas diferentes de definir a camada de apresentação, pode-se seguir uma aproximação tradicional, na qual toda a responsabilidade da camada de apresentação se encontra na View ou então podemos seguir um pattern para prodecer à separação de responsabilidades da camada de apresentção.
A aproximação mais tradicional torna-se mais simples e criar permite rapidamente uma camada de apresentação. É ideal quando se trata de aplicações de pequena dimensão .
O problema é que não existem aplicações de pequena dimensão, algumas podem começar dessa forma, mas muito rapidamente crescem e transforma-se em uma dor de cabeça para qualquer equipa de manutenção, dando origem a mais uma “Black Box” passados alguns anos.
Para ajudar a solucionar esse tipo de problema recorrente, nasceram algumas patterns que nos permitem estruturar de uma melhor forma a camada de apresentação das aplicações, entre elas estão o MVC (Model-View-Controller), MPV (Model-View-Presenter) e o MVVM (Model-View-ViewModel) que é o tema de este post.
Como não podemos deixar de falar em Silverlight ou WPF quando o tema é MVVM, vou apresentar um tutorial no qual demonstro como implementar MVVM no Silverlight.
O MVVM pretende estabelecer uma separação clara de responsabilidades na camada de apresentação, a View apenas é responsável pela User Interface, o Model detém as regras de negócio (classes de negócio, acesso a dados, etc) e o ViewModel é o responsável de fornecer à View apenas os dados que ela necessita e é também o ViewModel quem tem a responsabilidade de reagir aos eventos gerados pela View.
Uma das grandes vantagens na utilização de um pattern como este, é que permite alterar completamente a UI apresentada ao utilizador sem que se torne necessário alterar mais do que a View, ou então, num caso mais extremo, partilhar o Model e o ViewModel e criar dois Front-End diferentes, como Silverlight e Windows Phone 7.
Vamos então ao que interessa, criar uma Demo de uma aplicação de gestão de Clientes, que apresenta a lista de clientes de uma empresa e também permite adicionar um novos clientes à lista actual.
Vamos criar um novo projecto do tipo Silverlight Application no VS2010

Seleccionar a check-box “Host the Silverlight application in a new Web site”

Adicionar duas novas referências ao projecto Silverlight

Adicionar as referências à página MainPage.xaml
<UserControl x:Class="Silverlight_MVVM_Demo.MainPage"
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"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
</Grid>
</UserControl>
Vamos definir a View

Agora em XAML
<UserControl x:Class="Silverlight_MVVM_Demo.MainPage"
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"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="Gray" Width="800" Height="600">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--DataGrid Definition-->
<sdk:DataGrid Height="300" Grid.Row="0" BorderThickness="1"/>
<!--Data Input Definition-->
<sdk:Label
Grid.Row="1"
Height="28"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="171"
Content="Adicionar novo cliente"
FontFamily="Verdana"
FontSize="13.333"
FontWeight="Bold" />
<TextBlock
HorizontalAlignment="Left"
Margin="80,59,0,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="Nome"
VerticalAlignment="Top"/>
<TextBox
HorizontalAlignment="Left"
Margin="80,79,0,0"
Grid.Row="1"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="245"/>
<TextBlock
HorizontalAlignment="Left"
Margin="80,120,0,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="NIF"
VerticalAlignment="Top"/>
<TextBox
HorizontalAlignment="Left"
Margin="80,140,0,136"
Grid.Row="1"
TextWrapping="Wrap"
Width="245"
d:LayoutOverrides="Height"/>
<TextBlock
HorizontalAlignment="Left"
Margin="80,0,0,98"
Grid.Row="1"
TextWrapping="Wrap"
Text="Telemóvel"
VerticalAlignment="Bottom"/>
<TextBox
HorizontalAlignment="Left"
Margin="80,0,0,70"
Grid.Row="1"
TextWrapping="Wrap"
Width="245"
VerticalAlignment="Bottom"/>
<TextBlock
Margin="0,0,229,98"
Grid.Row="1"
TextWrapping="Wrap"
Text="Número de Cliente"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="111"/>
<TextBox
Margin="0,0,96,70"
Grid.Row="1"
TextWrapping="Wrap"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="245"/>
<TextBlock
Margin="0,120,321,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="Fax"
VerticalAlignment="Top"
HorizontalAlignment="Right"
RenderTransformOrigin="-1.579,0.438"/>
<TextBox
Margin="0,140,96,136"
Grid.Row="1"
TextWrapping="Wrap"
d:LayoutOverrides="Height"
HorizontalAlignment="Right"
Width="245"/>
<TextBlock
Margin="0,59,293,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="Telefone"
VerticalAlignment="Top"
HorizontalAlignment="Right"/>
<TextBox
Margin="0,79,96,0"
Grid.Row="1"
TextWrapping="Wrap"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Width="245"/>
<Button
Content="Adicionar"
HorizontalAlignment="Right"
Margin="0,0,8,8"
Grid.Row="1"
VerticalAlignment="Bottom"
Width="75"/>
</Grid>
</UserControl>
Criar a estrutura de directórios

De seguida adicionar ao directório Model uma nova classe com o nome Cliente.cs com a seguinte estrutura
namespace Silverlight_MVVM_Demo.Model
{
public class Cliente
{
public string ClienteID { get; set; }
public string Nome { get; set; }
public string NIF { get; set; }
public string Telefone { get; set; }
public string Telemovel { get; set; }
public string Fax { get; set; }
}
}
Vamos adicionar duas classe base que implementam os Interfaces ICommand e INotifyPropertyChanged.
Classe RelayCommand
using System;
using System.Windows.Input;
namespace Silverlight_MVVM_Demo.ViewModel
{
public class RelayCommand : ICommand
{
private Action<Object> _handler;
public RelayCommand(Action<Object> handler)
{
_handler = handler;
}
private bool _isEnabled = true;
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(parameter);
}
}
}
Se seguirmos uma abordagem simplista, podemos olhar para o ICommand como o interface que permite o ViewModel receber notificações de eventos da View.
Classe ViewModelBase
namespace Silverlight_MVVM_Demo.ViewModel
{
using System.ComponentModel;
/*TODOS OS VIEWMODEL HERDAM DESTA CLASSE*/
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Vamos herdar todos os ViewModel da classe ViewModelBase para simplificar as notificações quando existem alterações nos conteúdos das propriedades, assim apenas vai ser necessário chamar o método RaisePropertyChanged(“NomeDaPropriedade”) para notificar o ViewModel e a View caso tal seja necessário.
De seguida criamos a classe de ViewModel
namespace Silverlight_MVVM_Demo.ViewModel
{
using Model;
using System.Collections.ObjectModel;
public class MainPageViewModel : ViewModelBase
{
#region View Data Collection
private ObservableCollection<Cliente> _ClientList;
public ObservableCollection<Cliente> ClientList
{
get { return _ClientList; }
set
{
_ClientList = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientList");
}
}
#endregion
/*Propriedades utilizadas na View*/
#region View Properties
private string _ClientID;
public string ClientID
{
get { return _ClientID; }
set
{
_ClientID = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientID");
}
}
private string _ClientName;
public string ClientName
{
get { return _ClientName; }
set
{
_ClientName = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientName");
}
}
private string _ClientNIF;
public string ClientNIF
{
get { return _ClientNIF; }
set
{
_ClientNIF = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientNIF");
}
}
private string _ClientPhone;
public string ClientPhone
{
get { return _ClientPhone; }
set
{
_ClientPhone = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientPhone");
}
}
private string _ClientFax;
public string ClientFax
{
get { return _ClientFax; }
set
{
_ClientFax = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientFax");
}
}
private string _ClientCell;
public string ClientCell
{
get { return _ClientCell; }
set
{
_ClientCell = value;
/*Notificar alteração de conteúdo*/
RaisePropertyChanged("ClientCell");
}
}
#endregion
#region Commands
public RelayCommand LoadClientData { get; set; }
public RelayCommand SubmitForm { get; set; }
#endregion
public MainPageViewModel()
{
/*Comandos que o ViewModel vai reconhecer da View*/
this.LoadClientData = new RelayCommand(MockClientList);
this.SubmitForm = new RelayCommand(Submit);
}
/// <summary>
/// Adiciona um novo cliente
/// </summary>
/// <param name="parameter"></param>
protected void Submit(object parameter)
{
/*
Como utilizamos as notificações quando
é alterado o conteúdo de uma propriedade
da View, já temos no ViewModel todos os
dados.
*/
var c = new Cliente
{
ClienteID = ClientID,
Fax = ClientFax,
NIF = ClientNIF,
Nome = ClientName,
Telefone = ClientPhone,
Telemovel = ClientCell
};
ClientList.Add(c);
ClientID = string.Empty;
ClientFax = string.Empty;
ClientCell = string.Empty;
ClientName = string.Empty;
ClientPhone = string.Empty;
ClientNIF = string.Empty;
}
/// <summary>
/// Dados iniciais da DataGrid
/// </summary>
/// <param name="parameter"></param>
public void MockClientList(object parameter)
{
ClientList = new ObservableCollection<Cliente>();
for (int i = 0; i < 3; i++)
{
var c = new Cliente{
ClienteID=i.ToString(),
Fax="123456789",
NIF="789456123",
Nome="Client "+i,
Telefone="5556623",
Telemovel="3237656511"
};
ClientList.Add(c);
}
}
}
}
Agora ja temos definidas as três componentes do MVVM, apenas falta ligar a View com o ViewModel, e isso é conseguido através do Databinding, que o Silverlight nos proporciona para facilitar essa operação. Vamos então completar a página MainPage.xaml com os bindings.
<UserControl x:Class="Silverlight_MVVM_Demo.MainPage"
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"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:vm="clr-namespace:Silverlight_MVVM_Demo.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<!--DATACONTEXT DEFINITION-->
<UserControl.DataContext>
<!--Link entre o ViewModel e a View-->
<vm:MainPageViewModel/>
</UserControl.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<!--Quando a página é carregada é executado o comando LoadClientData-->
<i:InvokeCommandAction Command="{Binding LoadClientData}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid x:Name="LayoutRoot" Background="Gray" Width="800" Height="600">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--DataGrid Definition-->
<sdk:DataGrid Height="300" Grid.Row="0" BorderThickness="1"/>
<!--Data Input Definition-->
<sdk:Label
Grid.Row="1"
Height="28"
HorizontalAlignment="Left"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="171"
Content="Adicionar novo cliente"
FontFamily="Verdana"
FontSize="13.333"
FontWeight="Bold" />
<TextBlock
HorizontalAlignment="Left"
Margin="80,59,0,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="Nome"
VerticalAlignment="Top"/>
<TextBox
Text="{Binding ClientName, Mode=TwoWay}"
HorizontalAlignment="Left"
Margin="80,79,0,0"
Grid.Row="1"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="245"/>
<TextBlock
HorizontalAlignment="Left"
Margin="80,120,0,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="NIF"
VerticalAlignment="Top"/>
<TextBox
Text="{Binding ClientNIF, Mode=TwoWay}"
HorizontalAlignment="Left"
Margin="80,140,0,136"
Grid.Row="1"
TextWrapping="Wrap"
Width="245"
d:LayoutOverrides="Height"/>
<TextBlock
HorizontalAlignment="Left"
Margin="80,0,0,98"
Grid.Row="1"
TextWrapping="Wrap"
Text="Telemóvel"
VerticalAlignment="Bottom"/>
<TextBox
Text="{Binding ClientCell, Mode=TwoWay}"
HorizontalAlignment="Left"
Margin="80,0,0,70"
Grid.Row="1"
TextWrapping="Wrap"
Width="245"
VerticalAlignment="Bottom"/>
<TextBlock
Margin="0,0,229,98"
Grid.Row="1"
TextWrapping="Wrap"
Text="Número de Cliente"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="111"/>
<TextBox
Text="{Binding ClientID, Mode=TwoWay}"
Margin="0,0,96,70"
Grid.Row="1"
TextWrapping="Wrap"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="245"/>
<TextBlock
Margin="0,120,321,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="Fax"
VerticalAlignment="Top"
HorizontalAlignment="Right"
RenderTransformOrigin="-1.579,0.438"/>
<TextBox
Text="{Binding ClientFax, Mode=TwoWay}"
Margin="0,140,96,136"
Grid.Row="1"
TextWrapping="Wrap"
d:LayoutOverrides="Height"
HorizontalAlignment="Right"
Width="245"/>
<TextBlock
Margin="0,59,293,0"
Grid.Row="1"
TextWrapping="Wrap"
Text="Telefone"
VerticalAlignment="Top"
HorizontalAlignment="Right"/>
<TextBox
Text="{Binding ClientPhone, Mode=TwoWay}"
Margin="0,79,96,0"
Grid.Row="1"
TextWrapping="Wrap"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Width="245"/>
<Button
Content="Adicionar"
HorizontalAlignment="Right"
Margin="0,0,8,8"
Grid.Row="1"
VerticalAlignment="Bottom"
Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<!--Quando chamado o evento click, é executado o commando SubmitForm no ViewModel -->
<i:InvokeCommandAction Command="{Binding SubmitForm}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</UserControl>
E Finalmente temos a aplicação a funcionar com MVVM e nenhum código na View a não ser a definição do User Interface.

Solução VS2010 do projecto executado neste tutorial disponível aqui para download.
Referências:
http://www.silverlight.net/learn/videos/silverlight-4-videos/mvvm-introduction/
http://www.silverlight.net/learn/tutorials/silverlight-4/using-the-mvvm-pattern-in-silverlight-applications/
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090016