André Alves de Lima

Talking about Software Development and more…

Exemplo de CRUD no WPF com MVVM

Sem dúvida nenhuma, a melhor funcionalidade do WPF é o seu mecanismo de data binding. Ele é muito mais robusto do que o seu antecessor (Windows Forms) e é uma das grandes vantagens do WPF quando comparamos essas duas plataformas. Porém, de nada adianta essa super funcionalidade se o seu projeto estiver mal estruturado. O padrão arquitetural MVVM (Model-View-ViewModel) ganhou força lá por volta de 2006 juntamente com o lançamento do WPF e o seu propósito é justamente resolver esse problema: ele define exatamente como devemos separar as camadas do nosso projeto.

Entretanto, o grande problema que encontramos quando optamos por utilizar o MVVM no nosso projeto é a curva de aprendizado. A estrutura do MVVM parece fácil na teoria, mas quando tentamos colocar em prática, muitos obstáculos acabam aparecendo no meio do caminho.

Muita gente que está começando agora no mundo do desenvolvimento em WPF (ou qualquer outra tecnologia baseada em XAML, como Xamarin Forms ou UWP) acaba tendo essa mesma dúvida: como é que eu consigo implementar um simples CRUD no WPF com MVVM? Como seria o exemplo mais enxuto possível? Pois é isso que eu vou apresentar para você no artigo de hoje!

Disclaimer

Antes de continuarmos, eu quero deixar claro que a minha experiência profissional com WPF não é lá tão grande. O conhecimento que eu tenho de WPF foi adquirido através de estudos, desenvolvimento de exemplos aqui para o site (que, a propósito, você encontra na categoria WPF) e com a gravação de vídeos para o MSDN. Além disso, eu desenvolvi também uma ou outra aplicação para a Windows Store (“Metro Style“) e Windows Phone em parceria com um colega de trabalho. Porém, a maior parte da minha carreira eu trabalhei (e continuo trabalhando) desenvolvendo aplicações Windows Forms. Apesar de eu utilizar também o padrão MVVM nas aplicações Windows Forms, obviamente não é a mesma coisa que utilizá-lo com o WPF, que é muito mais adequado para esse tipo de arquitetura.

Dito isso, pode ser que a estrutura que eu vá mostrar nesse artigo não seja a melhor de todas. O código que eu vou apresentar aqui é exatamente o código que eu utilizaria se tivesse que implementar um CRUD no WPF com MVVM sem utilizar nenhuma biblioteca extra. Obviamente, se eu fosse desenvolver uma aplicação “de verdade“, eu utilizaria algum framework MVVM para facilitar um pouco as coisas, mas no artigo eu evitei a utilização de bibliotecas externas para que os leitores possam compreender 100% do que está acontecendo por trás dos panos.

Se você for um desenvolvedor WPF mais experiente e encontrar alguma parte que possa ser melhorada ou alguma coisa que eu tenha feito de errado, por favor, deixe um comentário no final do artigo. Para conteúdos profissionais sobre tecnologias baseadas em XAML, recomendo o blog do Bruno Sonnino, que é referência dessas tecnologias no Brasil.

MVVM?

A ideia desse artigo não é se aprofundar nas teorias do MVVM, mas sim, demonstrar um exemplo prático de CRUD (manipulação de dados com operações de criação, edição e deleção) no WPF utilizando esse modelo arquitetural. O MVVM surgiu basicamente com o intuito de separarmos a interface do código de negócios. Nesse padrão arquitetural, nós temos de um lado os “Models” (que são as nossas classes de domínio) e do outro lado as “Views” (que são as janelas e user controls). Entre esses dois elementos, temos as “ViewModels“, que são basicamente elementos que fazem a ligação entre os “Models” e as “Views“:

Image by Ugaya40 used under Creative Commons
https://commons.wikimedia.org/wiki/File:MVVMPattern.png

Como você pode perceber no diagrama acima, as “Views” se comunicam com as “ViewModels” (principalmente através de data binding) que, por sua vez, se comunicam com os “Models“. O mais importante nessa separação de atribuições é o seguinte:

– As ViewModels não conhecem as Views
– Os Models não conhecem as Views
– As Views não conhecem os Models

Com esses conceitos em mente, vamos começar o desenvolvimento da nossa aplicação de exemplo.

Criando a classe de domínio

Para não complicarmos ainda mais um conceito que já é difícil por si só, vamos manter o nosso domínio com uma estrutura bem simples. Nós teremos apenas uma classe chamada “Funcionario“. Essa classe terá algumas propriedades com tipos básicos e duas propriedades que serão valores de um “Enum“.

Vamos colocar os dois “Enums” e a classe “Funcionario” dentro de uma nova pasta no nosso projeto, a qual daremos o nome de “Models“. Criaremos essa nova pasta clicando com o botão direto no projeto e escolhendo a opção “Add => New Folder” e dando o nome de “Models” para a pasta que será criada:

Dentro dessa nova pasta, adicione os enumeradores “EstadoCivil” e “Sexo“, bem como a classe “Funcionario“:

// C#
public enum EstadoCivil
{
    Solteiro,
    Casado,
    Divorciado,
    Viuvo
}
    ' VB.NET
    Public Enum EstadoCivil
        Solteiro
        Casado
        Divorciado
        Viuvo
    End Enum
// C#
public enum Sexo
{
    Masculino,
    Feminino
}
    ' VB.NET
    Public Enum Sexo
        Masculino
        Feminino
    End Enum
// C#
public class Funcionario
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Sobrenome { get; set; }
    public DateTime DataNascimento { get; set; }
    public Sexo Sexo { get; set; }
    public EstadoCivil EstadoCivil { get; set; }
    public DateTime DataAdmissao { get; set; }
}
    ' VB.NET
    Public Class Funcionario
        Public Property Id As Integer
        Public Property Nome As String
        Public Property Sobrenome As String
        Public Property DataNascimento As DateTime
        Public Property Sexo As Sexo
        Public Property EstadoCivil As EstadoCivil
        Public Property DataAdmissao As DateTime
    End Class

No final desse processo, a estrutura dessa pasta deverá ficar parecida com a imagem abaixo:

Ajustando o layout da janela

