MVVM Starter Pack – Iniciando no MVVM – C#

Introdução

Fala pessoal, tudo bom?

Nesse post irei falar um pouco sobre o padrão de projetos chamado MVVM (Model – View – ViewModel) usado principalmente em aplicações XAML (WPF, UWP, Xamarin e etc).

Saliento, que a intenção aqui não é desenvolver um curso, ou um material completo sobre o assunto. Existem inúmeras fontes sobre. Se trata de um manual básico para a implementação desse padrão para pessoas que já possuem um conhecimento, ainda que básico, sobre o XAML e programação em geral.

Embora o projeto de exemplo que iremos desenvolver e eu disponibilizarei ao final tenha sido escrito para o WPF (Windows Presentation Foundation), os conceitos, classes e técnicas que abordarei neste post são como se fossem um Starter Pack para o MVVM. Independente de quem seja você ou qual seja o segmento do sistema/software/aplicativo ou plataforma (WPF, UWP, Xamaring…) que você pretende desenvolver usando MVVM, você irá, com toda certeza, usar a grande maioria do conteúdo que vou apresentar.

Então, fique tranquilo and here we go!

Conceitos

Para começar, vamos tentar entender o porquê de implementar um padrão de projeto.

Os padrões de projetos ou Design Patterns, em inglês, foram criados (existem muitos) com o fim de solucionar problemas e situações comuns em cenários de desenvolvimento de software. Existem vários, que foram desenvolvidos e aprimorados com o passar do tempo para os mais variados cenários.

Como o meu objetivo é falar do MVVM apenas não irei focar no assunto como um todo e sobre os demais (e são muitos) padrões existentes.

Apenas, quero que você foque na parte de que são implementados para solucionar problemas e situações comuns em cenários de desenvolvimento de software.

E o que vêm a ser esses problemas? Bom. Os mais variados. Mas vamos ao MVVM!

O que vou ganhar implementando o MVVM?

Primeiramente, o MVVM promove a separação por completo da interface do usuário e a lógica de negócios (ou simplesmente, a lógica por trás do funcionamento do aplicativo).

Tal separação permite que você faça o desenvolvimento da Interface à parte da lógica, isso porque, em soluções que utilizam a linguagem XAML, as UIs são escritas em XAML enquanto o restante dos códigos é escrito em C#/VB. Falando assim, parece até que já seriam independentes ambas as partes, mas não são!

