Skip to content

IDialogService

IDialogService provides a platform-agnostic way to show modal dialogs. From your ViewModels, you can open and handle modal dialogs using dedicated dialog ViewModels. This keeps your ViewModels completely decoupled from Avalonia window types.

Main features include:

  • ViewModel-driven dialogs — A dialog is defined entirely by a ViewModel. The ViewModel supplies the dialog title, content, and buttons.
  • Simple dialogs — No need to define a dialog View if you only want to display plain text with a button set. Just create a dialog ViewModel by deriving it from OkCancelDialogAwareViewModel and assign a string to the ViewModel's Content property.
  • Custom dialog Views — You can create a custom dialog View to render it as the dialog's inner content. This View is located automatically through the ViewLocatorAttribute applied to the dialog ViewModel.
  • Customizable button sets — The base dialog ViewModel class provides standard OK/Cancel buttons, but you can override CreateButtons to define any set of buttons with custom captions and placement.
  • Validation support — Override the CanOk method in your dialog ViewModel to disable the default button until the input is valid.
  • Nested modals — A dialog can open another modal dialog on top of itself. The nested dialog blocks its parent, not the whole application.
  • Dialog result — After the dialog is closed, it returns the result specified by the DialogResult enumeration.

Interface Definition

public interface IDialogService
{
    // Show a modal dialog driven by a ViewModel.
    DialogResult ShowDialog<T>(T viewModel, string? caption = null)
        where T : IDialogAwareViewModel;
}
Parameter Type Description
viewModel T The dialog ViewModel. It supplies the dialog content and buttons. The ViewModel is assigned to the dialog's Data Context. You can create your ViewModel by deriving it from the OkCancelDialogAwareViewModel class, which already implements the IDialogAwareViewModel interface. OkCancelDialogAwareViewModel hosts the OK and Cancel buttons, which can be replaced with a custom button set.
caption string? The dialog's caption. When set to null, the caption is taken from the dialog ViewModel's Title property (IWindowAwareViewModel.Title).

Returns: DialogResult — the button the dialog was closed with, or DialogResult.None if the user dismissed the dialog through the window caption button.

Dialog ViewModel Hierarchy

Your dialog ViewModel must implement the IDialogAwareViewModel interface. Typically you derive your dialog ViewModel from the OkCancelDialogAwareViewModel class, which already implements this interface. The OkCancelDialogAwareViewModel class contains the OK and Cancel buttons, which you can replace with custom buttons.

The following scheme shows the base class hierarchy for dialog ViewModels:

IWindowAwareViewModel
    └── IDialogAwareViewModel
            └── WindowAwareViewModel (abstract class)
                    └── DialogAwareViewModel (abstract class)
                            └── OkCancelDialogAwareViewModel (abstract class)
Class Purpose
WindowAwareViewModel The base class for ViewModels shown in a window. It tracks the window the ViewModel is attached to, closes it on a request, and creates the View declared by the ViewLocatorAttribute attribute.
DialogAwareViewModel The base class for ViewModels shown as modal dialogs. This class adds support for dialog buttons. You can override the CreateButtons method to define a custom button set.
OkCancelDialogAwareViewModel Provides the standard OK and Cancel buttons wired to the virtual OkCancelDialogAwareViewModel.Ok and OkCancelDialogAwareViewModel.Cancel methods. You can override the CanOk method to disable the OK button according to your dialog validation rules.

How to Use IDialogService

In your ViewModel, call the IDialogService.ShowDialog method to display a modal dialog. The method returns the dialog result (the button the user pressed).

Access the Service

There are two ways to access an IDialogService object in a ViewModel:

  • Through the Service<T>() helper method. This is a convenient approach to obtain registered application services shipped with the Eremex Controls library.
  • Through constructor injection.

Service() Helper

Implement a helper Service<T>() method in your ViewModel to get a requested service using the static ApplicationServicesContext.GetRequiredService<T>() method.

using Eremex.AvaloniaUI.Controls.ApplicationServices;
using CommunityToolkit.Mvvm.Input;

