Arquitetura MVVM no WPF: Implementação de Interfaces Baseadas em Dados

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.

Tags: WPF MVVM XAML C# dotnet

Publicado em 7-5 18:41