Agora que já temos a nossa classe de modelo criada, vamos partir para o layout da nossa janela. O elemento base da nossa tela será um Grid com duas linhas. Na primeira linha, teremos um StackPanel onde colocaremos os botões “Novo“, “Editar” e “Deletar“. Já na segunda linha do Grid, nós teremos um controle do tipo DataGrid (que estará posicionado dentro de um ScrollViewer para habilitar scrolling) com as colunas da classe “Funcionario“. Veja só como é que fica o XAML dessa janela:

<Window x:Class="WPFMVVMCRUD.FuncionariosWindow"
        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:WPFMVVMCRUD"
        mc:Ignorable="d"
        Title="Lista de Funcionários" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="9*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Margin="3" Padding="3" Content="Novo"/>
            <Button Margin="3" Padding="3" Content="Editar"/>
            <Button Margin="3" Padding="3" Content="Deletar"/>
        </StackPanel>
        <ScrollViewer Grid.Row="1" 
                      HorizontalScrollBarVisibility="Visible"
                      VerticalScrollBarVisibility="Hidden">
            <DataGrid AutoGenerateColumns="False" 
                      IsReadOnly="True">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Id"/>
                    <DataGridTextColumn Header="Nome"/>
                    <DataGridTextColumn Header="Sobrenome"/>
                    <DataGridTextColumn Header="Data Nascimento"/>
                    <DataGridTextColumn Header="Sexo"/>
                    <DataGridTextColumn Header="Estado Civil"/>
                    <DataGridTextColumn Header="Data Admissão"/>
                </DataGrid.Columns>
            </DataGrid>
        </ScrollViewer>
    </Grid>
</Window>

Nota: se você quiser saber mais sobre o funcionamento do controle DataGrid no WPF, confira esta série de artigos do site WPF Tutorial.

Interface INotifyPropertyChanged

Para que o mecanismo de data binding do WPF funcione da maneira mágica que conhecemos, os objetos que serão utilizados no data binding precisam implementar a interface INotifyPropertyChanged. Se você quiser saber mais sobre essa interface, confira o meu artigo sobre as interfaces de notificação de alteração no WPF, mas basicamente os objetos que implementam essa interface precisam disparar um evento quando o valor das suas propriedades for alterado.

Normalmente os frameworks de MVVM (como MVVM Light ou Caliburn) já possuem uma classe base que implementam essa interface, porém, como nós vamos implementar o exemplo desse artigo sem utilizar nenhum framework, nós temos que criar uma classe base para evitarmos repetição de código. Vamos chamar essa classe de “BaseNotifyPropertyChanged“:

// C#
public abstract class BaseNotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged
{
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    protected void SetField<T>(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            RaisePropertyChanged(propertyName);
        }
    }
    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}
' VB.NET
Public MustInherit Class BaseNotifyPropertyChanged
    Implements ComponentModel.INotifyPropertyChanged

    Private Event PropertyChanged As ComponentModel.PropertyChangedEventHandler Implements ComponentModel.INotifyPropertyChanged.PropertyChanged

    Protected Sub SetField(Of T)(ByRef field As T, value As T, <System.Runtime.CompilerServices.CallerMemberName> Optional propertyName As String = Nothing)
        If Not EqualityComparer(Of T).[Default].Equals(field, value) Then
            field = value
            RaisePropertyChanged(propertyName)
        End If
    End Sub
    Protected Sub RaisePropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(propertyName))
    End Sub
End Class

Nota: existem diversas maneiras que podemos utilizar para implementarmos a interface INotifyPropertyChanged. Essa implementação foi evoluindo ao longo dos anos com a adição de novas funcionalidades nas linguagens. Eu mesmo já escrevi um artigo anteriormente sobre a utilização do CallerMemberName para simplificar a implementação de INotifyPropertyChanged quando esse atributo foi lançado. Para implementar a versão apresentada acima, eu tomei como base o código desta thread do StackOverflow.

Criando a ViewModel

Com a nossa classe base criada, agora nós podemos partir para a criação da ViewModel para a tela de funcionários. Vamos começar bem simples. A primeira versão da nossa classe “FuncionariosViewModel” terá somente a lista de funcionários que será exibida no grid e uma propriedade para lidarmos com o funcionário selecionado no grid. Coloque essa classe dentro de uma nova pasta no projeto, chamada de “ViewModel“:

// C#
public class FuncionariosViewModel : BaseNotifyPropertyChanged
{
    public System.Collections.ObjectModel.ObservableCollection<Model.Funcionario> Funcionarios { get; private set; }

    private Model.Funcionario _funcionarioSelecionado;
    public Model.Funcionario FuncionarioSelecionado
    {
        get { return _funcionarioSelecionado; }
        set { SetField(ref _funcionarioSelecionado, value); }
    }

    public FuncionariosViewModel()
    {
        Funcionarios = new System.Collections.ObjectModel.ObservableCollection<Model.Funcionario>();
        Funcionarios.Add(new Model.Funcionario()
        {
            Id = 1,
            Nome = "André",
            Sobrenome = "Lima",
            DataNascimento = new DateTime(1984, 12, 31),
            Sexo = Model.Sexo.Masculino,
            EstadoCivil = Model.EstadoCivil.Casado,
            DataAdmissao = new DateTime(2010, 1, 1)
        });

        FuncionarioSelecionado = Funcionarios.FirstOrDefault();
    }
}
    ' VB.NET
    Public Class FuncionariosViewModel
        Inherits BaseNotifyPropertyChanged

        Public Property Funcionarios As System.Collections.ObjectModel.ObservableCollection(Of Model.Funcionario)

        Private _funcionarioSelecionado As Model.Funcionario
        Public Property FuncionarioSelecionado() As Model.Funcionario
            Get
                Return _funcionarioSelecionado
            End Get
            Set
                SetField(_funcionarioSelecionado, Value)
            End Set
        End Property

        Public Sub New()
            Funcionarios = New System.Collections.ObjectModel.ObservableCollection(Of Model.Funcionario)()
            Funcionarios.Add(New Model.Funcionario() With {
                .Id = 1,
                .Nome = "André",
                .Sobrenome = "Lima",
                .DataNascimento = New DateTime(1984, 12, 31),
                .Sexo = Model.Sexo.Masculino,
                .EstadoCivil = Model.EstadoCivil.Casado,
                .DataAdmissao = New DateTime(2010, 1, 1)})

            FuncionarioSelecionado = Funcionarios.FirstOrDefault()
        End Sub
    End Class

