Aplicando MVVM

15
Aplicando MVVM Separe a UI das regras de negócio com WPF Paulo Quicoli Editor técnico das revistas ClubeDelphi, .NET Magazine e WebMobile Magazine. Formado em processamento de dados pela FATEC-TQ. Atua como arquiteto de projetos .NET na Control-M Informática (www.controlm.com.br).Blog : www.quicoli.wordpress.com De que se trata o artigo A separação de camadas em um aplicativo sempre foi um desafio, principalmente quando se trata de separar a camada de interface do usuário do resto do aplicativo. Para aplicativos WPF e Silverlight foi desenvolvimento do padrão MVVM que vem resolver o problema de forma eficiente. Para que serve Isolar a interface, removendo dela toda lógica de negócio, deixando apenas código referente apresentação de informação, e somente quando for necessário. O MVVM se utiliza de recursos do framework para realizar esse feito da melhor forma possível. Em que situação o tema é útil No desenvolvimento organizado de software, onde cada funcionalidade está bem encaixada e separada em camadas que permitem um sistema mais testável e fácil de manter. Resumo Devman Este artigo apresenta o padrão MVVM e como ele utiliza os recursos de DataBinding e Commanding do WPF/Silverlight para conseguir deixar a camada de interface (UI) sem qualquer código de regra de negócio em seu code-behind, além de apresentar o MVVM Light Toolkit, um framework que facilita a utilização do padrão em aplicações. Seu uso é demonstrado em um aplicativo de prova de conceito que explora DataBinding, Commands e lógica de apresentação. Sempre que uma nova tecnologia é apresentada, surgem dúvidas sobre sua utilização, seus limites, sua aplicação. Para responder essas questões existem vários livros técnicos, material até mesmo originado pela empresa que lançou a tecnologia. Contudo existem alguns assuntos que nem sempre são tratados. Por exemplo, as boas práticas que envolvem a tecnologia. Existe uma boa prática que é comum à grande maioria de tipo de software: separar a interface de usuário da lógica de negócio. Para entender melhor o que é isto, é preciso explicitar as camadas nas quais um software pode ser dividido, como mostra a Figura 1 .

Transcript of Aplicando MVVM

Page 1: Aplicando MVVM

Aplicando MVVMSepare a UI das regras de negócio com WPF

Paulo QuicoliEditor técnico das revistas ClubeDelphi, .NET Magazine e WebMobile Magazine. Formado em processamento de dados pela FATEC-TQ. Atua como arquiteto de projetos .NET na Control-M Informática (www.controlm.com.br).Blog: www.quicoli.wordpress.com

De que se trata o artigo

A separação de camadas em um aplicativo sempre foi um desafio, principalmente quando se trata de separar a camada de interface do usuário do resto do aplicativo. Para aplicativos WPF e Silverlight foi desenvolvimento do padrão MVVM que vem resolver o problema de forma eficiente.

Para que serve

Isolar a interface, removendo dela toda lógica de negócio, deixando apenas código referente apresentação de informação, e somente quando for necessário. O MVVM se utiliza de recursos do framework para realizar esse feito da melhor forma possível.

Em que situação o tema é útil

No desenvolvimento organizado de software, onde cada funcionalidade está bem encaixada e separada em camadas que permitem um sistema mais testável e fácil de manter.

Resumo Devman

Este artigo apresenta o padrão MVVM e como ele utiliza os recursos de DataBinding e Commanding do WPF/Silverlight para conseguir deixar a camada de interface (UI) sem qualquer código de regra de negócio em seu code-behind, além de apresentar o MVVM Light Toolkit, um framework que facilita a utilização do padrão em aplicações. Seu uso é demonstrado em um aplicativo de prova de conceito que explora DataBinding, Commands e lógica de apresentação.

Sempre que uma nova tecnologia é apresentada, surgem dúvidas sobre sua utilização, seus limites, sua aplicação. Para responder essas questões existem vários livros técnicos, material até mesmo originado pela empresa que lançou a tecnologia. Contudo existem alguns assuntos que nem sempre são tratados. Por exemplo, as boas práticas que envolvem a tecnologia. Existe uma boa prática que é comum à grande maioria de tipo de software: separar a interface de usuário da lógica de negócio. Para entender melhor o que é isto, é preciso explicitar as camadas nas quais um software pode ser dividido, como mostra a Figura 1 .

Page 2: Aplicando MVVM

Os usuários de um sistema interagem com o mesmo pela camada de apresentação (presentation layer, UI), nela estão os controles e componentes visuais necessários para apresentação das informações e solicitação de informações. Essa camada por sua vez se comunica com a de serviço.

A camada de serviço centraliza, simplifica para a UI as rotinas através da criação de um API que abstraia um processo maior que envolva outras ações. Por exemplo ao imaginar um cadastro de pessoas. Haveria uma interface para esse cadastro e também haveria um serviço para cadastrar essas pessoas, algo como uma classe ServicoPessoa que conteria métodos como ValidarPessoa, SalvarPessoa e assim por diante. Esses métodos conteriam código que com certeza acessaria camadas inferiores. Assim, a interface acessando apenas os serviços, a implementação desses serviços pode mudar sem necessariamente exigir mudança na UI. Como foi dito, a camada de serviços acessa camadas inferiores, como pode exemplo a camada de negócio (Business Layer).

Nessa camada estão localizadas classes que representam o negócio em si, como entidades, validações específicas e outras que se fazerem necessárias. Então ainda existe a camada de acesso a dados (Data Layer) que contém toda estrutura para persistências das entidades de negócio.

Existem outros conceitos que podem atravessar todas as camadas, são os chamados cross-cuting. São funcionalidades que podem estar em qualquer camada, funcionalidades essas como Log e Segurança.

É muito importante que o código dessas camadas não se misture. A relação de comunicação entre elas se dá sempre de cima para baixo, ou seja: a UI possui acesso à camada de serviços, mas a camada de serviços não deve possuir acesso direto à camada de UI.

Qual a vantagem em separar a camada de interface?

Como a lógica da interface não está codificada e amarrada nos controles visuais utilizados, pode-se facilmente trocar esses controles sem influenciar o funcionamento de uma tela ou página por exemplo. Outro ponto favorável é a possibilidade de se testar a aplicação de forma automatizada. Ao fazer a separação é possível testar o comportamento da UI sem ter a necessidade de executar o software propriamente dito. Mas, existe um porém. Ao separar de forma correta não só a camada de interface, mas todas as camadas lógicas necessárias, há um aumento na complexidade do código, o que pode ser um problema para o entendimento do todo por parte de desenvolvedores menos experientes.

Page 3: Aplicando MVVM

Padrões

Para alcançar essa separação vários estudos ao longo dos anos foram feitos. Esses estudos e implementações levaram à criação dos chamados Padrões de Projeto.

Um padrão de projeto é uma solução para um problema recorrente. A separação da camada de interface é um problema recorrente em todo software. Assim, foram desenvolvidos alguns padrões que cuidam disso, os mais conhecidos são: Model-View-Controller (MVC) e Model-View-Presenter (MVP). Esses são padrões bem genéricos, que podem ser implementados em qualquer tecnologia, já que um padrão não define código e sim, a forma como solucionar o problema.

O WPF surgiu como base para desenvolvimento de aplicativos Windows e que gradativamente vem substituindo a plataforma anterior, o Windows Forms. Com o WPF novos recursos foram criados, de tal forma que eles poderiam ser utilizados para facilitar a separação de código. Assim surgiu o padrão Model-View-ViewModel ou simplesmente MVVM, que nada mais é do que um padrão de separação de interface que faz uso dos recursos do WPF e mais recentemente do Silverlight.

Figura 2. MVVM

O MVVM

Ele foi desenvolvido por John Gossman, que em 2005 era o arquiteto do WPF na Microsoft. A Figura2 apresenta a estrutura básica do padrão.

