WPF
MVVM
dialogs
software development
C#

Handling Dialogs in WPF with MVVM

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Dialogs are one of the first places where a WPF application can drift away from clean MVVM. It is easy to call MessageBox.Show or new SomeWindow().ShowDialog() directly from a view model, but that ties application logic to UI classes and makes testing painful. A better approach is to keep dialog behavior behind an interface and let the view layer implement the actual window logic.

Why Dialogs Feel Awkward in MVVM

MVVM works because the view model does not know about concrete controls. It exposes state and commands, and the view binds to them. Dialogs complicate that arrangement because showing a dialog is a UI action with UI-specific concerns such as ownership, modality, focus, and return values.

If a view model creates windows directly, a few problems appear immediately:

  • Unit tests now need WPF infrastructure.
  • The view model becomes hard to reuse outside a desktop window.
  • Owner windows, startup positions, and dialog results get scattered through business logic.

The goal is not to avoid dialogs. The goal is to move dialog creation to a service that the view model can call through an abstraction.

A Simple Dialog Service

The smallest useful pattern is an IDialogService interface. The view model depends on the interface, and the application provides a WPF-specific implementation.

csharp
1using System;
2using System.ComponentModel;
3using System.Runtime.CompilerServices;
4using System.Windows;
5using System.Windows.Input;
6
7namespace DialogSample;
8
9public interface IDialogService
10{
11    bool Confirm(string title, string message);
12}
13
14public sealed class DialogService : IDialogService
15{
16    public bool Confirm(string title, string message)
17    {
18        var result = MessageBox.Show(
19            message,
20            title,
21            MessageBoxButton.YesNo,
22            MessageBoxImage.Question);
23
24        return result == MessageBoxResult.Yes;
25    }
26}
27
28public sealed class RelayCommand : ICommand
29{
30    private readonly Action _execute;
31    private readonly Func<bool>? _canExecute;
32
33    public RelayCommand(Action execute, Func<bool>? canExecute = null)
34    {
35        _execute = execute;
36        _canExecute = canExecute;
37    }
38
39    public event EventHandler? CanExecuteChanged;
40
41    public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;
42
43    public void Execute(object? parameter) => _execute();
44
45    public void RaiseCanExecuteChanged() =>
46        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
47}
48
49public sealed class MainViewModel : INotifyPropertyChanged
50{
51    private readonly IDialogService _dialogService;
52    private string _status = "Nothing deleted yet.";
53
54    public MainViewModel(IDialogService dialogService)
55    {
56        _dialogService = dialogService;
57        DeleteCommand = new RelayCommand(DeleteItem);
58    }
59
60    public string Status
61    {
62        get => _status;
63        private set
64        {
65            _status = value;
66            OnPropertyChanged();
67        }
68    }
69
70    public ICommand DeleteCommand { get; }
71
72    private void DeleteItem()
73    {
74        if (_dialogService.Confirm("Delete item", "Delete the selected record?"))
75        {
76            Status = "Record deleted.";
77        }
78        else
79        {
80            Status = "Delete cancelled.";
81        }
82    }
83
84    public event PropertyChangedEventHandler? PropertyChanged;
85
86    private void OnPropertyChanged([CallerMemberName] string? name = null) =>
87        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
88}

The view stays simple because it only binds to the command and state:

xml
1<StackPanel Margin="24">
2    <Button Content="Delete" Command="{Binding DeleteCommand}" Width="120" />
3    <TextBlock Margin="0,12,0,0" Text="{Binding Status}" />
4</StackPanel>

This example is intentionally modest. The key idea is that the view model asks for a confirmation, not for a MessageBox. That distinction is what preserves testability.

Moving to Custom Dialog Windows

Once the service boundary exists, switching from a message box to a custom dialog is straightforward. The service can create a window, assign its DataContext, set Owner = Application.Current.MainWindow, and return ShowDialog().

That gives you a clean place to handle details such as:

  • mapping a dialog view model to a dialog window
  • centering on the owner window
  • passing initial data into the dialog
  • reading a typed result after the dialog closes

For example, an edit dialog can expose properties such as CustomerName and IsConfirmed. The service opens the window, and the main view model only receives the result. The main view model still does not need to know whether the UI was a modal window, a sheet-style experience, or a custom host control.

Testing the ViewModel

The service pattern pays off when you test command logic. A fake implementation can return a predetermined answer without spinning up WPF.

csharp
1public sealed class FakeDialogService : IDialogService
2{
3    private readonly bool _answer;
4
5    public FakeDialogService(bool answer) => _answer = answer;
6
7    public bool Confirm(string title, string message) => _answer;
8}

With that fake, a unit test can verify that Status becomes "Record deleted." when the answer is true, and "Delete cancelled." when the answer is false.

Common Pitfalls

  • Calling ShowDialog() directly in the view model. This is the most common MVVM leak and usually the hardest one to unwind later.
  • Forgetting the owner window. Without Owner, modal behavior, focus restoration, and taskbar stacking can feel wrong.
  • Returning raw UI types from the service. Keep the contract focused on domain-friendly results such as bool, strings, or a dedicated result object.
  • Using the dialog service for long-running work. Dialogs should collect a decision or some input; background operations should still live elsewhere.
  • Hiding validation inside the window code-behind. Validation rules belong in the view model so the dialog can be tested like the rest of the screen.

Summary

  • MVVM does not prevent dialogs; it requires dialog behavior to be abstracted.
  • An IDialogService keeps view models free of Window and MessageBox dependencies.
  • The same pattern works for both simple confirmations and custom modal windows.
  • Setting dialog ownership and returning typed results improves application behavior and maintainability.
  • Fake dialog services make command logic easy to unit test.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.