Você acabaria escrevendo rotinas no Code Behind (arquivo de código (C#/VB) referente a um XAML) que acabariam mesclando os dois lados da moeda.

A grande sacada do MVVM é implementar de forma consistente essa separação, assim, você consegue fazer modificações na UI do aplicativo sem precisar modificar o código em C#/VB e vice versa! Como você deve ter imaginado, isso facilita (e muito!) a manutenção dos códigos, a legibilidade (mas fáceis de serem entendidos), a reutilização dos mesmos e o trabalho em equipe (pois uma equipe de design pode trabalhar na UI enquanto outra equipe trabalha na lógica de negócios).

Mas o que é o MVVM, afinal?!

MVVM-01

MVVM, como dito anteriormente, é a sigla para Model – View – ViewModel.

A View representa a interface de usuário, a UI do aplicativo, escrita em XAML.

A ViewModel é uma classe que contém métodos, propriedades e comandos que alimentam a View e são linkados à mesma e se comunica com o(s) Model(s).

E Model é a representação dos dados com os quais se está trabalhando.

Ficou complicado de entender?! Eu sei, quando estamos acostumados a lidar com a codificação de maneira mais plana, fica complexo conseguir estabelecer e imaginar essas divisões que serão implementadas.

Então, vamos à explicação!

View

A View é um arquivo XAML como, por exemplo, uma Window, um UserControl, e etc!

Ela é apenas a representação gráfica, a interface de usuário, do seu aplicativo. O ideal é que não contenha nenhuma linha de código em C#/VB no code behind(arquivo de código referente a ela).

Assim, ela é um objeto independente.

Claro que, em alguns cenários, necessitamos implementar algum código no Code Behind da View, tal como um evento de um TextBox para impedir que o usuário digite letras ou números, ou implementar o fechamento de um diálogo. Não vejo problemas em situações como essas.

O importante, é que você não implemente nenhuma lógica de negócios na View.

A View, geralmente, é desenvolvida com o auxílio do Blend for Visual Studio, uma ferramenta que acompanha o Visual Studio e serve para efetuar as tarefas de design em XAML.

Resumindo: A View é a interface de usuário do aplicativo, sem nenhuma lógica.

Model

O Model é a implementação dos dados e das lógicas de negócios usadas para manipular e processar esses dados. É no Model que você acabará implementando grande parte das suas rotinas. Ele é, por assim dizer, o mais baixo nível.

ViewModel

A ViewModel é uma classe intermediária que fará a ponte entre a View e o Model. Ela fornece a lógica da View expondo Propriedades e Comandos que serão conectados à View usando os recursos de Data Binding do XAML.

Por exemplo, na ViewModel você poderá expor uma propriedade chamada PossoTrabalhar.  Na View você liga a propriedade IsEnabled dos seus controles à propriedade PossoTrabalhar. Assim, sempre que você detectar nas suas rotinas que o usuário pode interagir com os controles, clicar em um botão, por exemplo, basta definir a propriedade PossoTrabalhar para true que o controle será habilitado imediatamente! Magnífico, não?!

Já uma ação que deve ser executada por um botão, deverá ser definida na ViewModel como um Comando, um objeto que implemente a interface System.Windows.Input.ICommand. Os controles como os Buttons, dispõem de uma propriedade chamada Command. No caso, usando Data Binding, você deverá ligar essa propriedade ao Comando que você definiu na sua ViewModel. Assim, toda vez que o usuário clicar no Button, o comando que você definiu na ViewModel será invocado.

A ViewModel deverá ser declarada como sendo o Data Context da View.

Chega de conceitos! Hora de escrever códigos!!!

Eu preferi passar primeiramente o conceito geral para poder em seguida mostrar exemplos práticos.

Indo em direção à prática, o Model e a ViewModel são classes que devem implementar a interface System.ComponentModel.INotifyPropertyChanged.

Isso se deve ao fato de que você sempre deverá chamar o evento PropertyChanged dessa interface quando fizer qualquer alteração em qualquer propriedade do Model ou da ViewModel. Esse procedimento serve para manter a ViewModel informada sobre o que acontece no Model e também manter a View informada sobre o que acontece na ViewModel.

Por exemplo, é uma maneira da ViewModel dizer pra View: –Ei, eu carreguei novos itens para a coleção, você deve se atualizar e mostrar esses itens!

Ou seja, toda vez que algo é alterado na ViewModel ou no Model, esse evento será chamado como forma de manter as 3 partes (Model, View e ViewModel) sempre atualizadas e informadas sobre umas as outras).

Isso, aliás, é um requisito essencial do mecanismo de Data Binding do XAML. Qualquer classe para a qual você deseja se ligar (com data binding) deverá implementar INotifyPropertyChanged para que assim, quando alguma propriedade for alterada, essa alteração seja detectada pelo mecanismo de data binding.

Sendo assim, irei lhes mostrar agora, uma classe base para Models e ViewModels. É uma classe abstrata que implementa INotifyPropertyChanged e simplifica nosso trabalho:

    public abstract class ModelBase : System.ComponentModel.INotifyPropertyChanged
    {
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string Name)
        {
            PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(Name));
        }

        protected void SetField(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(field, value))
            {
                return;
            }
            else
            {
                field = value;
                OnPropertyChanged(propertyName);
            }
        }
    }

Como você pode ver, essa nossa classe base implementa corretamente a interface INotifyPropertyChanged e ainda, de quebra, nos fornece métodos que serão muitos úteis. Vamos a eles:

O método OnPropertyChanged(string) é bem óbvio. Ele irá chamar o evento PropertyChanged informando o nome da propriedade que sofreu alguma alteração. É de extrema importância informar o nome da propriedade que teve seu valor alterado!

Já o método SetField(T, T, string) é uma evolução que ocorreu na implementação do MVVM nos últimos anos. Ele serve para você alterar o valor da propriedade ao mesmo tempo que você chama o evento PropertyChanged e mais, sem a necessidade de informar o nome da propriedade nos seus códigos, já que o atributo [System.Runtime.CompilerServices.CallerMemberName] se encarrega de informar ao método o nome da propriedade que o invocou!