O padrão define três figuras bem distintas, que compõem o seu próprio nome. A View representa a janela onde será exibida informação. Mas não apenas uma janela, o conceito aqui é mais abrangente. Uma view pode ser constituída de um conjunto de controles visuais ou, ser apenas um controle. O ViewModel nada mais é que uma classe que contém as ações e dados que uma View pode tomar e exibir, encapsulando sua lógica.

Ele é responsável em preparar o Model para ser exibido pela View. Model por sua vez são as classes de negócio envolvidas mais o que for necessário para a lógica do negócio.

Comunicação das partes

Como é mostrado pela Figura 2 as partes que compõem o MVVM comunicam-se entre si utilizando recursos do framework WPF. O View Model obtém os dados do Model e notifica a View em eventuais mudanças. A View por sua vez acessa os dados expostos pelo ViewModel através do recurso de Binding. As ações que são tomadas, por exemplo, o que fazer no click de um botão estão encapsuladas no ViewModel em objetos classificados de Commands, ou comandos, que por sua vez estão também ligados à View por Binding.

Page 4: Aplicando MVVM

Binding

O Databinding, ligação de dados, no WPF é muito superior em relação ao já existente no Windows Forms. Basicamente em uma operação de databinding existem duas partes, o target e o source (Figura 3)

Figura 3. Databing

Para ligar a propriedade Idade de um objeto Pessoa à propriedade Text de um Textbox, esse objeto é o Source e o TextBox é o Target. Em XAML isso ficaria assim:

<TextBox Text="{Binding Path=objPessoa.Idade}"/>

Nota: Não é o objetivo deste artigo abranger a sintaxe XAML e sua estrutura

É possível ainda determinar o fluxo dos dados, ou seja, quem atualiza o que. Por exemplo, se apenas o source é que envia dados para o target, ou se apenas o target envia ou ambos trocam informações. Com esse comportamento é possível determinar uma espécie de binding somente leitura, onde um textbox apenas apresenta os dados e não envia as alterações para o objeto source. Os fluxos possíveis são vistos na Figura 4.

Figura 4. Fluxos de databinding

No modo OnWay, somente o source atualiza o target. No TwoWay os dois trocam informações e no OneWayToSource, o target é que envia os dados para o source. Em XAML isso é especificado da seguinte forma:

<TextBox Text="{Binding Path=objPessoa.Idade, Mode=TwoWay}"/>

Nos modos em que o source pode ser atualizado (TwoWay e OneWayToSource), é possível ainda determinar quando essa atualização é feita (Figura 5).

Figura 5. Determinando a atualização

Isso é feito através da propriedade UpdateSourceTrigger. O valor padrão é LostFocus, onde a atualização ocorre quando o target perde o foco e seu evento LostFocus é disparado.

Page 5: Aplicando MVVM

O outro valor possível é o PropertyChanged, que atualiza o source sempre que a propriedade do target ligada ao binding sofre alguma alteração. Por fim o Explicit. Nesse caso o target só envia as atualizações para o source quando o desenvolvedor chamar o método UpdateSource do target. Determinar tudo isso é muito simples, como é mostrado a seguir:

<TextBox Text="{Binding Path=objPessoa.Idade, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

O Binding simplifica o processo de exibição dos dados, encapsulando grande parte do trabalho necessário. Sua única desvantagem está no fato de que na sua forma padrão, ele é resolvido em tempo de execução, ou seja, caso alguma propriedade esteja escrita de forma incorreta, a informação pode não aparecer visualmente. O ponto favorável disso é que a tarefa de construção das telas não está diretamente ligada com os dados a serem apresentados, um designer poderia construir a identidade gráfica do projeto e posteriormente o desenvolvedor apenas faria a ligação dos dados.

Commands

O que difere um comando de um simples evento adicionado a um botão é que os comandos separam a semântica do processo e separam quem executa um comando da lógica de sua ação.

