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
MdiActiveDocumente verifica che non sianull. - I clic avvengono al di fuori di qualsiasi comando, nel contesto dell'applicazione. Chi modifica direttamente il disegno lo blocca con
LockDocument, altrimenti si ottieneErrorStatus.LockViolation. Spesso è meglio avviare un comando conSendStringToExecute. 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
PropertyChangedo generarlo con un nome scritto in modo errato.nameofaiuta. - Non generare mai
CanExecuteChanged. Il pulsante resta grigio anche se l'input è corretto. - Collegare le liste come
List<T>invece che comeObservableCollection<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
- Formazione: il corso WPF va da XAML al data binding fino a MVVM. Pattern e architettura li approfondisce il corso «C# & .NET (avanzato)».
- Modernizzazione: ristrutturiamo gradualmente su MVVM interfacce WinForms o WPF cresciute nel tempo, anche negli add-in CAD: Interfacce utente presso Cadida.
- Il Suo progetto: Fissi un primo colloquio.
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.