Nota: a propriedade “Funcionarios” da ViewModel deverá ser uma ObservableCollection, e não uma simples “List”. Fazemos isso porque a classe ObservableCollection já implementa as interfaces de notificação de alteração de coleções, coisa que a classe List não faz. Dessa forma, o WPF atualizará automaticamente o grid quando adicionarmos ou excluirmos itens da coleção por trás dos panos.

Uma vez criada a classe “FuncionariosViewModel“, vamos configurar o DataContext da nossa janela para uma instância dessa classe. Nós poderíamos fazer isso direto no XAML da janela (utilizando, por exemplo, o ViewModelLocator do MVVM Light), mas eu resolvi fazer isso de forma pragmática, colocando uma linha de código extremamente simples no code-behind do construtor da janela:

// C#
public partial class FuncionariosWindow : Window
{
    public FuncionariosWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel.FuncionariosViewModel();
    }
}
' VB.NET
Class FuncionariosWindow
    Public Sub New()
        InitializeComponent()
        DataContext = New ViewModel.FuncionariosViewModel()
    End Sub
End Class

Agora que nós já ligamos a nossa janela com a ViewModel, nós podemos utilizar as propriedades da ViewModel nos data bindings dos elementos da janela. A propriedade “ItemsSource” do DataGrid será conectada com a propriedade “Funcionarios” da ViewModel. Já a propriedade “SelectedItem” do DataGrid será conectada com a propriedade “FuncionarioSelecionado” da ViewModel. Por fim, configuraremos o DataBinding de cada uma das colunas do DataGrid, de maneira que elas apontem para as propriedades correspondentes na classe “Funcionario“:

<DataGrid ItemsSource="{Binding Funcionarios}" 
          AutoGenerateColumns="False" 
          IsReadOnly="True"
          SelectedItem="{Binding FuncionarioSelecionado}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
        <DataGridTextColumn Header="Nome" Binding="{Binding Nome}"/>
        <DataGridTextColumn Header="Sobrenome" Binding="{Binding Sobrenome}"/>
        <DataGridTextColumn Header="Data Nascimento" Binding="{Binding DataNascimento, StringFormat=\{0:d\}}"/>
        <DataGridTextColumn Header="Sexo" Binding="{Binding Sexo}"/>
        <DataGridTextColumn Header="Estado Civil" Binding="{Binding EstadoCivil}"/>
        <DataGridTextColumn Header="Data Admissão" Binding="{Binding DataAdmissao, StringFormat=\{0:d\}}"/>
    </DataGrid.Columns>
</DataGrid>

Execute a aplicação e veja o resultado que obtemos até o momento:

Nota: o mecanismo de data binding do WPF é bem avançado e nós podemos fazer muitos malabarismos com ele. Se você quiser saber mais sobre esse tema, recomendo um artigo / vídeo que escrevi tempos atrás sobre data binding no WPF.

Estrutura de comandos

O mecanismo de data binding do WPF não serve somente para ligarmos uma propriedade com outra propriedade. Uma outra possibilidade muito legal é a ligação dos controles com comandos da ViewModel. Dessa forma, ao invés de implementarmos o código do clique do botão no code-behind da janela, nós podemos ligar o botão com um comando localizado na ViewModel. No nosso exemplo, nós teremos os comandos “Deletar“, “Novo” e “Editar“.

Porém, antes de partirmos para a implementação real dos comandos, vamos criar uma classe que servirá como base para todos os comandos da nossa aplicação. Como fizemos com a implementação da interface INotifyPropertyChanged, nós criaremos agora a classe base chamada “BaseCommand“, que dessa vez implementará a interface ICommand:

// C#
public abstract class BaseCommand : System.Windows.Input.ICommand
{
    public event EventHandler CanExecuteChanged;

    public virtual bool CanExecute(object parameter) => true;
    public abstract void Execute(object parameter);

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}
' VB.NET
Public MustInherit Class BaseCommand
    Implements System.Windows.Input.ICommand

    Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

    Public Overridable Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return True
    End Function
    Public MustOverride Sub Execute(parameter As Object) Implements ICommand.Execute

    Public Sub RaiseCanExecuteChanged()
        RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
    End Sub
End Class

Observe que a implementação de ICommand é muito simples. Nós temos um método chamado “CanExecute” que avaliará o parâmetro fornecido e indicará se o comando pode ser executado ou não. Esse método é muito interessante, pois ele possibilitará que o mecanismo de data binding habilite ou desabilite o controle automaticamente caso o comando possa ser executado ou não (por exemplo, na nossa aplicação o botão de “Deletar” só poderá ficar habilitado se tivermos um funcionário selecionado).

Outro método que temos na interface ICommand é o “Execute“, que é o método onde teremos a implementação da lógica do comando de fato (ou seja, é aqui que implementaremos a lógica de deletar um funcionário no comando “Deletar“). Por fim, temos um evento chamado “CanExecuteChanged” que deverá ser disparado toda vez que o valor de “CanExecute” tiver sido alterado, de forma que o mecanismo de data binding atualize o estado do controle na interface (habilitado ou desabilitado).

Nota: mais uma vez, gostaria de ressaltar que nós só estamos implementando essa classe “na mão” porque nós não estamos utilizando nenhum framework MVVM nesse projeto de exemplo. Qualquer framework já teria uma implementação própria de ICommand o que, obviamente, tornaria redundante a criação da classe acima.

Comando “Deletar”

Com o nosso comando base criado, vamos partir para a implementação concreta dos comandos na nossa ViewModel. O primeiro comando que iremos implementar é o “Deletar“. Optei por criar primeiramente esse comando porque ele é o mais simples de todos.

O método “CanExecute” deverá retornar verdadeiro ou falso, dependendo se um funcionário está selecionado ou não (propriedade “FuncionarioSelecionado” da ViewModel diferente de nulo). Já o método “Execute” excluirá o funcionário selecionado da lista interna e configurará a propriedade “FuncionarioSelecionado” com o primeiro registro da lista (caso exista um registro). Vamos ao código, que deverá ser adicionado dentro da classe “FuncionariosViewModel“:

// C#
public class DeletarCommand : BaseCommand
{
    public override bool CanExecute(object parameter)
    {
        var viewModel = parameter as FuncionariosViewModel;
        return viewModel != null && viewModel.FuncionarioSelecionado != null;
    }

    public override void Execute(object parameter)
    {
        var viewModel = (FuncionariosViewModel)parameter;
        viewModel.Funcionarios.Remove(viewModel.FuncionarioSelecionado);
        viewModel.FuncionarioSelecionado = viewModel.Funcionarios.FirstOrDefault();
    }
}
        ' VB.NET
        Public Class DeletarCommand
            Inherits BaseCommand
            Public Overrides Function CanExecute(parameter As Object) As Boolean
                Dim viewModel = TryCast(parameter, FuncionariosViewModel)
                Return viewModel IsNot Nothing AndAlso viewModel.FuncionarioSelecionado IsNot Nothing
            End Function

            Public Overrides Sub Execute(parameter As Object)
                Dim viewModel = DirectCast(parameter, FuncionariosViewModel)
                viewModel.Funcionarios.Remove(viewModel.FuncionarioSelecionado)
                viewModel.FuncionarioSelecionado = viewModel.Funcionarios.FirstOrDefault()
            End Sub
        End Class

Observe que o parâmetro que está sendo passado para o comando é a própria instância de “FuncionariosViewModel“. Uma outra opção seria passar diretamente o funcionário selecionado como parâmetro para o comando, porém, se fizéssemos isso, nós teríamos que receber a ViewModel como parâmetro no construtor do comando (afinal, nós temos que deletar o funcionário de alguma maneira e isso só é possível se tivermos acesso à ViewModel). Se estivéssemos trabalhando com um framework MVVM isso ficaria bem mais simples também (através da utilização de RelayCommands).

A próxima alteração que temos que fazer é adicionarmos uma instância desse comando na classe FuncionariosViewModel. Para isso, vamos criar uma nova propriedade nessa classe:

// C#
public DeletarCommand Deletar { get; private set; } = new DeletarCommand();
' VB.NET
Public Property Deletar As New DeletarCommand

Por fim, nós temos que configurar o “Command” e “CommandParameter” no botão “Deletar”, de forma que o “Command” aponte para o comando e o “CommandParameter” aponte para a ViewModel:

<Button Margin="3" 
        Padding="3"  
        CommandParameter="{Binding}" 
        Command="{Binding Deletar}"
        Content="Deletar"/>

Atenção! É importante que você configure primeiramente o “CommandParameter” e, somente depois dele, o “Command”. Se você fizer na ordem inversa, você terá alguns efeitos colaterais, como os que foram apresentados nesta thread do StackOverflow.

Execute a aplicação, delete o funcionário e confira o resultado:

Ué, por que é que o botão “Deletar” ainda está habilitado, mesmo depois de termos removido o único registro disponível no grid? O problema nesse caso é que nós temos que notificar ao mecanismo de binding do WPF que o valor do “CanExecute” do botão deletar foi alterado. Nós fazemos isso chamando o método “RaiseCanExecuteChanged” do comando. Ou seja, no “setter” da propriedade “FuncionarioSelecionado“, nós temos que chamar esse método do comando “Deletar“:

// C#
public Model.Funcionario FuncionarioSelecionado
{
    get { return _funcionarioSelecionado; }
    set
    {
        SetField(ref _funcionarioSelecionado, value);
        Deletar.RaiseCanExecuteChanged();
    }
}
        ' VB.NET
        Public Property FuncionarioSelecionado() As Model.Funcionario
            Get
                Return _funcionarioSelecionado
            End Get
            Set
                SetField(_funcionarioSelecionado, Value)
                Deletar.RaiseCanExecuteChanged()
            End Set
        End Property

Com essa alteração, se fizermos o teste de exclusão do funcionário novamente, nós veremos que o botão “Deletar” será desabilitado automaticamente assim que nós deletarmos o registro do grid:

Comando “Novo”

O próximo comando que implementaremos nesse exemplo é o comando para inserirmos um novo funcionário na nossa lista. Porém, antes de implementarmos esse comando, nós temos que criar uma nova janela que servirá de diálogo para que o usuário informe as propriedades do novo funcionário. Vamos chamar essa nova janela de “FuncionarioWindow“.

Nessa janela, nós teremos somente um StackPanel com vários controles, representando cada uma das propriedades do funcionário. Teremos alguns TextBoxes, DatePickers e ComboBoxes, além de dois botões para confirmarmos ou cancelarmos a operação. Veja como é que fica o XAML dessa nova janela:

<Window x:Class="WPFMVVMCRUD.FuncionarioWindow"
        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:WPFMVVMCRUD"
        mc:Ignorable="d"
        Title="Funcionário" Height="326.178" Width="300" WindowStyle="ToolWindow">
    <Grid Margin="3">
        <StackPanel Orientation="Vertical">
            <TextBlock Text="Id"/>
            <TextBox Text="{Binding Id}" IsEnabled="False"/>
            <TextBlock Text="Nome"/>
            <TextBox Text="{Binding Nome}"/>
            <TextBlock Text="Sobrenome"/>
            <TextBox Text="{Binding Sobrenome}"/>
            <TextBlock Text="Data de Nascimento"/>
            <DatePicker SelectedDate="{Binding DataNascimento}"/>
            <TextBlock Text="Sexo"/>
            <ComboBox Name="SexoComboBox" Text="{Binding Sexo}"/>
            <TextBlock Text="Estado Civil"/>
            <ComboBox Name="EstadoCivilComboBox" Text="{Binding EstadoCivil}"/>
            <TextBlock Text="Data de Admissão"/>
            <DatePicker SelectedDate="{Binding DataAdmissao}"/>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="5*"/>
                    <ColumnDefinition Width="5*"/>
                </Grid.ColumnDefinitions>
                <Button Name="OKButton" 
                        Grid.Column="0"
                        Content="OK" 
                        Margin="3"
                        IsDefault="True" 
                        Click="OKButton_Click"/>
                <Button Grid.Column="1" 
                        Content="Cancelar" 
                        Margin="3"
                        IsCancel="True"/>
            </Grid>
        </StackPanel>
    </Grid>
</Window>

No code-behind dessa janela, nós precisamos alimentar os ComboBoxes com os valores dos enumeradores “EstadoCivil” e “Sexo“. Além disso, temos que implementar o clique do botão “OK“, que simplesmente configurará o valor de “DialogResult” como sendo “true“:

// C#
public partial class FuncionarioWindow : Window
{
    public FuncionarioWindow()
    {
        InitializeComponent();
        SexoComboBox.ItemsSource = Enum.GetValues(typeof(Model.Sexo)).Cast<Model.Sexo>();
        EstadoCivilComboBox.ItemsSource = Enum.GetValues(typeof(Model.EstadoCivil)).Cast<Model.EstadoCivil>();
    }

    private void OKButton_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = true;
    }
}
' VB.NET
Public Class FuncionarioWindow
    Public Sub New()
        InitializeComponent()
        SexoComboBox.ItemsSource = System.Enum.GetValues(GetType(Model.Sexo)).Cast(Of Model.Sexo)()
        EstadoCivilComboBox.ItemsSource = System.Enum.GetValues(GetType(Model.EstadoCivil)).Cast(Of Model.EstadoCivil)()
    End Sub

    Private Sub OKButton_Click(sender As Object, e As RoutedEventArgs)
        DialogResult = True
    End Sub
End Class

Nota: existem maneiras de popular ComboBoxes com valores de enumeradores diretamente pelo XAML. Porém, como no code-behind a implementação é muito mais simples (somente uma linha), eu resolvi seguir a maneira mais pragmática e fiz esse carregamento no code-behind. Se você quiser saber como fazer o carregamento direto no XAML, confira esta thread no StackOverflow.

Com a nova janela criada, vamos partir para a implementação do comando “Novo“. A sua implementação será também muito simples. O “CanExecute” retornará verdadeiro se o parâmetro do comando for uma instância de “FuncionariosViewModel” (ou seja, será sempre verdadeiro).

Já no método “Execute“, nós criaremos um novo funcionário, configuraremos o ID como sendo o maior ID + 1 e exibiremos uma nova instância de “FuncionarioWindow“, configurando o DataContext dela como sendo o novo funcionário que acabamos de criar. Uma vez que a janela for fechada, nós verificaremos o valor de DialogResult e, caso ele seja verdadeiro (o que significa que o usuário clicou em “OK“), nós adicionaremos esse novo funcionário na lista:

// C#
public class NovoCommand : BaseCommand
{
    public override bool CanExecute(object parameter)
    {
        return parameter is FuncionariosViewModel;
    }

    public override void Execute(object parameter)
    {
        var viewModel = (FuncionariosViewModel)parameter;
        var funcionario = new Model.Funcionario();
        var maxId = 0;
        if (viewModel.Funcionarios.Any())
        {
            maxId = viewModel.Funcionarios.Max(f => f.Id);
        }
        funcionario.Id = maxId + 1;

        var fw = new FuncionarioWindow();
        fw.DataContext = funcionario;
        fw.ShowDialog();
        
        if (fw.DialogResult.HasValue && fw.DialogResult.Value)
        {
            viewModel.Funcionarios.Add(funcionario);
            viewModel.FuncionarioSelecionado = funcionario;
        }
    }
}
        ' VB.NET
        Public Class NovoCommand
            Inherits BaseCommand
            Public Overrides Function CanExecute(parameter As Object) As Boolean
                Return TypeOf parameter Is FuncionariosViewModel
            End Function

            Public Overrides Sub Execute(parameter As Object)
                Dim viewModel = DirectCast(parameter, FuncionariosViewModel)
                Dim funcionario = New Model.Funcionario()
                Dim maxId = 0
                If viewModel.Funcionarios.Any() Then
                    maxId = viewModel.Funcionarios.Max(Function(f) f.Id)
                End If
                funcionario.Id = maxId + 1

                Dim fw = New FuncionarioWindow()
                fw.DataContext = funcionario
                fw.ShowDialog()

                If fw.DialogResult.HasValue AndAlso fw.DialogResult.Value Then
                    viewModel.Funcionarios.Add(funcionario)
                    viewModel.FuncionarioSelecionado = funcionario
                End If
            End Sub
        End Class

Em seguida, nós temos que criar uma nova instância desse comando dentro da ViewModel:

// C#
public NovoCommand Novo { get; private set; } = new NovoCommand();
' VB.NET
Public Property Novo As New NovoCommand

E, por fim, temos que ajustar o binding do comando no botão “Novo“:

<Button Margin="3" 
        Padding="3" 
        CommandParameter="{Binding}"
        Command="{Binding Novo}"
        Content="Novo"/>

Pronto! Execute a aplicação, clique no botão “Novo“, preencha as informações na janela de diálogo e veja o resultado no grid:

Nota: as pessoas que levam o MVVM ao extremo dizem que não é correto chamar uma janela de diálogo dentro de um comando, uma vez que a ViewModel não deve saber nada das Views. Porém, nesse caso, nós só estamos exibindo uma janela de diálogo para buscar mais informações. Existem maneiras de evitar esse tipo de acoplamento, entretanto, isso deixaria o código muito mais complexo e, na minha opinião, para esse caso particular, esse aumento da complexidade não faria sentido. Ou seja, nessa situação, eu particularmente deixaria o código desse jeito mesmo. Se você tem uma opinião diferente da minha, fico aguardando os seus comentários no final do artigo.

Nota 2: a tela FuncionarioWindow está sendo “bindada” diretamente com uma instância da classe Funcionario. Como vimos no diagrama no início do artigo, as Views não devem se comunicar diretamente com os Models. Porém, nesse caso eu acho completamente desnecessário criarmos uma ViewModel que só servirá de intermediação para as propriedades da classe Funcionario. Se tivéssemos mais lógica a ser implementada na janela FuncionarioWindow, aí sim eu partiria para a criação de uma ViewModel específica para ela. Mas, como esse não é o caso, resolvi seguir o caminho pragmático e fiz o data binding direto com a classe Funcionario.