Tendo essa nossa base definida, um exemplo de um Model extremamente simples seria:

    public class Brasileiro : ModelBase
    {
        private string _Nome;
        private string _CPF;

        public string Nome
        {
            get => _Nome;
            set => SetField(ref _Nome, value);
        }

        public string CPF
        {
            get => _CPF;
            set
            {
                _CPF = value;
                OnPropertyChanged("CPF");
            }
        }

        public override string ToString()
        {
            return Nome + ": " + CPF;
        }
    }

Observe bem o Model que implementa a classe ModelBase. Demonstrei como fica o código usando o método SetField e a maneira “antiga” de se fazer. Veja que usando o nosso método SetField, o código se torna mais limpo e simples!

Esse é um exemplo extremamente simples de um Model. A princípio, ele poderia conter métodos para trabalhar com os dados. Também poderia conter mais propriedades e variáveis e etc. O importante aqui, é você visualizar o conceito.

Agora que temos o Model, iremos implementar a ViewModel!

Mas para tal, é necessário fazermos algumas definições básicas. Precisamos criar um Comando personalizado que herde de ICommand. O que eu mais utilizo é o RelayCommand. Vamos codar nosso RelayCommand então:

    public class RelayCommand : ICommand
    {
        private Action<object> _Action;
        private Predicate<object> _CanExecutePredicate;

        public Action<object> Action { get { return _Action; } set { _Action = value; } }
        public Predicate<object> CanExecutePredicate { get { return _CanExecutePredicate; } set { _CanExecutePredicate = value; } }

        public RelayCommand(Action<object> Action, Predicate<object> CanExecutePredicate = null)
        {
            _Action = Action;
            _CanExecutePredicate = CanExecutePredicate;
        }

        public bool CanExecute(object parameter)
        {
            if (_Action == null) return false;
            if (CanExecutePredicate != null)
                return CanExecutePredicate.Invoke(parameter);
            else
                return true;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _Action?.Invoke(parameter);
        }
    }

Eis aí nosso RelayCommand, bem simples. Não irei me aprofundar muito sobre o mundo dos Comandos no XAML. Apenas gostaria de salientar algumas coisas.

Eu linkei o evento CanExecuteChanged do nosso RelayCommand com o evento RequerySuggested da classe CommandManager (Uma classe que há no WPF…).

Vale citar que a classe CommandManager existe apenas no WPF. Ou seja, se você quiser usá-la em outra platafoma (UWP, Xamarin.Forms…) vai ter que escrever uma similar. Já tendo me deparado com este problema, eu desenvolvi uma solução para isso. Se trata de uma API multi-plataforma para facilitar a implementação do padrão MVVM em qualquer plataforma. No final do post eu deixarei o link para você conferir.

Acontece que toda vez que o CommandManager acha que deve revalidar os comandos, ele chama esse evento. Mas, revalidar pra quê?

Simples, quando o evento CanExecuteChanged é chamado, o XAML invoca o método CanExecute(object) para verificar se é possível executar o comando. Se o nosso método CanExecute retornar false, o controle (um Button, por exemplo) que tiver a propriedade Command linkada para esse nosso RelayCommand será desativado. Se CanExecute retornar true, ele será então reativado.

Dessa forma, não precisamos nos preocupar em ativar/desativar um controle que esteja linkado a esse nosso comando.

A verificação ocorre por meio de um Predicate que é informado, ou não, no construtor do RelayCommand.

O que nosso RelayCommand irá fazer, basicamente, é invocar o Action que foi informado no construtor.

Yep, eu sei. Isso ta ficando complexo né. Mas relaxa!

Agora que nossa classe RelayCommand está pronta, podemos partir para a ViewModel

Se segura filhão, que agora as coisas vão ficar mais interessantes.

