Software architecture and UML
The dialog works – for now. The code-behind contains two thousand lines: click handlers read text boxes, calculate, write to the drawing and enable or disable buttons. Every new requirement makes it more fragile, and the only way to test it is for someone to click through it. MVVM cleans this up.
Three roles
- View: the user interface in XAML. It displays data and accepts input. It knows the view model only through bindings.
- View model: the state and commands of the user interface as a plain C# class – with no reference to windows, text boxes or colours.
- Model: business logic and data, including access to databases, devices or CAD behind interfaces.
WPF data binding provides the connection. When a property in the view model changes, it reports this via INotifyPropertyChanged, and the user interface follows. A button no longer raises a click event but triggers a command, an ICommand. The benefit: the view model can be tested without a user interface, and the user interface can be rebuilt without touching the logic.
The view model
A small example: the user enters a width, and the “Draw” button is only enabled for valid input.
public class ParameterViewModel : INotifyPropertyChanged
{
private readonly IDrawingService _drawing;
private string _width = "100";
public ParameterViewModel(IDrawingService drawing)
{
_drawing = drawing;
DrawCommand = new RelayCommand(Draw, () => IsValid);
}
public event PropertyChangedEventHandler PropertyChanged;
public string Width
{
get => _width;
set
{
if (_width != value)
{
_width = value;
Notify(nameof(Width));
DrawCommand.RaiseCanExecuteChanged(); // re-evaluate the button
}
}
}
public bool IsValid => double.TryParse(Width, out var value) && value > 0;
public RelayCommand DrawCommand { get; }
private void Draw() => _drawing.DrawRectangle(double.Parse(Width));
private void Notify(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
You write the RelayCommand once and use it everywhere. If you want to avoid the repetition, use the CommunityToolkit.Mvvm package: with the attributes [ObservableProperty] and [RelayCommand], a source generator creates this code at compile time. The package also runs on .NET Framework 4.8 and therefore in add-ins for older AutoCAD versions.
The view only binds
<StackPanel Margin="12">
<Label Content="Width (mm)" Target="{Binding ElementName=WidthBox}" />
<TextBox x:Name="WidthBox"
Text="{Binding Width, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Draw" Command="{Binding DrawCommand}" Margin="0,8,0,0" />
</StackPanel>
The code-behind is down to one line: DataContext = new ParameterViewModel(service);. UpdateSourceTrigger=PropertyChanged makes the button react while the user is typing, not only when they leave the field.
MVVM in a CAD add-in
MVVM pays off particularly well in palettes and dialogs of CAD add-ins. There, the view model does not call the AutoCAD API but an interface such as IDrawingService. Only its implementation works with AutoCAD and takes care of the rules that apply there:
- A palette lives longer than any drawing. The implementation therefore fetches the active document fresh via
MdiActiveDocumenton every call and checks it fornull. - Clicks run outside any command, in the application context. If you change the drawing directly, lock it with
LockDocument, otherwise you getErrorStatus.LockViolation. It is often better to start a command withSendStringToExecute. It appears on the command line, has its own undo step and can be repeated.
In a unit test, the view model gets a fake implementation of the interface. This lets you check input, commands and states in milliseconds without AutoCAD running. The separation also helps when switching platforms: with a different implementation of the service, the same view model can be reused in BricsCAD or Revit.
How far to go
MVVM is a tool, not a dogma. Code that only concerns the presentation – setting the focus, starting an animation – may stay in the code-behind. The view model opens message boxes and file dialogs through a service of its own, so tests do not get stuck on a window. And for validation messages that should appear right next to the field, there is INotifyDataErrorInfo.
Typical mistakes
- The view model knows controls, colours or
MessageBox. It can then no longer be tested without a user interface. - Forgetting
PropertyChangedor raising it with a misspelled name.nameofhelps. - Never raising
CanExecuteChanged. The button stays grey even though the input is valid. - Binding lists as
List<T>instead ofObservableCollection<T>– new entries don't appear. - Caching a drawing in the view model of a CAD add-in that has long been closed.
- Overlooking binding errors. They only appear in the Visual Studio output window, not as exceptions.
How we help
- Training: The WPF training takes you from XAML through data binding to MVVM. Patterns and architecture are covered in depth in the “C# & .NET (advanced)” training.
- Modernisation: We convert grown WinForms or WPF user interfaces to MVVM step by step, including in CAD add-ins: user interfaces at Cadida.
- Your project: Arrange an initial consultation.
Sources: Ebi F. Eilber, AutoCAD .NET API (book, expected 2027), chapter “GUI programming”; Microsoft Learn: Data binding in WPF; Microsoft Learn: MVVM Toolkit.
