O Conceito de MVVM no WPF
O padrão Model-View-ViewModel (MVVM) é uma evolução na forma de construir interfaces de usuário, especialmente no ecossistema .NET. Diferente do desenvolvimento tradicional em WinForms, onde a lógica é fortemente acoplada aos controles (UI-driven), o MVVM propõe uma separação clara onde a interface é guiada pelos dados (Data-driven).
- Model: Representa os dados e as regras de negócio.
- View: A camada visual definida em XAML. Ela não deve conter lógica de negócio, apenas a disposição dos elementos.
- ViewModel: Atua como um intermediário. Ela expõe propriedades para exibição de dados e comandos para execução de ações, conectando-se à View através do
DataContext.
A Diferença de Paradigma: WinForms vs. WPF/MVVM
No modelo convencional (WinForms), o desenvolvdeor arrasta um componente, como um TextBox, e escreve a lógica diretamente no seu evento de clique ou alteração. Se o componente for substituído por um Slider, o código quebra, pois as propriedades e nomes mudam.
No MVVM, a lógica reside na ViewModel. Se alterarmos um campo de texto por um seletor deslizante, basta manter o vínculo (Binding) com a mesma propriedade da ViewModel, mantendo o código de negócio intacto.
Estrutura Base para MVVM
Para implementar o MVVM, precisamos de duas classes fundamentais que lidam com a notificação de mudanças e a execução de comandos.
1. Classe Base para Notificação (ObservableObject)
Esta classe implementa INotifyPropertyChanged, essencial para que a UI saiba quando um valor na ViewModel foi alterado via código.
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2. Implementação de Comandos (RelayCommand)
Em vez de eventos de clique no code-behind, utilizamos a interface ICommand para desacoplar a ação da View.
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Exemplo Prático: Calculadora Simples
Vamos criar uma ViewModel que realiza a soma de dois valores e permite salvar o estado.
public class CalculadoraViewModel : ObservableObject
{
private double _valorA;
public double ValorA
{
get => _valorA;
set { _valorA = value; OnPropertyChanged(); }
}
private double _valorB;
public double ValorB
{
get => _valorB;
set { _valorB = value; OnPropertyChanged(); }
}
private double _resultado;
public double Resultado
{
get => _resultado;
set { _resultado = value; OnPropertyChanged(); }
}
public ICommand SomarCommand { get; }
public ICommand SalvarCommand { get; }
public CalculadoraViewModel()
{
SomarCommand = new RelayCommand(o => {
Resultado = ValorA + ValorB;
});
SalvarCommand = new RelayCommand(o => {
var sfd = new Microsoft.Win32.SaveFileDialog();
sfd.ShowDialog();
});
}
}
Vínculo na Camada de Visão (View)
No XAML, vinculamos os controles às propriedades da ViewModel. Observe que a lógica não depende do tipo de controle.
Cenário A: Utilizando TextBox
<StackPanel>
<TextBox Text="{Binding ValorA, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding ValorB, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Resultado}" />
<Button Content="Somar" Command="{Binding SomarCommand}" />
<Button Content="Exportar" Command="{Binding SalvarCommand}" />
</StackPanel>
Cenário B: Alteração de Requisito para Sliders
Se o cliente solicitar que a entrada seja feita por Sliders em vez de caixas de texto, alteramos apenas o XAML. A CalculadoraViewModel permanece inalterada.
<StackPanel>
<Slider Minimum="0" Maximum="100" Value="{Binding ValorA}" />
<Slider Minimum="0" Maximum="100" Value="{Binding ValorB}" />
<ProgressBar Value="{Binding Resultado}" Maximum="200" />
<Button Content="Calcular" Command="{Binding SomarCommand}" />
<Menu>
<MenuItem Header="Arquivo">
<MenuItem Header="Salvar" Command="{Binding SalvarCommand}" />
</MenuItem>
</Menu>
</StackPanel>
Essa abordagem garante que a manutenção do software seja simplificada, permitindo mudanças drásticas na interface sem a necessidade de reescrever a lógica operacional da aplicação.