Comando “Editar”

O último comando que vamos implementar é o comando que fará a edição de um funcionário já existente. Para implementarmos esse comando, nós enviaremos uma cópia do funcionário para a janela “FuncionarioWindow“. Por causa disso, nós teremos que, primeiramente, implementar a interface “ICloneable” na classe Funcionario:

// C#
public class Funcionario : ICloneable
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public string Sobrenome { get; set; }
    public DateTime DataNascimento { get; set; }
    public Sexo Sexo { get; set; }
    public EstadoCivil EstadoCivil { get; set; }
    public DateTime DataAdmissao { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}
    ' VB.NET
    Public Class Funcionario
        Implements ICloneable

        Public Property Id As Integer
        Public Property Nome As String
        Public Property Sobrenome As String
        Public Property DataNascimento As DateTime
        Public Property Sexo As Sexo
        Public Property EstadoCivil As EstadoCivil
        Public Property DataAdmissao As DateTime

        Public Function Clone() As Object Implements ICloneable.Clone
            Return Me.MemberwiseClone()
        End Function
    End Class

Observe que a única alteração na classe foi a implementação do método “Clone“, que simplesmente retornará o resultado do método MemberwiseClone (que por sua vez retornará um objeto idêntico ao atual, copiando todas as propriedades dele para a nova instância).

Uma vez implementada a interface “ICloneable” na classe Funcionario, nós podemos partir para a implementação do comando “Editar“. Veja como é que fica o código dele:

// C#
public class EditarCommand : BaseCommand
{
    public override bool CanExecute(object parameter)
    {
        var viewModel = parameter as FuncionariosViewModel;
        return viewModel != null && viewModel.FuncionarioSelecionado != null;
    }

    public override void Execute(object parameter)
    {
        var viewModel = (FuncionariosViewModel)parameter;
        var cloneFuncionario = (Model.Funcionario)viewModel.FuncionarioSelecionado.Clone();
        var fw = new FuncionarioWindow();
        fw.DataContext = cloneFuncionario;
        fw.ShowDialog();

        if (fw.DialogResult.HasValue && fw.DialogResult.Value)
        {
            viewModel.FuncionarioSelecionado.Nome = cloneFuncionario.Nome;
            viewModel.FuncionarioSelecionado.Sobrenome = cloneFuncionario.Sobrenome;
            viewModel.FuncionarioSelecionado.DataNascimento = cloneFuncionario.DataNascimento;
            viewModel.FuncionarioSelecionado.Sexo = cloneFuncionario.Sexo;
            viewModel.FuncionarioSelecionado.EstadoCivil = cloneFuncionario.EstadoCivil;
            viewModel.FuncionarioSelecionado.DataAdmissao = cloneFuncionario.DataAdmissao;
        }
    }
}
        ' VB.NET
        Public Class EditarCommand
            Inherits BaseCommand
            Public Overrides Function CanExecute(parameter As Object) As Boolean
                Dim viewModel = TryCast(parameter, FuncionariosViewModel)
                Return viewModel IsNot Nothing AndAlso viewModel.FuncionarioSelecionado IsNot Nothing
            End Function

            Public Overrides Sub Execute(parameter As Object)
                Dim viewModel = DirectCast(parameter, FuncionariosViewModel)
                Dim cloneFuncionario = DirectCast(viewModel.FuncionarioSelecionado.Clone(), Model.Funcionario)
                Dim fw = New FuncionarioWindow()
                fw.DataContext = cloneFuncionario
                fw.ShowDialog()

                If fw.DialogResult.HasValue AndAlso fw.DialogResult.Value Then
                    viewModel.FuncionarioSelecionado.Nome = cloneFuncionario.Nome
                    viewModel.FuncionarioSelecionado.Sobrenome = cloneFuncionario.Sobrenome
                    viewModel.FuncionarioSelecionado.DataNascimento = cloneFuncionario.DataNascimento
                    viewModel.FuncionarioSelecionado.Sexo = cloneFuncionario.Sexo
                    viewModel.FuncionarioSelecionado.EstadoCivil = cloneFuncionario.EstadoCivil
                    viewModel.FuncionarioSelecionado.DataAdmissao = cloneFuncionario.DataAdmissao
                End If
            End Sub
        End Class

Note que a implementação desse comando também é muito tranquila. O “CanExecute” retorna verdadeiro ou falso dependendo se um funcionário está selecionado ou não (do mesmo jeito que fizemos com o comando “Deletar“). Já no método “Execute” nós clonamos o funcionário selecionado, mandamos para a janela “FuncionarioWindow” e, caso o usuário clique em “OK” nessa janela (DialogResult verdadeiro), nós copiamos os novos valores nas propriedades do funcionário selecionado.

Em seguida, temos que criar uma instância desse comando dentro da ViewModel:

// C#
public EditarCommand Editar { get; private set; } = new EditarCommand();
' VB.NET
Public Property Editar As New EditarCommand

E não podemos esquecer de chamar o método “RaiseCanExecuteChanged” desse comando quando o valor da propriedade “FuncionarioSelecionado” for alterado:

// C#
public Model.Funcionario FuncionarioSelecionado
{
    get { return _funcionarioSelecionado; }
    set
    {
        SetField(ref _funcionarioSelecionado, value);
        Deletar.RaiseCanExecuteChanged();
        Editar.RaiseCanExecuteChanged();
    }
}
        ' VB.NET
        Public Property FuncionarioSelecionado() As Model.Funcionario
            Get
                Return _funcionarioSelecionado
            End Get
            Set
                SetField(_funcionarioSelecionado, Value)
                Deletar.RaiseCanExecuteChanged()
                Editar.RaiseCanExecuteChanged()
            End Set
        End Property

Por fim, vamos configurar o data binding no botão “Editar“, de forma que ele utilize o comando que acabamos de criar:

<Button Margin="3" 
		Padding="3"
		CommandParameter="{Binding}"
		Command="{Binding Editar}"
		Content="Editar"/>

