15 11 2017
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/
Trabalhando com código de barras no Report Viewer Evitando memory leaks no .NET com Dispose e blocos using
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.
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
a liberação de objetos correta em C#, já era um tema que estava esperando à tempo, aguardando…..
Olá Claudio, obrigado pelo comentário!
Pois é, esse é um tema bem importante.. Quarta-feira sai o vídeo.. ;)
Abraço!
André Lima
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..).
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
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!
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
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!
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
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!
Legal, Herbert! Quando tiver uma versão pronta, se possível, me envia uma cópia para eu dar uma olhada.. :)
Abraço!
André Lima
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.
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
Excelente artigo André! Meus parabéns!
Era isso mesmo que eu estava procurando.
Grande abraço!!!!
Fala Jalber!
Que bom que você conseguiu encontrar o artigo que estava procurando.. :)
Qualquer dúvida, é só entrar em contato..
Abraço!
André Lima
Boa noite André! Parabéns pelo artigo!
Como eu faço para fazer o download do projeto?
Já sou assinante da newsletter…
Obrigado!
Olá Ademilson!
Mandei o link no seu e-mail.. Qualquer coisa é só falar..
Abraço!
André Lima
Saudações, André Lima!
Primeiramente, muito obrigado por distribuir conhecimento atraves do seu blog, para os desenvolvedores, o seu blog é utilidade publica e fico muito feliz vendo os brasileiros adotarem o padrão MVVM nos projetos (independente da plataforma).
Estudo o WPF a 2(dois) anos e realmente é um caminho bem mais complexo que o WF (WinForms), o meu TCC mesmo fiz sobre WPF tentando implementar o MVVM com o minímo código de fundo com o framework de estilo MAHAPPS, e ficou excepcional.
Na sua proxima thread, poderia fazer (se possivel) um CRUD com SQLServer no WPF utlizando objetos complexos (com validacão, relaycommands, programação assicrona) utlizando framework de estilo (mahapps).
Abraços.
Olá Kevin!
Muito obrigado pelo comentário! Pois é, esse é um tema bem difícil de encontrar conteúdo em português.. Muita gente fala sobre isso, mas vejo pouca gente que realmente colocou em prática..
Eu não conhecia o MahApps, mas parece ser bem legal, hein.. Vou dar uma olhada mais a fundo assim que possível.. Obrigado pela indicação.. :)
E quanto à sua sugestão, eu coloquei ela aqui na minha lista.. Espero que eu consiga escrever um artigo nessa linha no ano que vem..
Abraço!
André Lima
Boa tarde Andre,
Otimo conteudo, pratico e objetivo, exatamente o que estava procurando.
Como faço para baixar o projeto?
Parabéns pela iniciativa.
Obrigado.
Olá Rodrigo, muito obrigado pelo comentário! Fico feliz que você tenha gostado..
Quando aos projetos de exemplo, você deveria ter recebido o link no e-mail de confirmação da inscrição.. Mas, de qualquer forma, eu acabei de enviar novamente no seu e-mail.. Qualquer coisa é só entrar em contato novamente..
Abraço!
André Lima
Oi André
Ótimo artigo, ainda mais no escasso mundo WPF brazuca. =D
Só posso fazer um comentário: a proposta do WPF não é ser um conjunto de regras engessadas, e essa idéia de que é engessada acabou levando muita gente à frustração. O enunciado diz que “sempre que possível” e “o ideal é que”, mas a galera acabou entendendo como “nunca faça isso ou aquilo” e “MVVM tem que ser puro” e etc.
O que eu já vi de contorcionismo pelo simples fato do programador querer ser purista e não usar uma mera linha no code-behind.
Se a galera que vai entrar no MVVM for desarmada dessa “obrigação” de ser xiita, acredito que fica mais suave a transição de quem só aprendeu a programar baseado em eventos de controles.
No mais, abraços e continue com os belos artigos.
Olá Marcio, muito obrigado pelo comentário!
Infelizmente, esse negócio da galera ser xiita é uma realidade não só do WPF / MVVM, mas sim em praticamente todas as plataformas e tecnologias de desenvolvimento.. TDD, DDD.. Escolha a sigla e você terá uma legião de pessoas que são totalmente intolerantes a qualquer vírgula que saia fora do que está escrito na definição.. É bem triste, mas acho que isso nunca vai mudar.. :(
Mas, de qualquer forma, quem perde são eles.. ;)
Abraço!
André Lima
Excelente o post, foi a melhor referência que encontrei para iniciar o uso do WPF. Gostaria de saber se é possível fazer o download do Projeto, sou iniciante no WPF e este material é completo e muito bem elaborado. Agradeço a atenção e parabéns pelo tabalho
Olá Antonio, muito obrigado!
Os projetos de exemplo estão disponíveis para os assinantes da minha newsletter.. Eu vi que você assinou ontem.. Você deve ter recebido o link para baixar os projetos no e-mail de confirmação..
Mas, de qualquer forma, hoje eu te enviei um outro e-mail de boas vindas, aí aproveitei e mandei o link novamente.. Qualquer coisa é só entrar em contato..
Abraço!
André Lima
Perfeito seu tutorial, fico no aguardo da implementação de um CRUD com algum framework, ficou muito bom, parabéns .. O WPF é muito desvalorizado pelo Brasil, e na Europa ? Abraço
Olá Gustavo!
Muito obrigado pelo comentário! Fico feliz que você tenha gostado da explicação.. :)
Quanto à valorização do WPF na Europa, eu sinceramente não sei te dizer.. Na empresa onde eu trabalho nós avaliamos o uso do WPF alguns anos atrás e decidimos ficar no Windows Forms mesmo.. De vez em quando aparece uma oportunidade ou outra onde a empresa utiliza WPF, mas acaba sendo bem raro pra ser sincero..
Abraço!
André Lima
Muito bom esse exemplo
Valeu, Renato! Fico feliz por ter conseguido ajudar.. :)
Abraço!
André Lima
Boa tarde Renato, obrigado por compartilhar o seu conhecimento, parabéns.
Como faço para baixar o código fonte?
Já sou assinante da newsletter.
Um abraço.
Olá Marcos!
Enviei o link no seu e-mail.. A propósito, me chamo “André”, e não “Renato”.. ;)
Abraço!
André Lima
Boa tarde, obrigado por compartilhar o seu conhecimento, parabéns.
Olá Daniel, valeu pelo comentário!
Abraço!
André Lima
Olá André, Muito bom artigo.
Como faço para baixar o código fonte?
Já sou assinante da newsletter.
Um abraço.
Olá Giovanni, obrigado pelo comentário!
Mandei no seu e-mail o link para acessar os projetos de exemplo.. Qualquer coisa estamos aí..
Abraço!
André Lima
Pode me enviar o código projeto por favor?
Olá Guilherme!
Enviei o link no seu e-mail.. Qualquer coisa estamos aí..
Abraço!
André Lima
Obrigado pelo artigo, André! Onde encontro o link para baixar o projeto de exemplo?
Olá Marco!
Ao inscrever-se na minha lista (link aqui), no e-mail de confirmação você recebe o endereço para baixar os projetos de exemplo..
Abraço!
André Lima