Isso permite que diferentes fontes invoquem o mesmo comando. Isso é muito comum em vários sistemas, por exemplo pode-se disparar o comando Copiar do Bloco de Notas selecionando um texto e pressionando Ctrl+C ou, com o texto selecionado, simplesmente escolher do menu a opção Copiar. Os commands podem ainda ter sua execução habilitada ou não mediante alguma informação externa. Por exemplo, um comando copiar só poderia ser acionado quando um texto estiver selecionado. No WPF existe o CommandManager, onde ficam registrados os commands criados. Esse CommandManager tem o papel de verificar o estado de um command, se ele pode ser executado ou não. Se o comando Colar estiver ligado a um botão, e esse comando estiver desabilitado, o CommandManager identifica essa situação e desabilita também todos os controles ligados ao comando Colar.

Frameworks de apoio

Implementar a partir do nada o padrão MVVM não é difícil, mas é uma tarefa um pouco trabalhosa, por isso vários desenvolvedores criaram frameworks MVVM que possuem classes bases que contém grande parte do código que seria necessário implementar.

Além disso, possuem já soluções prontas para problemas que seriam enfrentados durante a aplicação do MVVM. Assim, eles estão disponíveis para auxiliar o uso do mesmo. É muito fácil confundir um framework MVVM com o padrão propriamente dito. Vale ressaltar aqui que o framework é apenas uma ferramenta facilitadora, que o importante é entender o que está envolvido no MVVM.

Assim, será apresentado a seguir o framework MVVM Light, que possui versões para WPF, Silverlight e Windows Phone.

Page 6: Aplicando MVVM

MVVM Light

O framework oferece algumas classes que facilitam o uso do MVVM, podendo citar dentre elas:

- ViewModelBase: Classe base para ViewModels. Implementa a interface - INotifyPropertyChanged. Esta é uma interface especial do framework .NET que é utilizada para avisar o mecanismo de binding de que determinada propriedade sofreu alteração e que isso precisa ser repassado.

- RelayCommand: São classes que implementam ICommand (interface que representa um comando, ação, para o WPF e Silverlight) de maneira que simplifica a criação de comandos.

- EventToCommand: É um behavior que permite a um evento executar um command, mantendo assim a separação das camadas.

Nota Devman 1: Um behavior nada mais é que uma class e que possui um comportamento específico, e esse comportamento é disparado quando um determinado evento de um controle sofre alteração. Existem os mais diversos behaviors. Por exemplo o TextBox padrão do WPF ao receber o foco não seleciona de forma automática seu conteúdo. Assim, esse comportamento pode ser adicionado através de um novo behavior, uma classe que é adicionada ao controle e que implementa o evento que detecta o foco do controle de tal forma que ao detectar esse foco, selecione o conteúdo do controle onde esse behavior foi adicionado

Implementando o MVVM com MVVM Light

Para demonstrar seu uso, uma aplicação WPF foi criada. O objetivo desta aplicação é mostrar de forma simples como utilizar o MVVM e separar a camada de apresentação de sua lógica. Assim, essa pequena aplicação é didática, nela um usuário pode informar o nome de diversas frutas e então adicioná-las a uma lista, que posteriormente permite a exclusão de seus itens. A Figura 6 mostra o layout principal.

Figura 6. Layout da aplicação

Page 7: Aplicando MVVM

É uma boa prática organizar o projeto em pastas, tendo por exemplo um pasta Views que conteria as views do projeto e outra ViewModels, que teriam seus correspondentes viewmodels. Nesse projeto, por ter apenas uma View, criou-se apenas a pasta ViewModels. Nela foi adicionada uma nova classe, de nome MainWindowViewModel. Ao projeto também foi adicionada uma referência ao assembly GalaSoft.MvvmLight.WPF4, que é a dll principal do MVVM Light.

Para tornar a classe recém adicionada em um view model é necessário fazê-la herdar de ViewModelBase.

Continuando com o raciocínio da aplicação, esse ViewModel precisa de uma propriedade que estará ligada à View e que será utilizada para conter o nome da fruta que o usuário poderá utilizar. Assim, a seguinte propriedade é implementada:

private string _fruta;public string Fruta{ get { return _fruta; } set { _fruta = value; RaisePropertyChanged("Fruta"); }}

Como mencionado anteriormente, a classe ViewModelBase implementar INotifyPropertyChanged para avisar o mecanismo de binding que uma determinada propriedade teve seu valor alterado. Essa interface foi implementada pelo framework, disponibilizando o método RaisePropertyChanged() para notificar qual foi a propriedade alterada. Por isso esse método é chamado no Set da propriedade e recebe como parâmetro o nome da propriedade.

Como o usuário poderá incluir frutas em uma lista, essa lista deve ser implementada. No código a seguir uma lista do tipo BindingList<T> é utilizada para representar a lista de frutas.

private BindingList<string> _listaFrutas;public BindingList<string> ListaFrutas{ get { return _listaFrutas; } set { _listaFrutas = value; RaisePropertyChanged("ListaFrutas"); }}

O tipo BindingList<T> foi utilizado por suportar, como seu próprio nome diz, operações de binding. É muito divulgado também o tipo ObservableCollection<T> que também é utilizada em operações de binding. A preferência pelo BindingList<T> se dá pelo fato de que ele implementa uma série de interfaces não implementadas pelo ObservableCollection, por exemplo a interface IBindingList, que permite ordenação e busca de itens.

Como o objetivo é separar interface de regra de negócio, para adicionar um item na lista será necessária a implementação de comandos (commands), e para a exclusão também.

Um comando é representado no ViewModel por um propriedade do tipo ICommand. Essa interface é que é esperada pelos controles visuais. O MVVM Light oferece uma implementação baseada no ICommand chamada RelayCommand. Assim, no construtor do ViewModel é necessário instanciar a propriedade que representa o comando passando um tipo RelayCommand que permite em seu construtor passar dois parâmetros, dois métodos. Um que é a ação que o comando faz e outro que é a verificação do comando, ou seja, quando o comando pode ser implementado. Assim, a Listagem 1 mostra essa implementação.

Page 8: Aplicando MVVM

Listagem 1. Comando para adicionar uma fruta

public ICommand CmdAdicionarFruta { get; set; }

private void DoAdicionarFruta(){ ListaFrutas.Add(Fruta); Fruta = "";}

private bool CanDoAdicionarFruta(){ return (!string.IsNullOr WhiteSpace(Fruta));}

O método DoAdicionarFruta() simplesmente adiciona o conteúdo da propriedade Fruta na propriedade ListaFrutas e na sequência limpa seu valor. Já o método CanDoAdicionarFruta() retorna um bool, os métodos que são utilizado para indicar se um comando pode ou não ser executado sempre devem retornar um bool.

No caso, o comando só poderá ser executado se a propriedade fruta tiver algum conteúdo válido, ou seja, se não estiver vazia. Isso porque não faz sentido inserir um texto vazio na lista de frutas. A amarração desses métodos com o comando é feita no construtor da classe, como mostra o código a seguir:

CmdAdicionarFruta = new RelayCommand(DoAdicionarFruta, CanDoAdicionarFruta);

Como RelayCommand implementa ICommand ele pode ser passado à propriedade CmdAdicionarFruta. Para a interface não existe RelayCommand, somente ICommand. A exclusão de uma fruta da lista também é feita de forma semelhante só que alguns cuidados a mais.

A exclusão de uma fruta só pode ser feita se houver um item selecionado na lista, sendo assim, é necessário também criar uma propriedade que representa um item selecionado na lista de frutas. Essa propriedade é mostrada no código seguinte:

private string _frutaSelecionada;public string FrutaSelecionada{ get { return _frutaSelecionada; } set { _frutaSelecionada = value;

RaisePropertyChanged("FrutaSelecionada"); }}

Agora é possível utilizá-la nos métodos que compõem o comando CmdExcluirFruta(), como mostra a Listagem 2. No construtor do ViewModel também deve ser instanciado o comando CmdExcluirFruta passando uma instância de RelayCommand, como feito para o comando anterior.

Listagem 2. Comando para excluir uma fruta

public ICommand CmdExcluirFruta { get; set; }

private void DoExcluirFruta(){ ListaFrutas.Remove(FrutaSelecionada);}

private bool CanDoExcluirFruta(){ return (FrutaSelecionada != null);}

Realizando o Binding

Page 9: Aplicando MVVM

Uma vez que se tenha o ViewModel é necessário ligá-lo a uma View. Isso é feito inicialmente através de Binding. Assim, a primeira ligação que deve ser feita é entre o TextBox de entrada de dados com a propriedade Fruta do ViewModel, como é mostrado a seguir:

<TextBox Width="200" Height="24" Text="{Binding Fruta, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

O modo TwoWay foi especificado porque no ViewModel, após a inclusão de uma fruta, essa propriedade é limpa, tem seu valor apagado. Para que a UI seja atualizada com um valor que foi alterado pelo código e não por ela mesma, o uso do TwoWay se faz necessário. O modo PropertyChanged de atualização também foi utilizado para mostrar uma maior responsividade da aplicação. Isso porque o comando que adiciona uma fruta só é executado quando a propriedade fruta possuir um valor válido, assim o usuário poderia digitar e não ver que o comando estaria pronto, ao utilizar o modo mencionado quando a propriedade possuir um valor válido, automaticamente o comando é habilitado e o usuário perceberá isso.

Para ligar o comando ao botão também é uma questão de binding:

<Button Content="Adicionar à Lista" Margin="3" Command="{Binding CmdAdicionarFruta}"/>

A propriedade Command do botão recebe a propriedade do ViewModel que é do tipo ICommand, mas que durante a execução se comportará como um RelayCommand. Os outros bindings são semelhantes e constam na Listagem 3.

<ListBox Margin="10" Height="200" ItemsSource="{Binding ListaFrutas}" SelectedItem="{Binding FrutaSelecionada,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>

<Button Content="Excluir da lista" Width="100" Command="{Binding CmdExcluirFruta}" />

O ListBox possui duas propriedades suas ligadas ao ViewModel, ItemsSource que é o conteúdo do ListBox está ligado com a lista do tipo BindingList<string> ListaFrutas e, SelectedItem que representa o elemento selecionado, foi ligado à propriedade FrutaSelecionada.

Para que tudo seja finalizado é necessário informar quem é o DataContext da MainWindow. Essa propriedade é o objeto que contém as informações que estão ligadas por binding, sendo assim, ela deve ser o ViewModel. O único código que existe na View está implementado no evento Loaded, mostrado a seguir:

DataContext = new MainWindowViewModel();

Ao passar uma instância de MainWindowViewModel ao DataContext da janela, automaticamente durante a execução, todas propriedades e comandos envolvidos por binding são encontrados e resolvidos.

Page 10: Aplicando MVVM

Conclusão

É fundamental organizar o código de tal maneira que seja fácil de manter e estender. O padrão apresentado pode ser utilizado em aplicações Silverlight e também em aplicações para Windows Phone.

Este foi apenas uma introdução no assunto. O framework MVVM Light possui mais recursos valiosos que poderiam ser explorados, como o EventToCommand, uma classe de troca de mensagens para desacoplar a criação de Views e ViewModels e muito mais. Fica o desafio a você leitor de experimentar e melhorar a qualidade de seu código. Abraço!

Links

MVVM Light ToolKit - Get Startedhttp://www.galasoft.ch/mvvm/getstarted/

The MVVM Pattern for WPFhttp://msdn.microsoft.com/en-us/magazine/dd419663.aspx

Blogs do autorhttp://quicoli.wordpress.com

http://twitter.com/pauloquicoli

Saiba Mais

Vídeo - Introdução ao WPF - Moacir Casemirohttp://www.devmedia.com.br/post-5095-Introducao-ao-WPF--Windows-Presentation-Foundation.html

Vídeo - Novidades do databinding no SilverLight 4 - Guinther Paulihttp://www.devmedia.com.br/post-18085-Novidades-do-DataBinding-Silverlight-4--Novidades-do-Visual-Studio-2010-Parte-29.html