Skip to content

Latest commit

History

120 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Introduction

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.

GitHub Pages Site

NugetGitHubCodeFactor

https://jamsoft.github.io/JamSoft.AvaloniaUI.Dialogs/

Installation

dotnet add package JamSoft.AvaloniaUI.Dialogs --version 1.4.1
Install-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

Tested On

  • Windows 10 & 11
  • MacOS Sonoma 14.5
  • Pop!_OS 22.04

Sample Application

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. sample-app

Import Styles

All Defaults

<Application.Styles>
<FluentTheme />
<StyleIncludeSource="avares://JamSoft.AvaloniaUI.Dialogs/Themes/Default.axaml"/>
</Application.Styles>

Individual Style Files

<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>

Custom Window Styling

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.

Creating Service Instances

Dialog Service

IDialogServicedialogService=DialogServiceFactory.Create(newDialogServiceConfiguration({ApplicationName="Dialog Sample App",UseApplicationNameInTitle=true,ViewsAssemblyName=Assembly.GetExecutingAssembly().GetName().Name});

MessageBox Service

IMessageBoxServicemsgboxService=DialogServiceFactory.CreateMessageBoxService();

Registration Example Using Splat DI

Registering the Services - Program.cs

publicstaticvoidMain(string[]args){RegisterDependencies();BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);}privatestaticvoidRegisterDependencies()=>BootStrapper.Register(Locator.CurrentMutable,Locator.Current);

Registering the Services - Bootstrapper.cs

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());}}

Usage

Resolving Services

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;}
...

Usage - Message Boxes

basic-message-box

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.

Result Object

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);}

Show Message Box

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);

Custom Icons

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);

Changing Icon Background Color

<StyleSelector="Ellipse#MsgBoxIconBackgroundEllipse">
<SetterProperty="Fill"Value="Red"/>
</Style>

Custom Button Text

varresult=await_messageBoxService.Show("German Yes No Cancel","Möchten Sie weitermachen?",MsgBoxButton.YesNoCancel,MsgBoxImage.Question,"Nein","Ja","Abbrechen");

Show Message Box With Checkbox

basic-message-box-with-checkbox

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;

File Paths

Open Any File

stringpath=await_dialogService.OpenFile("Open Any File");

Open A Specific File Type

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});

Open Multiple Files

string[]paths=await_dialogService.OpenFiles("Open Multiple Files");

Save Any Path

stringpath=await_dialogService.SaveFile("Save Any File");

Save Any Path And Provide a File Name

stringpath=await_dialogService.SaveFile("Save Any File",suggestedFileName:"Suggested Name");

Save To Your Custom File Type

stringpath=await_dialogService.SaveFile("Save New MyApp Project",newList<FileDialogFilter>{new(){Name="MyApp Project",Patterns=newList<string>{"*.myappext"},AppleUniformTypeIdentifiers=newList<string>{"com.myorgname.myappext"},}});

Usage - Dialogs

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.

Show Dialog

basic-dialog

_dialogService.ShowDialog(Locator.Current.GetService<MyDialogViewModel>(),DialogCallback);privatevoidDialogCallback(MyDialogViewModelobj){Message=obj.DialogMessage;}

Custom Button Text

privatevoidShowCustomizedDialogCommandExecuted(){varvm=Locator.Current.GetService<MyDialogViewModel>();vm.AcceptCommandText="Accept";vm.CancelCommandText="Oh No!";_dialogService.ShowDialog(vm,DialogCallback);}

Alternate Views

privatevoidShowCustomizedDialogCommandExecuted(){varvm=Locator.Current.GetService<MyDialogViewModel>();vm.AcceptCommandText="Accept";vm.CancelCommandText="Oh No!";_dialogService.ShowDialog(newMyAlternateDialogView(),vm,DialogCallback);}

Default Key Mapping

The dialog buttons are also associated with their keyboard inputs.

<Window.KeyBindings>
<KeyBindingGesture="Escape"Command="{Binding CancelCommand}" />
<KeyBindingGesture="Enter"Command="{Binding AcceptCommand}" />
</Window.KeyBindings>

Show Child Window

jamsoft-logo

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.

Show Child Window - Alternate View Parameter

privatevoidShowChildWindowCommandExecuted(){varvm=Locator.Current.GetService<MyChildViewModel>();
..._dialogService.ShowChildWindow(newMyAlternateChildView(),vm, model =>{Message=$"Child Closed - {model.ChildMessage}";});}

Wizard Control

The library also has a wizard control allowing multiple page dialogs. You can define a wizard like this:

wizard-control

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.

Saving & Restoring Window Positions

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.

Application Styles

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>

Known Issues

Window sizes and positions on Linux not always accurate due to window manager differences. Under investigation.

About

Provides the ability to show various dialogs and child windows in a DI injectable application dialog 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.

Topics

Resources

Stars

50 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages