Provides the ability to show various dialogs, child windows, message boxes and Wizards in a DI injectable service ready to plug into MVVM AvaloniaUI applications.
The general idea is to make it as simple as possible to handle all the basics of using dialogs with as few assumptions as possible whilst also providing a feature rich experience.
More or less everything is replaceable, extendable & customisable.
https://jamsoft.github.io/JamSoft.AvaloniaUI.Dialogs/
dotnet add package JamSoft.AvaloniaUI.Dialogs --version 1.4.1Install-Package JamSoft.AvaloniaUI.Dialogs -Version 1.4.1<PackageReferenceInclude="JamSoft.AvaloniaUI.Dialogs"Version="1.4.1" />paket add JamSoft.AvaloniaUI.Dialogs --version 1.4.1- Windows 10 & 11
- MacOS Sonoma 14.5
- Pop!_OS 22.04
The sample application demonstrates how to use the library in a real-world scenario. It shows how to use the dialog service to open files, save files, show message boxes, show dialogs and child windows. It also demonstrates how to use the wizard control along with saving and restoring window sizes and positions.

<Application.Styles>
<FluentTheme />
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/Default.axaml"/>
</Application.Styles><Application.Styles>
<FluentTheme />
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/MsgBoxStyles.axaml"/>
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/ChildStyle.axaml"/>
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/ModalStyle.axaml"/>
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/WizardStyle.axaml"/>
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/WizardStepStyle.axaml"/>
</Application.Styles>Since we are using plain old Window objects, basic styling properties like Background colors will be inherited from your own applications default Window style, such as:
<StyleSelector="Window">
<SetterProperty="Background"Value="#333333" />
</Style>The same is true for your default button styles and basic text and font settings so theming things should be little more than plugging in the library and starting to use it.
IDialogServicedialogService=DialogServiceFactory.Create(newDialogServiceConfiguration({ApplicationName="Dialog Sample App",UseApplicationNameInTitle=true,ViewsAssemblyName=Assembly.GetExecutingAssembly().GetName().Name});IMessageBoxServicemsgboxService=DialogServiceFactory.CreateMessageBoxService();publicstaticvoidMain(string[]args){RegisterDependencies();BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);}privatestaticvoidRegisterDependencies()=>BootStrapper.Register(Locator.CurrentMutable,Locator.Current);publicclassBootStrapper{publicstaticvoidRegister(IMutableDependencyResolverservices,IReadonlyDependencyResolverresolver){services.RegisterLazySingleton(()=>DialogServiceFactory.Create(newDialogServiceConfiguration{ApplicationName="Dialog Sample App",UseApplicationNameInTitle=true,ViewsAssemblyName="JamSoft.AvaloniaUI.Dialogs.Sample"}));services.RegisterLazySingleton(DialogServiceFactory.CreateMessageBoxService);services.Register(()=>newMainWindowViewModel(resolver.GetService<IDialogService>()!,resolver.GetService<IMessageBoxService>()!));services.Register(()=>newMyDialogViewModel());services.Register(()=>newMyChildViewModel());}}Now that we have this setup and registered, we can make use of the service from view models like this. First, add it as a constructor parameter.
privatereadonlyIDialogService_dialogService;privatereadonlyIMessageBoxService_messageBoxService;publicMainWindowViewModel(IDialogService dialogService,IMessageBoxService messageBoxService){_dialogService=dialogService;_messageBoxService=messageBoxService;}
...The message box implementation closely follows the .NET/Forms/WPF MessageBox class. It provides a simple way to show message boxes with various button configurations and icons.
The Show method returns a MsgBoxResult object which contains the button result that was clicked by the user. This can be used to determine the action to take in your application.
If the checkbox is also used, by providing text for the CheckBox message in the Show call, the result can be checked in the returned MsgBoxResult object.
publicsealedclassMsgBoxResult{publicMsgBoxButtonResultButtonResult{get;}publicboolCheckBoxResult{get;}privateMsgBoxResult(boolcheckBoxResult,MsgBoxButtonResultbuttonResult){CheckBoxResult=checkBoxResult;ButtonResult=buttonResult;}publicstaticMsgBoxResultCreateResult(boolcheckBoxChecked,MsgBoxButtonResultbuttonResult)=>new(checkBoxChecked,buttonResult);}varmsgbResult=await_messageBoxService.Show("OK Cancel","Do you want to carry on?",MsgBoxButton.OkCancel,MsgBoxImage.Question);You can also pass a view model instance to the Show method to customise the message box using the default provided MsgBoxViewModel class.
varviewModel=newMsgBoxViewModel("Yes No With Icon","Do you want to carry on?",MsgBoxButton.YesNo,MsgBoxImage.Warning);varbtnResult=await_messageBoxService.Show(viewModel);You can also use any custom view model class by implementing the IMessageBoxViewModel interface.
publicclassMyCustomMsgBoxViewModel:IMsgBoxViewModel{}varmyCustomMsgBoxViewModel=newMyCustomMsgBoxViewModel();
...var btnResult =await_messageBoxService.Show(myCustomMsgBoxViewModel);varviewModel=newMsgBoxViewModel("Yes No With Icon","Do you want to carry on?",MsgBoxButton.YesNo,MsgBoxImage.Custom);viewModel.Icon=newBitmap("myicon.png");varresult=await_messageBoxService.Show(viewModel);<StyleSelector="Ellipse#MsgBoxIconBackgroundEllipse">
<SetterProperty="Fill"Value="Red"/>
</Style>varresult=await_messageBoxService.Show("German Yes No Cancel","Möchten Sie weitermachen?",MsgBoxButton.YesNoCancel,MsgBoxImage.Question,"Nein","Ja","Abbrechen");varmsgBoxResult=await_messageBoxService.Show("OK Cancel With Checkbox","Do you want to carry on?",MsgBoxButton.OkCancel,MsgBoxImage.Error,checkBoxText:"Don't ask me again");msgBoxResult.ButtonResult;msgBoxResult.CheckBoxResult;stringpath=await_dialogService.OpenFile("Open Any File");stringpath=await_dialogService.OpenFile("Open Word File",newList<FileDialogFilter>{new("Word Files"){Patterns=newList<string>{"*.docx","*.doc"},AppleUniformTypeIdentifiers=newList<string>{"com.microsoft.word.doc","org.openxmlformats.wordprocessingml.document"},MimeTypes=newList<string>{"application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document"}}});You can also make use of the built in CommonFilters helper class.
stringpath=await_dialogService.OpenFile("Open Word File",newList<FileDialogFilter>{CommonFilters.WordFilter});string[]paths=await_dialogService.OpenFiles("Open Multiple Files");stringpath=await_dialogService.SaveFile("Save Any File");stringpath=await_dialogService.SaveFile("Save Any File",suggestedFileName:"Suggested Name");stringpath=await_dialogService.SaveFile("Save New MyApp Project",newList<FileDialogFilter>{new(){Name="MyApp Project",Patterns=newList<string>{"*.myappext"},AppleUniformTypeIdentifiers=newList<string>{"com.myorgname.myappext"},}});There are two base view model classes already baked in for ease of use of the library. These are provided as defaults and a starting point. Create a suitable view model and inherit from either DialogViewModel or ChildWindowViewModel as base class.
_dialogService.ShowDialog(Locator.Current.GetService<MyDialogViewModel>(),DialogCallback);privatevoidDialogCallback(MyDialogViewModelobj){Message=obj.DialogMessage;}privatevoidShowCustomizedDialogCommandExecuted(){varvm=Locator.Current.GetService<MyDialogViewModel>();vm.AcceptCommandText="Accept";vm.CancelCommandText="Oh No!";_dialogService.ShowDialog(vm,DialogCallback);}privatevoidShowCustomizedDialogCommandExecuted(){varvm=Locator.Current.GetService<MyDialogViewModel>();vm.AcceptCommandText="Accept";vm.CancelCommandText="Oh No!";_dialogService.ShowDialog(newMyAlternateDialogView(),vm,DialogCallback);}The dialog buttons are also associated with their keyboard inputs.
<Window.KeyBindings>
<KeyBindingGesture="Escape"Command="{Binding CancelCommand}" />
<KeyBindingGesture="Enter"Command="{Binding AcceptCommand}" />
</Window.KeyBindings>privatevoidShowChildWindowCommandExecuted(){varvm=Locator.Current.GetService<MyChildViewModel>();// these values could be stored in user settings and loaded at runtime etc.vm.RequestedLeft=50;vm.RequestedTop=50;vm.RequestedHeight=600;vm.RequestedWidth=800;vm.ChildMessage="Child Message Value";vm.ChildWindowTitle="My Child Window Title";_dialogService.ShowChildWindow(vm, model =>{Message=$"Child Closed - {model.ChildMessage}";});}The child windows are draggable and also update these properties in real-time. This means that your application can easily restore child window positions between application runs by storing these values.
privatevoidShowChildWindowCommandExecuted(){varvm=Locator.Current.GetService<MyChildViewModel>();
..._dialogService.ShowChildWindow(newMyAlternateChildView(),vm, model =>{Message=$"Child Closed - {model.ChildMessage}";});}The library also has a wizard control allowing multiple page dialogs. You can define a wizard like this:
privatevoidWizardViewCommandExecuted(){varvm=Locator.Current.GetService<MyWizardViewModel>();vm.RequestedLeft=MyUserSettings.Instance.Left;vm.RequestedTop=MyUserSettings.Instance.Top;vm.RequestedHeight=MyUserSettings.Instance.Height;vm.RequestedWidth=MyUserSettings.Instance.Width;vm.ChildWindowTitle="My Wizard";_dialogService.StartWizard(vm, model =>{Message=$"Wizard Closed - {model.GetType()}";});}<controls:WizardButtonPlacement="Bottom"ProgressPlacement="Bottom">
<controls:WizardStepHeader="Page 1"StepComplete="{Binding WizardStepOneComplete}">
<controls:WizardStep.Content>
<StackPanelOrientation="Vertical"Spacing="20">
<TextBlock>Page 1</TextBlock>
<TextBoxForeground="White"Text="{Binding ValueOne}"/>
</StackPanel>
</controls:WizardStep.Content>
</controls:WizardStep>
<controls:WizardStepHeader="Page 2"StepComplete="{Binding WizardStepTwoComplete}">
<controls:WizardStep.Content>
<StackPanelOrientation="Vertical"Spacing="20">
<TextBlock>Page 2</TextBlock>
<TextBoxForeground="White"Text="{Binding ValueTwo}"/>
</StackPanel>
</controls:WizardStep.Content>
</controls:WizardStep>
<controls:WizardStepHeader="Page 3"StepComplete="{Binding WizardStepThreeComplete}">
<controls:WizardStep.Content>
<StackPanelOrientation="Vertical"Spacing="20">
<TextBlock>Page 3</TextBlock>
<TextBoxForeground="White"Text="{Binding ValueThree}"/>
</StackPanel>
</controls:WizardStep.Content>
</controls:WizardStep>
<controls:WizardStepHeader="Page 4"StepComplete="{Binding WizardStepFourComplete}">
<controls:WizardStep.Content>
<StackPanelOrientation="Vertical"Spacing="20">
<TextBlock>Final Step</TextBlock>
<TextBoxForeground="White"Text="{Binding ValueFour}"/>
</StackPanel>
</controls:WizardStep.Content>
</controls:WizardStep>
</controls:Wizard>The WizardStep defines a bindable property called StepComplete which you can bind in your view model to control step validation and navigation. It also makes use of the ChildWindow so inherits the position awareness should you want that functionality.
First you need a mechanism to store positions as set by the user moving things around.
publicclassMyUserSettings:SettingsBase<MyUserSettings>{publicdoubleLeft{get;set;}=50;publicdoubleTop{get;set;}=50;publicdoubleHeight{get;set;}=600;publicdoubleWidth{get;set;}=800;}SettingsBase<T> can be found in the JamSoft.Helpers package https://github.com/jamsoft/JamSoft.Helpers
Nuget - https://www.nuget.org/packages/JamSoft.Helpers
Then in your view model you can listen for the RequestCloseDialog event and respond accordingly by storing the settings in the OnRequestCloseDialog method.
publicclassMyChildWindowViewModel:ChildWindowViewModel{privatestring?_childMessage;publicMyChildWindowViewModel(){RequestCloseDialog+=OnRequestCloseDialog;}publicstring?ChildMessage{get=>_childMessage;set=>RaiseAndSetIfChanged(ref_childMessage,value);}privatevoidOnRequestCloseDialog(object?sender,RequestCloseDialogEventArgse){MyUserSettings.Instance.Top=RequestedTop;MyUserSettings.Instance.Left=RequestedLeft;MyUserSettings.Instance.Width=RequestedWidth;MyUserSettings.Instance.Height=RequestedHeight;RequestCloseDialog-=OnRequestCloseDialog;}}The next time this view model is requested by the user you can then restore these values.
vm.RequestedLeft=MyUserSettings.Instance.Left;vm.RequestedTop=MyUserSettings.Instance.Top;vm.RequestedHeight=MyUserSettings.Instance.Height;vm.RequestedWidth=MyUserSettings.Instance.Width;vm.ChildWindowTitle="My Custom Child Window Title";_dialogService.ShowChildWindow(vm, model =>{Message=$"Child Remember Position Closed - {model.GetType()}";});See the Sample Application for a complete implementation example and further guidance.
You can easily target elements of the dialogs via their names and types, such as:
<Application>
<Application.Styles>
<StyleSelector="Button.CloseChildButton:pointerover /template/ Border">
<SetterProperty="Background"Value="#c42b1c" />
</Style>
<StyleSelector="DockPanel#ChromeDockPanel">
<SetterProperty="Height"Value="32"/>
<SetterProperty="Background"Value="#000000"/>
</Style>
<StyleSelector="TextBlock#ChromeDockPanelTitle">
<SetterProperty="FontSize"Value="12"/>
<SetterProperty="VerticalAlignment"Value="Center" />
<SetterProperty="Margin"Value="10,0"/>
</Style>
<!-- Override Wizard complete ellipse fill and stroke -->
<StyleSelector="controls|WizardStep:complete /template/ Ellipse#PART_SelectedPipe">
<SetterProperty="Fill"Value="DeepPink" />
<SetterProperty="StrokeThickness"Value="0" />
</Style>
<!-- Hide the child window Chrome -->
<StyleSelector="DockPanel#ChromeDockPanel">
<SetterProperty="IsVisible"Value="False" />
</Style>
</Application.Styles>
</Application>Window sizes and positions on Linux not always accurate due to window manager differences. Under investigation.