public partial class MyViewModel : ObservableObject
{
    // Provides access to any registered service
    protected static T Service<T>() where T : class
        => ApplicationServicesContext.GetRequiredService<T>();

    [RelayCommand]
    private void ShowDialog()
    {
        // Create the dialog view model
        var dialogViewModel = new MyDialogViewModel("Dialog Title", "Dialog content");

        // Resolve the service and show the dialog
        var result = Service<IDialogService>()
            .ShowDialog(dialogViewModel, caption: null);
    }
}

In the App.axaml.cs file, ensure that Eremex application services are registered using SimpleServiceProvider and ApplicationServicesContext as follows:

public class App : Application
{
    public override void OnFrameworkInitializationCompleted()
    {
        RegisterApplicationServices();
        //...
    }

    static void RegisterApplicationServices()
    {
        // Register built-in services.
        var serviceProvider = new SimpleServiceProvider();
        ApplicationServicesContext.RegisterApplicationServices(serviceProvider.AddSingleton);
        ApplicationServicesContext.SetCurrent(serviceProvider);
    }
}

Constructor Injection

Implement a constructor in your ViewModel with IDialogService as a parameter. When you instantiate the ViewModel, pass the service object to this constructor.

public partial class MyViewModel
{
    private readonly IDialogService _dialogService;

    public MyViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;
    }

    [RelayCommand]
    private void ShowDialog()
    {
        var dialogViewModel = new MyDialogViewModel("Dialog Title", "Dialog content");
        var result = _dialogService.ShowDialog(dialogViewModel, caption: null);
        //...
    }
}

Create a Dialog ViewModel

A dialog ViewModel supplies the dialog content and buttons. The simplest approach is to derive from OkCancelDialogAwareViewModel, which provides the OK and Cancel buttons out of the box.

Tip

OkCancelDialogAwareViewModel allows you to replace the OK and Cancel buttons with a custom button set. Each button can close the dialog with a specific result (a DialogResult value).

The following example shows how to show a dialog with the OK and Cancel buttons. A ViewModel is created by deriving from the OkCancelDialogAwareViewModel class. The strings passed to the ViewModel's constructor are used to initialize the dialog's title and content.

app-services-idialogservice-ok-cancel

using Eremex.AvaloniaUI.Controls.ApplicationServices;

public class MyDialogViewModel : OkCancelDialogAwareViewModel
{
    public MyDialogViewModel(string title, string message) : base(title)
    {
        Content = message;
    }
}

To show this dialog, use the following code:

var result = Service<IDialogService>()
    .ShowDialog(new MyDialogViewModel("Dialog Title", "Dialog content"));

Custom Button Sets

Override the CreateButtons method in your dialog ViewModel to replace the standard button set. You can use the built-in button factories (CreateOk, CreateCancel, CreateApply) to create the standard OK, Cancel, and Apply buttons. Use the CreateCustom method to create custom buttons.

The following example shows a dialog with the Yes, No, and Cancel buttons. This dialog is represented by DialogServiceYesNoCancelViewModel derived from the OkCancelDialogAwareViewModel class. The CreateButtons method is overridden to create the custom button set.

The Yes button is set as the default button. The default button's command is invoked when a user presses ENTER in the dialog. The built-in Cancel button has the IsCancel option enabled. This option activates this button's command when the ESC key is pressed.

app-services-idialogservice-yes-no-cancel-buttons

using CommunityToolkit.Mvvm.Input;
using Eremex.AvaloniaUI.Controls.ApplicationServices;

/// <summary>
/// IDialogService with the Yes, No, and Cancel buttons.
/// </summary>
public partial class DialogServiceYesNoCancelViewModel : OkCancelDialogAwareViewModel
{
    public DialogServiceYesNoCancelViewModel(string title, string message) : base(title)
    {
        Content = message;
    }

    [RelayCommand]
    public void Yes(object parameter) => CloseWindow(DialogResult.Yes);

    [RelayCommand]
    public void No(object parameter) => CloseWindow(DialogResult.No);