Execute a aplicação e veja que… a edição não vai funcionar! Por que é que não funcionou? Simples: nós esquecemos de implementar a interface INotifyPropertyChanged na classe Funcionario. Ou seja, o mecanismo de data binding do WPF não tem como saber que as propriedades do funcionário foram alteradas, aí ele não atualiza os valores no grid.

Dito isso, para consertarmos esse problema, nós temos que herdar a nossa classe Funcionario da classe “BaseNotifyPropertyChanged” que nós criamos anteriormente e temos que ajustar as propriedades de forma que elas notifiquem quando o seu valor tiver sido alterado:

// C#
public class Funcionario : BaseNotifyPropertyChanged, ICloneable
{
    private int _id;
    public int Id
    {
        get { return _id; }
        set { SetField(ref _id, value); }
    }

    private string _nome;
    public string Nome
    {
        get { return _nome; }
        set { SetField(ref _nome, value); }
    }

    private string _sobrenome;
    public string Sobrenome
    {
        get { return _sobrenome; }
        set { SetField(ref _sobrenome, value); }
    }

    private DateTime _dataNascimento;
    public DateTime DataNascimento
    {
        get { return _dataNascimento; }
        set { SetField(ref _dataNascimento, value); }
    }

    private Sexo _sexo;
    public Sexo Sexo
    {
        get { return _sexo; }
        set { SetField(ref _sexo, value); }
    }

    private EstadoCivil _estadoCivil;
    public EstadoCivil EstadoCivil
    {
        get { return _estadoCivil; }
        set { SetField(ref _estadoCivil, value); }
    }

    private DateTime _dataAdmissao;
    public DateTime DataAdmissao
    {
        get { return _dataAdmissao; }
        set { SetField(ref _dataAdmissao, value); }
    }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}
    ' VB.NET
    Public Class Funcionario
        Inherits BaseNotifyPropertyChanged
        Implements ICloneable

        Private _id As Integer
        Public Property Id() As Integer
            Get
                Return _id
            End Get
            Set
                SetField(_id, Value)
            End Set
        End Property

        Private _nome As String
        Public Property Nome() As String
            Get
                Return _nome
            End Get
            Set
                SetField(_nome, Value)
            End Set
        End Property

        Private _sobrenome As String
        Public Property Sobrenome() As String
            Get
                Return _sobrenome
            End Get
            Set
                SetField(_sobrenome, Value)
            End Set
        End Property

        Private _dataNascimento As DateTime
        Public Property DataNascimento() As DateTime
            Get
                Return _dataNascimento
            End Get
            Set
                SetField(_dataNascimento, Value)
            End Set
        End Property

        Private _sexo As Sexo
        Public Property Sexo() As Sexo
            Get
                Return _sexo
            End Get
            Set
                SetField(_sexo, Value)
            End Set
        End Property

        Private _estadoCivil As EstadoCivil
        Public Property EstadoCivil() As EstadoCivil
            Get
                Return _estadoCivil
            End Get
            Set
                SetField(_estadoCivil, Value)
            End Set
        End Property

        Private _dataAdmissao As DateTime
        Public Property DataAdmissao() As DateTime
            Get
                Return _dataAdmissao
            End Get
            Set
                SetField(_dataAdmissao, Value)
            End Set
        End Property

        Public Function Clone() As Object Implements ICloneable.Clone
            Return Me.MemberwiseClone()
        End Function
    End Class

Pronto! Agora sim. Execute a aplicação, tente alterar as informações de um funcionário e veja que o grid será atualizado corretamente.

Baixe o projeto de exemplo

Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário no final do artigo.

Concluindo

No artigo de hoje você conferiu um exemplo de CRUD no WPF com MVVM, sem utilizar nenhum framework de MVVM e sem nenhuma referência externa, ou seja, fazendo tudo “na mão“. Você aprendeu sobre a interface de notificação de alteração (INotifyPropertyChanged) e sobre os comandos do WPF (ICommand). Quem sabe mais para a frente eu não escreva outras partes desse artigo, introduzindo um framework MVVM no processo e implementando operações mais avançadas.

E você, já trabalha com MVVM no WPF? Você implementaria esse CRUD do mesmo jeito que eu implementei? Quais as melhorias que você faria no código que eu apresentei hoje? Lembre-se que eu não sou expert em WPF, portanto, pode ser que algumas coisas que eu apresentei no artigo possam ser feitas de maneira mais elegante. Se esse for o caso, fico aguardando a sua opinião na caixa de comentários logo abaixo.

Até a próxima!

André Lima

Photo by Pixabay used under Creative Commons
https://pixabay.com/en/computer-laptop-windows-10-hybrid-914546/

Newsletter do André Lima

* indicates required



Powered by MailChimp

16 thoughts on “Exemplo de CRUD no WPF com MVVM

  • João Victor disse:

    André, mais uma vez, parabéns pelos temas trazidos em seu blog. São extremamente interessantes e artigos que não encontramos, com esse detalhamento em lugar algum.

    Possuo alguns projetos WPF rodando, porém não utilizo o mundo perfeito.
    Tenho as views, view-models (que contém as propriedades das models e também as regras de negócio e acesso a dados via Entity) e as models que só definem as classes, porém tento utilizar alguns conceitos, como por exemplo, utilizando interfaces na ViewModel para que a mesma não implemente métodos como .ShowDialog() etc.
    A plataforma é excelente.

    Para implementar a notificação às Views minhas VewsModels herdam de uma classe pronta chama ViewModelBase que implementa o RaisePropertyChanged além de conter métodos para controle de erros que coloquei como Data Annotations na ViewModel.

    • andrealveslima disse:

      Olá João!

      Muito obrigado pelo elogio.. A ideia é trazer artigos mais longos e completos, mostrando realmente um passo a passo.. Eu acredito que tem muito artigo na internet que é muito superficial.. Esse tipo de artigo também é importante (para quem quer resolver alguma coisa rapidamente), mas eu preferi seguir outro caminho e escrever sempre com um nível bem alto de detalhes..

      Quanto ao esquema do MVVM, o jeito é ir melhorando o código aos poucos mesmo.. Muito dificilmente uma primeira versão do projeto vai acabar saindo com uma arquitetura legal.. O importante é ir evoluindo com o tempo e melhorando sempre que possível..

      Se eu conseguir eu quero escrever uma continuação desse artigo aqui, introduzindo algum framework MVVM e melhorando um pouco o código.. Vou colocar aqui na minha agenda.. ;)

      Abraço!
      André Lima

  • Claudio disse:

    a liberação de objetos correta em C#, já era um tema que estava esperando à tempo, aguardando…..

  • Bruno Almeida disse:

    Primeiramente parabéns por abordar um tema tão relegado atualmente, porém tenho algumas considerações.

    Um problema que vejo no MVVM é considerar o model do MVVM como o model da aplicação e com isso “sujar” as classes de domínio com coisas de WPF (p. ex. INPC – INotifyPropertyChanged).

    Essa abordagem é boa para exemplos simples, mas considero ruim para sistemas mais complexos e para reaproveitamento de código (imagine futuramente querer usar esse domínio em outro sistema que não use WPF?).

    O que uso atualmente, para projetos complexos, é considerar esse “model” um modelo somente da ViewModel, onde este é populado pela ViewModel com dados vindo de fato do model/domínio da aplicação (que poderia ser outra camada local, uma camada de serviços, webService, etc..).

    • andrealveslima disse:

      Olá Bruno, muito obrigado pelo comentário!

      Pois é, também acho ruim ter que implementar INPC nas classes do domínio.. Porém, será que você não gera muita duplicação de código ao implementar classes de model que só servirão para a utilização na ViewModel? Acho que acaba perdendo o sentido.. Talvez nesse caso eu prefira implementar INPC nas classes de domínio mesmo, uma vez que isso seria útil também em diversas outras plataformas, como Windows Forms, Xamarin, UWP, etc..

      Mas, enfim, uma outra opção seria utilizar algum esquema de pós-compilação que adicione a implementação do INPC automaticamente.. Eu nunca fiz dessa forma, mas esses dias atrás vi um artigo sobre isso que achei muito interessante:

      IL weaving para quem não sabe nada sobre IL weaving

      Abraço!
      André Lima

  • Marcelo Dias disse:

    Parabéns pelo artigo, gosto muito de trabalhar com WPF, inclusive eu penso que para aplicações empresariais o conjunto WPF operando com WEBAPI é uma ótima opção.

    Hoje em dia a maioria das pessoas acham que aplicações web são as melhores escolhas quando querem criar sistemas e com tantos frameworks front end disponíveis cada dia complica mais.

    Nada como criar uma interface WPF e deixar de lado várias preocupações que o desenvolvimento para browser nos proporciona.

    É para se pensar!!!
    Grande artigo! parabéns novamente!

    • andrealveslima disse:

      Olá Marcelo, muito obrigado pelo comentário!

      Concordo 100%! Hoje em dia vejo muita gente querendo fazer tudo na web, mas tem coisa que não faz sentido nenhum migrar para a web “só por migrar”.. Eu acho que sistemas empresariais se enquadram nessa categoria (a não ser que também precisem ser acessados por outras plataformas, como Linux)..

      Até mesmo o Windows Forms é uma excelente opção, na minha opinião.. Desenvolvemos os nossos sistemas aqui com ele e atende muito bem as necessidades dos nossos clientes..

      Abraço!
      André Lima

  • Herbert Lausmann disse:

    Boa noite André!

    Excelente post! Fiquei curioso quanto a uma coisa:

    Por que implementar cada comando como classes individuais ao invés de usar, por exemplo, um RelayCommand com um delegate contendo a rotina específica para cada comando?

    Queria entender a diferença.

    Abraço!

    • andrealveslima disse:

      Olá Herbert, obrigado pelo comentário!

      Primeiramente, parabéns pelo seu site.. Vi que você voltou a publicar conteúdo nas últimas semanas e seus artigos são sempre excelentes.. Até recomendei o seu site essas semanas atrás na minha newsletter..

      Enfim, quanto à sua questão, não tem diferença.. Ou melhor, a implementação com RelayCommand ficaria muito mais simples, como você comentou.. Porém, como não existe um RelayCommand nativo no .NET, eu teria duas opções: ou eu implementava um ou utilizava algum framework MVVM que já tivesse essa implementação.. Como eu já estava abordando muitos conceitos em um artigo só, resolvi implementar os comandos dessa forma mesmo, ao invés de ter que explicar também o conceito de RelayCommand..

      Inclusive, eu mencionei no artigo que a implementação ficaria mais simples se utilizássemos um framework MVVM que tivesse uma implementação de RelayCommand (procura por “RelayCommand” aí no texto que você vai encontrar o trecho onde eu menciono isso)..

      Quem sabe mais para a frente eu escreva uma continuação desse artigo com esse tipo de melhoria..

      Abraço!
      André Lima

  • Herbert Lausmann disse:

    Boa tarde André!
    Muito obrigado! Pretendo continuar postando sempre que possível.

    Entendi a razão.
    Estou codando um “micro framework” para .NET Core 2 de base pra implementar o MVVM em qualquer plataforma que suporte o .NET core. Uma lib multi plataforma bem simples que provê classes bases para o Model e a ViewModel, assim como uma implementação básica do RelayCommand. Também algumas classes para facilitar um pouco quando se é necessário implementar o suporte assíncrono no MVVM.

    Agora estou implementando a classe CommandManager que é ausente no .Net Core.

    Um abraço!

  • Douglas Alves disse:

    Olá André,
    Muito bom seus tutoriais e estou aprendendo muito.
    Eu me inscrevi na sua newsletter mas mesmo assim não tenho acesso ao projeto.
    Obrigado.

    • andrealveslima disse:

      Olá Douglas, muito obrigado pelo comentário!

      Você recebeu o link para baixar todos os projetos de exemplo tanto no e-mail de confirmação de inscrição quanto no meu e-mail de boas vindas.. Mas, enfim, acabei de mandar o link novamente para você..

      Abraço!
      André Lima

  • Jalber disse:

    Excelente artigo André! Meus parabéns!
    Era isso mesmo que eu estava procurando.
    Grande abraço!!!!

    • andrealveslima disse:

      Fala Jalber!

      Que bom que você conseguiu encontrar o artigo que estava procurando.. :)

      Qualquer dúvida, é só entrar em contato..

      Abraço!
      André Lima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *