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
OkCancelDialogAwareViewModeland assign a string to the ViewModel'sContentproperty. - 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
ViewLocatorAttributeapplied to the dialog ViewModel. - Customizable button sets — The base dialog ViewModel class provides standard OK/Cancel buttons, but you can override
CreateButtonsto define any set of buttons with custom captions and placement. - Validation support — Override the
CanOkmethod 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
DialogResultenumeration.
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.
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.
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:
-
Create a View with controls to display in the dialog.
Add a
UserControlcontaining the controls you need. When required, bind its properties to the dialog ViewModel's properties so the two stay in sync. -
Apply
ViewLocatorAttributeto 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.
-
(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;
}

