Introdução ao MVVM (Model-View-ViewModel) — Parte 2 — View
Dando continuidade nessa série de artigos sobre MVVM, vou prosseguir descrevendo o funcionamento da View.
Antes, gostaria de esclarecer que apesar de o MVVM ser utilizado em vários cenários. Este artigo vai focar em 3 cenários semelhantes: UWP (Windows 10), WPF e Xamarin.Forms. É na View onde acontece a maior diferenciação relacionado ao MVVM, quando comparado outros cenários. Portanto as partes: Xamarin.iOS, Xamarin.Android e Web (MVC, Angular e knockout.js) fica de fora, por enquanto.
Para facilitar o entendimento vou demonstrar alguns exemplos, escritos em C# com WPF.
WPF, é um acrônimo para o Windows Presentation Foundation, é uma abordagem da Microsoft para desenvolver GUI (Graphical User Interface) em desktops, usado com o .NET framework.
A tecnologia usada em outros ambientes mais recentes, tais como: Windows Phone 7/8/8.1, Windows 7/8/8.1, UWP (Windows 10) e Xamarin.Forms tem como base o WPF/XAML.
A maioria dos conceitos que aprenderemos ao usar WPF, serão utilizados também nesses outros ambientes.
Ao criar uma View (tela, pagina, etc…) utilizamos uma notação baseada em XML, chamada de XAML (cuja pronuncia é Zâmel), para definição dos componentes das telas. XAML é o acrônimo de eXtensible Application Markup Language.
Em XAML os componentes (elementos, se compararmos com XML) são definidos entre <>, ficando assim: <componente /> ou <componente> <componente/>.
[sourcecode language=”csharp”]
<Button Content=”Enviar” />
<Label Content=”Cupom” />
<TextBox />
// Mesmas instruções, com escrita um pouco diferente:
<Button>
Content=”Enviar”
<Button/>
<Label
Content=”Cupom”
<Label/>
<TextBox><TextBox/>
[/sourcecode]
No primeiro, definimos um botão com o texto: Enviar
No segundo um Label com o texto: Cupom
No terceiro uma caixa de entrada de texto, sem nenhum conteúdo.
Data Binding
Vamos entender um pouco mais sobre binding, focando principalmente em dois blocos que fazem parte do MVVM: A View e a ViewModel.
No artigo anterior, escrevemos que Data Binding é o mecanismo de comunicação entre uma View e uma ViewModel, e que bloco responsável pelo binding é a View.
Escrevemos várias vezes a palavra binding, mas o que vem ser isso?
Binding é um vínculo entre as propriedades de dois ou mais objetos. Temos uma propriedade de um objeto atuando como fonte e uma (ou mais) propriedades de um (ou mais) objeto(s) atuando como destino.
Binding basicamente copia o valor da propriedade fonte para a propriedade destino. Existe uma definição no binding, em que, a fonte pode ser alterada quando o destino modifica o valor. Havendo troca de valores nos dois sentidos.
Parece confuso! Mas não é tão complicado assim…
Modos de Binding
Os modos de binding, Binding Modes, definem a direção do fluxo dos dados. Normalmente o fluxo de dados é da fonte para o destino. Na lista abaixo vemos os 4 principais modos de binding.
- Default: O sentido do fluxo de informações é dependente do componente. Label: OneWay, TextBox: TwoWay (WPF).
- OneWay: Único sentido, alterações do conteúdo da fonte são refletidas para o destino.
- OneTime: Semelhante ao OneWay, mas o fluxo do dado ocorre apenas uma vez na inicialização do componente. O conteúdo do destino, não é mais alterado mesmo que ocorram alterações na propriedade fonte.
- TwoWay: Nesse modo de binding toda alteração realizada em um lado, origem ou destino, é refletida no outro.
UpdateSourceTrigger
No binding a atualização do conteúdo do destino ocorre automaticamente, imediatamente, após a mudança do valor da propriedade de origem.
No Binding que ocorre em direção reversa (TwoWay binding), destino para fonte, por padrão isso não acontece automaticamente. A propriedade UpdateSourceTrigger é utilizada para definir quando que a fonte, tem seu conteúdo atualizada com uma mudança no destino. Valores usados em UpdateSourceTrigger:
- Default: O tipo de atualização é dependente do componente, na maioria dos casos este é o mesmo que PropertyChanged.
- Explicit: O binding não ocorre até UpdateSource() ser chamado.
- LostFocus: A fonte é atualizada quando o componente, perde o foco. É o padrão (default) para o TextBox.
- PropertyChanged: A fonte é atualizada imediatamente à mudança do valor.
Definição do Binding
A sintaxe para definição do binding em XAML, é a seguinte:
[sourcecode language=”csharp”]
<Componente PropriedadeDestino=”{Binding ObjetoFonte, Path=PropriedadeFonte, Mode=TwoWay, UpdateSourceTrigger=LostFocus}”/>
Ou
<Componente PropriedadeDestino=”{Binding PropriedadeFonte}”/>
[/sourcecode]
Esses comandos definem o vínculo entre a fonte e o destino.
Quando Mode ou UpdateSourceTrigger, não são especificados considera-se a opção Default.
No segundo caso, utilizou-se somente o nome da propriedade fonte, nesse caso o objeto utilizado como origem (ObjetoFonte) é o DataContext, ou no caso de Xamarin.Forms o BindingContext. Vamos ver mais a frente sobre esses objetos.
Binding entre componentes
Tudo é objeto, em programação orientada à objetos, mas eu vou chamar de componentes aqueles objetos visuais que aparecem na tela e de objetos, os dados que vem de uma ViewModel.
Vamos ver um exemplo de binding entre componentes. A propriedade de um componente atua como fonte enquanto a propriedade de outro componente atua como destino.
[sourcecode language=”csharp”]
<Slider Name=”FontSizeSlider” Minimum=”5" Maximum=”100" Value=”14"/>
[/sourcecode]
No código acima definimos um Slider, cujo nome é: FontSizeSlider. Esse nome é usado como uma referência a esse componente, algo como um nome de uma variável.
[sourcecode language=”csharp”]
<TextBox Name=”SizeTextBox”
Text=”{Binding ElementName=FontSizeSlider, Path=Value}”/>
[/sourcecode]
No código acima definimos um TextBox, cujo nome é SizeTextBox, e a propriedade Text deste componente está vinculada, como sendo o destino, da propriedade Value do componente FontSizeSlider (que atua como fonte). Toda vez que houver uma alteração no conteúdo de Value (Slider) o texto dentro do TextBox é atualizado com esse valor. Nesse caso também acontece o inverso (TwoWay Binding), ou seja, o texto mostrado no TextBox está vinculado ao valor do Slider. Quando houver alteração em um deles esta alteração passa a ser refletida no outro. Mas essa alteração de Value (Slider) somente acontece quando o TextBox perder o foco, depois de pressionar TAB ou clicar em outro controle. Devido ao UpdateSourceTrigger ser LostFocus.
Binding com objetos
As informações que mostramos na tela para o usuário, vem obviamente de algum lugar.
A ViewModel é quem tem a responsabilidade de fornecer essas informações à View. O lugar (propriedade) onde o binding da View procura por essas informações é o DataContext (ou BindingContext, no Xamarin.Forms).
O DataContext pode ser definido tanto em XAML como em código, Code-behind.
A View, falando em termos de programação, é composta de dois arquivos: NomeDoArquivo.xaml e NomeDoArquivo.xaml.cs o primeiro tem as definições escritas em XAML e o segundo em C#, também chamado de Code-behind, tem algumas informações adicionais. O Code-behind quase sempre é pouco usado, pois definimos os componentes visuais através de XAML e deixamos para executar as ações através da ViewModel. Somente coisas específicas à View devem ser escritas no Code-behind.
Como ainda não falei em detalhes sobre a ViewModel, todo o código que irei usar agora, vai estar no Code-behind. Mas normalmente este código estaria na ViewModel, o importante neste artigo é entender o binding na parte de XAML.
[sourcecode language=”csharp”]
class Pessoa
{
public string Nome { get; set; }
public string Sobrenome { get; set; }
}
[/sourcecode]
Acima definimos uma classe simples, Pessoa, que tem apenas duas propriedades: Nome e Sobrenome.
Vamos instanciar um objeto do tipo pessoa, e definir no Code-behind o DataContext com esse objeto.
[sourcecode language=”csharp”]
public partial class ObjetoBindingWindow : Window
{
public ObjetoBindingWindow()
{
InitializeComponent();
Pessoa eu = new Pessoa()
{
Nome = “Valério”,
Sobrenome = “Ferreira”
};
DataContext = eu;
}
}
[/sourcecode]
Na View em XAML, vamos fazer o binding com esse objeto, através do DataContext.
[sourcecode language=”csharp”]
<TextBox Text=”{Binding Nome}” />
<TextBox Text=”{Binding Sobrenome}” />
[/sourcecode]
Bem simples. Através do DataContext, que neste caso é implícito, um TextBox vai acessar o conteúdo do nome do objeto Pessoa e o outro TextBox vai acessar o conteúdo do sobrenome do objeto Pessoa.
Binding com coleções
As vezes temos uma lista de objetos a ser mostrada ao usuário.
Nesse caso o binding também é bem simples.
[sourcecode language=”csharp”]
class Estado
{
public string[] Municipios { get; set; }
}
[/sourcecode]
Acima definimos uma classe simples, Estado, que tem apenas um vetor de municípios.
Vamos instanciar um objeto do tipo Estado, e definir no Code-behind o DataContext com esse objeto.
[sourcecode language=”csharp”]
public partial class ColecaoBindingWindow : Window
{
public ColecaoBindingWindow()
{
InitializeComponent();
Estado sp = new Estado()
{
Municipios = new[] {“Santo André”, “São Bernardo do Campo”, “São Caetano do Sul”, “São Paulo”}
};
DataContext = sp;
}
}
[/sourcecode]
Na View em XAML, vamos fazer o binding com esse objeto, através do DataContext.
[sourcecode language=”csharp”]
<ComboBox ItemsSource=”{Binding Municipios}” />
[/sourcecode]
Através do DataContext, que neste caso é implícito, o ComboBox vai mostrar uma lista de municípios.
O binding de coleções normalmente é mais complicado, do que o exposto acima. Pois em vez de termos um vetor de string, normalmente temos uma lista de objetos.
Veja como seria o binding com uma lista de pessoas.
[sourcecode language=”csharp”]
<ComboBox ItemsSource=”{Binding Pessoas}” DisplayMemberPath=”Nome” SelectedValuePath=”NomeID” />
[/sourcecode]
Nesse caso precisamos definir qual a propriedade do objeto Pessoa queremos exibir (DisplayMemberPath) e também um código para o item selecionado (SelectedValuePath).
Binding de comandos
Ainda existe um tipo especial de objeto usado no binding, é um objeto que implementa a interface ICommand.
Essa interface define ações que serão executas e quando podem ser executadas.
Um caso comum é o click de um botão, nesse caso o “command” tem a definição do que deve ser executado ao clicar no botão e também uma definição, se o botão está habilitado/desabilitado.
[sourcecode language=”csharp”]
<Button Content=”Salvar” Command=”FazAlgumaCoisa” />
[/sourcecode]
Em termos de binding é bem simples, toda a complexidade fica na ViewModel, que fornece à View esses comandos.
Por enquanto é isso pessoal, no próximo artigo entraremos em detalhe sobre a ViewModel.
O código fonte dos exemplos está disponível em: https://github.com/Sylix/CursoMVVM