Costruire interfacce WPF pulite: MVVM nella pratica

Hand bedient ein Tablet, darüber schweben Diagramme und Kurven als Sinnbild für Benutzeroberflächen

Architettura software e UML

La finestra di dialogo funziona – per ora. Nel code-behind ci sono duemila righe: i gestori dei clic leggono le caselle di testo, calcolano, scrivono nel disegno e attivano o disattivano i pulsanti. Ogni nuovo requisito la rende più fragile, e la si può testare solo se qualcuno clicca. MVVM qui fa ordine.

Tre ruoli

  • View: l'interfaccia in XAML. Visualizza e riceve gli input. Il ViewModel lo conosce solo tramite i binding.
  • ViewModel: stato e comandi dell'interfaccia come normale classe C# – senza alcun riferimento a finestre, caselle di testo o colori.
  • Model: logica di dominio e dati, dietro interfacce anche l'accesso a database, dispositivi o CAD.

Il collegamento lo realizza il data binding di WPF. Se una proprietà nel ViewModel cambia, questo lo segnala tramite INotifyPropertyChanged, e l'interfaccia si adegua. Un pulsante non genera più un evento Click, ma un comando, un ICommand. Il vantaggio: il ViewModel si può testare senza interfaccia, e l'interfaccia si può ristrutturare senza toccare la logica.

Il ViewModel

Un piccolo esempio: l'utente immette una larghezza, e il pulsante «Zeichnen» (Disegna) è attivo solo con un input valido.

public class ParameterViewModel : INotifyPropertyChanged

{

    private readonly IZeichnungsDienst _zeichnung;

    private string _breite = "100";



    public ParameterViewModel(IZeichnungsDienst zeichnung)

    {

        _zeichnung = zeichnung;

        ZeichnenBefehl = new RelayCommand(Zeichne, () => IstGültig);

    }



    public event PropertyChangedEventHandler PropertyChanged;



    public string Breite

    {

        get => _breite;

        set

        {

            if (_breite != value)

            {

                _breite = value;

                Melde(nameof(Breite));

                ZeichnenBefehl.MeldeÄnderung();   // rivalutare il pulsante

            }

        }

    }



    public bool IstGültig => double.TryParse(Breite, out var wert) && wert > 0;



    public RelayCommand ZeichnenBefehl { get; }



    private void Zeichne() => _zeichnung.ZeichneRechteck(double.Parse(Breite));



    private void Melde(string name) =>

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

}



public class RelayCommand : ICommand

{

    private readonly Action _ausführen;

    private readonly Func<bool> _darfAusführen;



    public RelayCommand(Action ausführen, Func<bool> darfAusführen = null)

    {

        _ausführen = ausführen;

        _darfAusführen = darfAusführen;

    }



    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter) => _darfAusführen?.Invoke() ?? true;

    public void Execute(object parameter) => _ausführen();

    public void MeldeÄnderung() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

}

Il RelayCommand lo si scrive una volta e lo si usa ovunque. Chi vuole risparmiarsi la ripetizione usa il pacchetto CommunityToolkit.Mvvm: con gli attributi [ObservableProperty] e [RelayCommand] un source generator genera questo codice in fase di compilazione. Il pacchetto funziona anche con il .NET Framework 4.8 e quindi negli add-in per versioni più vecchie di AutoCAD.

La View si limita al binding

<StackPanel Margin="12">

    <Label Content="Breite (mm)" Target="{Binding ElementName=BreiteFeld}" />

    <TextBox x:Name="BreiteFeld"

             Text="{Binding Breite, UpdateSourceTrigger=PropertyChanged}" />

    <Button Content="Zeichnen" Command="{Binding ZeichnenBefehl}" Margin="0,8,0,0" />

</StackPanel>

Nel code-behind resta una riga: DataContext = new ParameterViewModel(dienst);. UpdateSourceTrigger=PropertyChanged fa sì che il pulsante reagisca già durante la digitazione e non solo quando l'utente lascia il campo.

MVVM nell'add-in CAD

MVVM conviene in particolare nelle palette e nelle finestre di dialogo degli add-in CAD. Lì il ViewModel non chiama l'API di AutoCAD, ma un'interfaccia come IZeichnungsDienst. Solo la sua implementazione lavora con AutoCAD e si occupa di ciò che lì vale:

  • Una palette vive più a lungo di qualsiasi disegno. L'implementazione recupera quindi il documento attivo a ogni chiamata, ex novo, tramite MdiActiveDocument e verifica che non sia null.
  • I clic avvengono al di fuori di qualsiasi comando, nel contesto dell'applicazione. Chi modifica direttamente il disegno lo blocca con LockDocument, altrimenti si ottiene ErrorStatus.LockViolation. Spesso è meglio avviare un comando con SendStringToExecute. Compare nella riga di comando, ha un proprio passo di annullamento e si può ripetere.

Nello unit test il ViewModel riceve un'imitazione dell'interfaccia. Così verifica input, comandi e stati in millisecondi, senza che AutoCAD sia in esecuzione. La separazione aiuta anche nel cambio di piattaforma: con un'altra implementazione del servizio lo stesso ViewModel si può riutilizzare in BricsCAD o Revit.

Fin dove spingersi

MVVM è uno strumento, non un dogma. Il codice che riguarda solo la visualizzazione – impostare il focus, avviare un'animazione – può restare nel code-behind. Le finestre di messaggio e le finestre di dialogo dei file il ViewModel le richiama tramite un servizio proprio, in modo che i test non restino bloccati su una finestra. E per le verifiche degli input che devono comparire direttamente sul campo c'è INotifyDataErrorInfo.

Errori tipici

  • Il ViewModel conosce controlli, colori o MessageBox. Allora non è più testabile senza interfaccia.
  • Dimenticare PropertyChanged o generarlo con un nome scritto in modo errato. nameof aiuta.
  • Non generare mai CanExecuteChanged. Il pulsante resta grigio anche se l'input è corretto.
  • Collegare le liste come List<T> invece che come ObservableCollection<T> – le nuove voci non compaiono.
  • Nell'add-in CAD, tenere in memoria nel ViewModel un disegno chiuso da tempo.
  • Trascurare gli errori di binding. Compaiono solo nella finestra di output di Visual Studio, non come eccezione.

Come La aiutiamo

Fonti: Ebi F. Eilber, AutoCAD .NET API (libro tecnico, uscita prevista nel 2027), capitolo «Programmazione della GUI»; Microsoft Learn: data binding in WPF; Microsoft Learn: MVVM Toolkit.

CONTACT

Seleziona la tua lingua

Primo colloquiofissare oraPrimo colloquiofissare ora