JamSoft.AvaloniaUI.Dialogs 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package JamSoft.AvaloniaUI.Dialogs --version 1.1.0                
NuGet\Install-Package JamSoft.AvaloniaUI.Dialogs -Version 1.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JamSoft.AvaloniaUI.Dialogs" Version="1.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add JamSoft.AvaloniaUI.Dialogs --version 1.1.0                
#r "nuget: JamSoft.AvaloniaUI.Dialogs, 1.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install JamSoft.AvaloniaUI.Dialogs as a Cake Addin
#addin nuget:?package=JamSoft.AvaloniaUI.Dialogs&version=1.1.0

// Install JamSoft.AvaloniaUI.Dialogs as a Cake Tool
#tool nuget:?package=JamSoft.AvaloniaUI.Dialogs&version=1.1.0                

Introduction

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.

More or less everything is replaceable, extendable, customisable.

GitHub Pages Site

Nuget GitHub

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

Installation

dotnet add package JamSoft.AvaloniaUI.Dialogs --version 1.0.0
Install-Package JamSoft.AvaloniaUI.Dialogs -Version 1.0.0
<PackageReference Include="JamSoft.AvaloniaUI.Dialogs" Version="1.0.0" />
paket add JamSoft.AvaloniaUI.Dialogs --version 1.0.0

Import Styles

All Defaults

<Application.Styles>
    <FluentTheme Mode="Dark"/>
    <StyleInclude Source="avares://JamSoft.AvaloniaUI.Dialogs/Themes/Default.axaml"/>
</Application.Styles>

Individual

<Application.Styles>
    <FluentTheme Mode="Dark"/>
    <StyleInclude Source="avares://JamSoft.AvaloniaUI.Dialogs/Themes/ChildStyle.axaml"/>
    <StyleInclude Source="avares://JamSoft.AvaloniaUI.Dialogs/Themes/ModalStyle.axaml"/>
</Application.Styles>

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

<Style Selector="Window">
    <Setter Property="Background" Value="#333333" />
</Style>

Creating Service Instances