A ViewModel poderia, a princípio, herdar da classe ModelBase. Masss, nos meus projetos, eu tenho feito de uma maneira ligeiramente diferente. Tenho criado uma classe base especialmente para as ViewModels para nos ajudar a poupar código na declaração dos Comandos. Essa classe base é a seguinte:

    public abstract class ViewModelBase : ModelBase
    {

        public ViewModelBase()
        {
            _Commands = new Dictionary<string, icommand="">(5);
        }
        private Dictionary<string, icommand=""> _Commands;

        protected ICommand GetCommand(ICommand Command, [System.Runtime.CompilerServices.CallerMemberName]string Name = null)
        {
            begin:
            if (_Commands.TryGetValue(Name, out ICommand cmd))
                return cmd;
            else
            {
                _Commands.Add(Name, Command);
                goto begin;
            }
        }

        protected RelayCommand GetCommand(RelayCommand Command, [System.Runtime.CompilerServices.CallerMemberName]string Name = null)
        {
            begin:
            if (_Commands.TryGetValue(Name, out ICommand cmd))
                return cmd as RelayCommand;
            else
            {
                _Commands.Add(Name, Command);
                goto begin;
            }
        }

        protected ICommand GetCommand(string Name)
        {
            if (_Commands.TryGetValue(Name, out ICommand cmd))
                return cmd;
            else
                return null;
        }

        protected void SetCommand(ICommand Command, [System.Runtime.CompilerServices.CallerMemberName]string Name = null)
        {
            if (Command == null || string.IsNullOrWhiteSpace(Name)) return;
            if (_Commands.ContainsKey(Name))
            {
                _Commands[Name] = Command;
            }
            else
            {
                _Commands.Add(Name, Command);
            }
        }
    }

Em uma analogia bem simples, essa classe nos ajuda a poupar código, da mesma maneira que o método SetField faz na ModelBase.

Basicamente, o método GetCommand() serve para retornar um comando existente através do nome e se ele ainda não existir, cria-o e o retorna. É bem cômodo isso.

Agora que apresentei a base para a ViewModel, irei apresentar uma ViewModel:

    public class MainWindowViewModel : ViewModelBase
    {
        private ObservableCollection<Brasileiro> _Brasileiros = new ObservableCollection<Brasileiro>();

        private string _InformedName;
        private string _InformedCPF;

        public string InformedName { get => _InformedName; set => SetField(ref _InformedName, value); }
        public string InformedCPF { get => _InformedCPF; set => SetField(ref _InformedCPF, value); }
        public ObservableCollection<Brasileiro> Brasileiros => _Brasileiros;

        public RelayCommand AdicionarBrasileiro =>
            GetCommand(new RelayCommand(new Action<object>((param) =>
            {
                Brasileiro br = new Brasileiro();
                br.Nome = InformedName;
                br.CPF = InformedCPF;
                _Brasileiros.Add(br);
            }), new Predicate<object>((param) =>
            {
                return !string.IsNullOrWhiteSpace(InformedCPF) & !string.IsNullOrWhiteSpace(InformedName);
            })));
    }

Veja como é simples declarar um comando. Tendo feito todos os preparativos, nós conseguimos economizar bastante tempo e código. A classe ViewModel base se preocupa de gravar os comandos para nós. Nós apenas precisamos declara-los. Note que eu uso muito expressões lambda e métodos anônimos.

Declarei o nosso comando informado um Action e um Predicate. O Action irá criar e adicionar um novo objeto do tipo brasileiro na coleção usando os dados informados pelo usuário em dois TextBoxs.

O Predicate serve para decidir se é possível criar o objeto com os dados que o usuário inseriu (checando se o usuário inseriu alguma coisa, os as caixas de texto estão vazias).

O resultado disso é que o botão Adicionar Brasileiro ficará desabilitado caso o usuário não informe um Nome e um CPF. Bacana né?!

Note que eu usei uma coleção do tipo ObservableCollection. Isso se deve ao fato de ela ser uma coleção que implementa aquela interface, a INotifyPropertyChanged. Ela é a coleção ideal para ser usada no MVVM pois ela automaticamente notifica o sistema de Data Binding toda vez que algum item é adicionado/removido ou modificado nela. Ou seja, para Data Binding, ObservableCollection é o necessário!

InformedName e InformedCPF são propriedades que serão ligadas à dois TextBoxs em nossa View. O valor delas será, automaticamente, o texto atual da propriedade Text desses TextBoxs.

Tendo dito isso, creio que podemos partir para a nossa View!

Segue abaixo um exemplo de uma View que exibe os dados e interage com a ViewModel que nós acabamos de escrever:

<Window x:Class="WpfApp1.MainWindow"
        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:local="clr-namespace:WpfApp1"
        xmlns:mvvm="clr-namespace:WpfApp1.MVVM"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="336.174">
    <Window.DataContext>
        <mvvm:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <ListBox Height="188" Margin="10,123,10.4,0" VerticalAlignment="Top" ItemsSource="{Binding Brasileiros}"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="65,33,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding InformedName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBox HorizontalAlignment="Left" Height="23" Margin="65,61,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding InformedCPF, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBlock HorizontalAlignment="Left" Margin="26,36,0,0" TextWrapping="Wrap" Text="NOME" VerticalAlignment="Top"/>
        <TextBlock HorizontalAlignment="Left" Margin="26,65,0,0" TextWrapping="Wrap" Text="CPF" VerticalAlignment="Top"/>
        <Button Content="Adicionar" HorizontalAlignment="Left" Margin="199,33,0,0" VerticalAlignment="Top" Width="105" Height="51" Command="{Binding AdicionarBrasileiro, Mode=OneWay}"/>
    </Grid>
</Window>

Por fim, temos aí o código da nossa View. Note que, eu não escrevi nenhuma única linha de código em C#/VB para a View. Usei apenas o XAML da mesma.

Observe bem como eu usei os recursos de Data Binding e linkei a propriedade Command do Button à propriedade AdicionarBrasileiro da nossa ViewModel.

Veja como linkei os TextBoxs às propriedades InformedName e InformedCPF.

Lembrando que eu instanciei ali a nossa ViewModel na propriedade DataContext da janela. Isso é essencial.

Note, também, que eu liguei a propriedade ItemsSource do controle ListBox à nossa ObservableCollection Brasileiros de nossa ViewModel. Dessa forma, o ListBox irá apresentar de forma atualizada todos os itens da coleção.

Fim do Conteúdo

Finalmente, depois de muita explicação, implementamos um pequeno aplicativo em WPF utilizando o padrão MVVM!

Baixe esse projeto que nós desenvolvemos ao longo do post no link abaixo:

MVVM Sample Code.zip

Conclusão e Considerações Finais

Dessa maneira, poderíamos, por exemplo, refazer toda a nossa View a qualquer momento sem nos preocuparmos com o restante do código. Assim como, também, poderíamos refazer as nossas rotinas sem ter que reescrever a UI.

Também, fica bem mais simples de entender o projeto. E, conforme ele vai aumentando e se tornando grande, com o auxílio do MVVM, fica mais simples de mantê-lo e entendê-lo.

Claro, no começo, você irá perder um pouco de tempo efetuando toda essa implementação. Mas conforme o desenvolvimento avança, você verá quão maravilhoso é trabalhar com este padrão.

O ideal, é que você pegue todas essas classes bases, o RelayCommand e qualquer outra classe do gênero que você tenha e a compile numa API de base. Assim, para cada projeto que você precisar escrever e quiser usar o padrão MVVM, basta adicionar uma referência para essa API e começar a codar o seu projeto.

Existem alguns Frameworks de MVVM disponíveis na internet. Alguns muito bons. Mas eu sou aquele cara do faça você mesmo, e sempre uso minhas próprias classes.

Inclusive, tenho meu próprio framework básico de MVVM que é multi plataforma. Nele você encontra todas essas classes e mais algumas. Tem também um RelayCommand assíncrono, uma ThreadSafeObservableCollection que é para trabalhar em cenários multitarefas. Também possui uma classe CommandManager multiplataforma.

É um framework escrito para o .NET CORE 2. Você pode acessá-lo e baixá-lo através do GitHub no link abaixo:

GitHub – HerbertLausmann/HL.MVVM: Minimalist cross platform MVVM framework

E assim chegamos ao final deste post. Se tornaria impossível escrever um post detalhado sobre essa assunto, já que ele aborda muitos aspectos e temas diferentes. Se eu fosse descrever detalhe a detalhe, poderia escrever um livro.

Volto a salientar que a intenção aqui é apresentar os principais conceitos sobre o assunto e trazer para todos as bases necessárias para começar no MVVM.

Espero que esse post possa lhe ter ajudado! Um grande abraço!

Hasta la vista!

Anúncios

Deixe um comentário :)

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s