    protected override IDialogButtonViewModel[] CreateButtons()
    {
        var buttons = new IDialogButtonViewModel[]
        {
            DialogButtonViewModel.CreateCustom(YesCommand, "Yes", classes: "accent"),
            DialogButtonViewModel.CreateCustom(NoCommand, "No"),
            DialogButtonViewModel.CreateCancel(CancelCommand),
        };
        (buttons[0] as DialogButtonViewModel).IsDefault = true;
        return buttons;
    }
}

To show this dialog, use the following code:

var result = Service<IDialogService>().ShowDialog(new DialogServiceYesNoCancelViewModel("Warning", "Save the changes?"));

Custom Captions and Placement

You can create custom buttons by overriding the DialogAwareViewModel.CreateButtons method and using the static DialogButtonViewModel.CreateCustom method within it. The CreateCustom method allows you specify a button caption and placement.

The following example shows a dialog with the Yes, No, and Cancel buttons. The Yes and No buttons are placed to the left of the Cancel button using the dockType parameter.

using CommunityToolkit.Mvvm.Input;
using Eremex.AvaloniaUI.Controls.ApplicationServices;

public partial class DialogServiceNoYesCancelViewModel : OkCancelDialogAwareViewModel
{
    public DialogServiceNoYesCancelViewModel(string title, string message) : base(title)
    {
        Content = message;
    }

    [RelayCommand]
    public void Yes(object parameter) => CloseWindow(DialogResult.Yes);

    [RelayCommand]
    public void No(object parameter) => CloseWindow(DialogResult.No);

    protected override IDialogButtonViewModel[] CreateButtons() => new[]
    {
        DialogButtonViewModel.CreateCancel(CancelCommand),
        DialogButtonViewModel.CreateCustom(NoCommand, "No", dockType: DockType.Left),
        DialogButtonViewModel.CreateCustom(YesCommand, "Yes", dockType: DockType.Left),
    };
}

To show this dialog, use the following code:

var result = Service<IDialogService>().ShowDialog(new DialogServiceNoYesCancelViewModel("Info", "Custom button placement"));

Custom Dialog Content and Validation

By default, a dialog displays a string assigned to the dialog ViewModel's Content property (WindowAwareViewModel.Content). You can also display custom content instead of a simple string.

To provide custom content for a dialog:

  1. Create a View with controls to display in the dialog.

    Add a UserControl containing the controls you need. When required, bind its properties to the dialog ViewModel's properties so the two stay in sync.

  2. Apply ViewLocatorAttribute to the dialog ViewModel.

    Decorate the dialog ViewModel with ViewLocatorAttribute, passing the type of the view you created. The attribute associates the view with the ViewModel. The dialog service uses it to create and display the view inside the dialog's client area (above the dialog's footer buttons).

When the ViewLocatorAttribute attribute is present, the base WindowAwareViewModel creates the view automatically when the dialog is opened. This view is assigned to the Content property. The ViewModel never needs to reference the view type directly.

Tip

You can also manually set the Content property for the dialog ViewModel, without using the ViewLocatorAttribute.

  1. (Optional) Control the availability of the dialog's OK button

    You can override the dialog ViewModel's CanOk method to disable/enable the OK button according to your requirements.

Example

The following example shows a dialog with a text field and OK/Cancel buttons. The text field comes from a custom view (DialogServiceValidationView). The CanOk method is overridden to keep the OK button disabled while the text field is empty.

ViewModel

using DemoCenter.Views.ApplicationServices;

using Eremex.AvaloniaUI.Controls.ApplicationServices;

namespace DemoCenter.ViewModels.ApplicationServices;

/// <summary>
/// IDialogService with validation: Ok stays disabled until the field is filled in.
/// The view is picked by <see cref="ViewLocatorAttribute"/>, so the view model does not
/// need to know how it is displayed.
/// </summary>
/// <remarks>
/// The property is written by hand: dialog view models derive from the Eremex base class
/// rather than the CommunityToolkit ObservableObject, so the [ObservableProperty] generator
/// does not apply to them.
/// </remarks>
[ViewLocator(typeof(DialogServiceValidationView))]
public class DialogServiceValidationViewModel : OkCancelDialogAwareViewModel
{
    private string userName = string.Empty;