IDialogService dialogService = DialogServiceFactory.Create(new DialogServiceConfiguration({
    ApplicationName = "Dialog Sample App", 
    UseApplicationNameInTitle = true,
    ViewsAssemblyName = Assembly.GetExecutingAssembly().GetName().Name
});

Registration Example Using Splat DI

public static void Main(string[] args)
{
    RegisterDependencies();
    
    BuildAvaloniaApp()
        .StartWithClassicDesktopLifetime(args);
}

private static void RegisterDependencies() =>
    BootStrapper.Register(Locator.CurrentMutable, Locator.Current);
        
Registering the Service - Bootstrapper.cs
public class BootStrapper
{
    public static void Register(IMutableDependencyResolver services, IReadonlyDependencyResolver resolver)
    {
        services.RegisterLazySingleton(() => DialogServiceFactory.Create(new DialogServiceConfiguration
        {
            ApplicationName = "Dialog Sample App", 
            UseApplicationNameInTitle = true,
            ViewsAssemblyName = "JamSoft.AvaloniaUI.Dialogs.Sample"
        }));
        
        services.Register(() => new MainWindowViewModel(resolver.GetService<IDialogService>()));
        services.Register(() => new MyDialogViewModel());
        services.Register(() => new MyChildViewModel());
    }
}        

Usage

Sample Application

sample-app

File Paths

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.

private readonly IDialogService _dialogService;

public MainWindowViewModel(IDialogService dialogService)
{
    _dialogService = dialogService;
}
...

Open Any File

string path = await _dialogService.OpenFile("Open Any File");

Open A Specific File Type

string path = await _dialogService.OpenFile("Open Word File", new List<FileDialogFilter>
{
    new()
    {
        Name = "Docx Word File", 
        Extensions = new List<string> { "docx" }
    }
});

You can also make use of the built in CommonFilters helper class.

string path = await _dialogService.OpenFile("Open Word File", new List<FileDialogFilter>
{
    CommonFilters.WordFilter
});

Open Multiple Files

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

Save Any Path

string path = await _dialogService.SaveFile("Save Any File");

Save To Your Custom File Type

string path = await _dialogService.SaveFile("Save New MyApp Project", new List<FileDialogFilter>
{
    new()
    {
        Name = "MyApp Project", 
        Extensions = new List<string> { "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);

private void DialogCallback(MyDialogViewModel obj)
{
    Message = obj.DialogMessage;
}

Custom Button Text

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

Alternate View

private void ShowCustomizedDialogCommandExecuted()
{
    var vm = Locator.Current.GetService<MyDialogViewModel>();
    vm.AcceptCommandText = "Accept";
    vm.CancelCommandText = "Oh No!";
    
    _dialogService.ShowDialog(new MyAlternateDialogView(), vm, DialogCallback);
}

Default Key Mapping

The dialog buttons are also associated with their keyboard inputs.

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

Show Child Window

jamsoft-logo

private void ShowChildWindowCommandExecuted()
{
    var vm = 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

private void ShowChildWindowCommandExecuted()
{
    var vm = Locator.Current.GetService<MyChildViewModel>();

    ...
    
    _dialogService.ShowChildWindow(new MyAlternateChildView(), 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

private void WizardViewCommandExecuted()
{
    var vm = 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:Wizard ButtonPlacement="Bottom" ProgressPlacement="Bottom">
    
    <controls:WizardStep Header="Page 1" StepComplete="{Binding WizardStepOneComplete}">
        <controls:WizardStep.Content>
            <StackPanel Orientation="Vertical" Spacing="20">
                <TextBlock>Page 1</TextBlock>
                <TextBox Foreground="White" Text="{Binding ValueOne}"/>
            </StackPanel>
        </controls:WizardStep.Content>
    </controls:WizardStep>
    
    <controls:WizardStep Header="Page 2" StepComplete="{Binding WizardStepTwoComplete}">
        <controls:WizardStep.Content>
            <StackPanel Orientation="Vertical" Spacing="20">
                <TextBlock>Page 2</TextBlock>
                <TextBox Foreground="White" Text="{Binding ValueTwo}"/>
            </StackPanel>
        </controls:WizardStep.Content>
    </controls:WizardStep>
    
    <controls:WizardStep Header="Page 3" StepComplete="{Binding WizardStepThreeComplete}">
        <controls:WizardStep.Content>
            <StackPanel Orientation="Vertical" Spacing="20">
                <TextBlock>Page 3</TextBlock>
                <TextBox Foreground="White" Text="{Binding ValueThree}"/>
            </StackPanel>
        </controls:WizardStep.Content>
    </controls:WizardStep>
    
    <controls:WizardStep Header="Page 4" StepComplete="{Binding WizardStepFourComplete}">
        <controls:WizardStep.Content>
            <StackPanel Orientation="Vertical" Spacing="20">
                <TextBlock>Final Step</TextBlock>
                <TextBox Foreground="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.

public class MyUserSettings : SettingsBase<MyUserSettings>
{
    public double Left { get; set; } = 50;
    public double Top { get; set; } = 50;
    public double Height { get; set; } = 600;
    public double Width { get; set; } = 800;
}

SettingsBase<T> can be found in the JamSoft.Helpers package https://github.com/jamsoft/JamSoft.Helpers

Install-Package 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.

public class MyChildWindowViewModel : ChildWindowViewModel
{
    private string? _childMessage;
    
    public MyChildWindowViewModel()
    {
        RequestCloseDialog += OnRequestCloseDialog;
    }
    
    public string? ChildMessage
    {
        get => _childMessage;
        set => RaiseAndSetIfChanged(ref _childMessage, value);
    }
    
    private void OnRequestCloseDialog(object? sender, RequestCloseDialogEventArgs e)
    {
        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 Auto Find";

_dialogService.ShowChildWindow(vm, model =>
{
    Message = $"Child Remember Position Closed - {model.GetType()}";
});

See the Sample Application for a complete implementation example and guidance.

Application Styles

You can easily target elements of the dialogs via their names and types, such as:

<Application>
    <Application.Styles>        
        <Style Selector="Button.CloseChildButton:pointerover /template/ Border">
                <Setter Property="Background" Value="#c42b1c" />
        </Style>
        
        <Style Selector="DockPanel#ChromeDockPanel">
                <Setter Property="Height" Value="32"/>
                <Setter Property="Background" Value="#000000"/>
        </Style>
        
        <Style Selector="TextBlock#ChromeDockPanelTitle">
                <Setter Property="FontSize" Value="12"/>
                <Setter Property="VerticalAlignment" Value="Center" />
                <Setter Property="Margin" Value="10,0"/>
        </Style>        
    </Application.Styles>
</Application>
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.4.0 214 6/5/2024
1.3.1 117 6/3/2024
1.3.0 120 5/26/2024
1.2.2 132 5/25/2024
1.2.1 110 5/24/2024
1.2.0 128 4/22/2024
1.1.4 207 6/18/2023
1.1.3 229 4/16/2023
1.1.2 222 3/6/2023
1.1.1 245 3/1/2023
1.1.0 258 3/1/2023
1.0.0 237 2/17/2023