    public DialogServiceValidationViewModel(string title) : base(title)
    {
    }

    public string UserName
    {
        get => userName;
        set
        {
            if (userName == value)
                return;

            userName = value;
            OnPropertyChanged(nameof(UserName));
            OkCommand.NotifyCanExecuteChanged();
        }
    }

    public override bool CanOk(object parameter) => !string.IsNullOrWhiteSpace(UserName);
}

View

<UserControl x:Class="DemoCenter.Views.ApplicationServices.DialogServiceValidationView"
             xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mxe="https://schemas.eremexcontrols.net/avalonia/editors"
             xmlns:vm="using:DemoCenter.ViewModels.ApplicationServices"
             x:CompileBindings="True"
             x:DataType="vm:DialogServiceValidationViewModel"
             MinWidth="380">

    <StackPanel Margin="20" Spacing="10">
        <TextBlock Text="The Ok button stays disabled until the field is filled in."
                   TextWrapping="Wrap" />
        <mxe:TextEditor EditorValue="{Binding UserName, Mode=TwoWay}" />
    </StackPanel>
</UserControl>

Show Dialog

To show this dialog, use the following code:

var result = Service<IDialogService>().ShowDialog(new DialogServiceValidationViewModel("Custom dialog content and validation"));

Nested Modal Dialogs

A dialog can open another modal dialog on top of itself.

The following example shows a dialog with the "Nested dialog" button. A click on this button displays another dialog.

using CommunityToolkit.Mvvm.Input;
using Eremex.AvaloniaUI.Controls.ApplicationServices;

// The primary dialog ViewModel, which contains a button that invokes a nested dialog.
public partial class DialogServiceNestedViewModel : OkCancelDialogAwareViewModel
{
    private readonly IDialogService dialogService;

    public DialogServiceNestedViewModel(string title, IDialogService dialogService) : base(title)
    {
        this.dialogService = dialogService;
        Content = "Press \"Nested dialog\" to open a modal dialog on top of this one. "
            + "The nested dialog blocks this window only.";
    }

    [RelayCommand]
    public void ShowNested(object parameter)
    {
        var nested = new DialogServiceOkCancelViewModel(
            "Nested dialog",
            "This modal dialog is owned by the dialog underneath.");

        dialogService.ShowDialog(nested, caption: null);
    }

    protected override IDialogButtonViewModel[] CreateButtons() => new[]
    {
        DialogButtonViewModel.CreateCustom(ShowNestedCommand, "Nested dialog", dockType: DockType.Left),
        DialogButtonViewModel.CreateOk(OkCommand),
        DialogButtonViewModel.CreateCancel(CancelCommand),
    };
}

// The simplest nested dialog ViewModel
public class DialogServiceOkCancelViewModel : OkCancelDialogAwareViewModel
{
    public DialogServiceOkCancelViewModel(string title, string message) : base(title)
    {
        Content = message;
    }
}

To show this dialog, use the following code:

var result = Service<IDialogService>().ShowDialog(new DialogServiceNestedViewModel("Dialog that shows a nested dialog", Service<IDialogService>()));

Accent a Button

You can accent a specific button in the dialog to make it stand out visually. For instance, you can accent the default button to visually highlight it.

Tip

The default button is the one pressed when the user presses ENTER.

To accent a button, include "accent" in the button's classes parameter of the CreateCustom method.

In the following example, the Yes button is accented. The button's IsDefault property is enabled to mark this button as the dialog's default button.

    protected override IDialogButtonViewModel[] CreateButtons()
    {
        var buttons = new IDialogButtonViewModel[]
        {
            DialogButtonViewModel.CreateCustom(YesCommand, "Yes", classes: "accent"),
            DialogButtonViewModel.CreateCustom(NoCommand, "No"),
            DialogButtonViewModel.CreateCancel(CancelCommand),
        };
        (buttons[0] as DialogButtonViewModel).IsDefault = true;
        return buttons;
    }