diff --git a/Directory.Packages.props b/Directory.Packages.props
index b81cd74..f57884b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -2,21 +2,22 @@
true
false
- 10.0.8
- 12.0.4
+ 10.0.10
+ 12.1.0
-
+
-
-
+
+
+
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/AvaloniaDialogService.cs b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/AvaloniaDialogService.cs
new file mode 100644
index 0000000..005520b
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/AvaloniaDialogService.cs
@@ -0,0 +1,166 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using EgoEngineLibrary.Frontend.Dialogs.File;
+
+namespace EgoEngineLibrary.Frontend.Dialogs;
+
+///
+/// A for Avalonia.
+///
+public sealed class AvaloniaDialogService : DialogServiceBase
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public AvaloniaDialogService(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+
+ ///
+ public override async Task ShowDialogAsync(IDialogViewModel viewModel)
+ {
+ if (!Dispatcher.UIThread.CheckAccess())
+ {
+ return await Dispatcher.UIThread.InvokeAsync(() => ShowDialogAsync(viewModel));
+ }
+
+ var frameworkHandled = await HandleFrameworkDialogsAsync(viewModel);
+ if (frameworkHandled)
+ {
+ // result unused
+ return false;
+ }
+
+ var window = CreateWindow(viewModel);
+ var ctx = new DialogContext(window, viewModel);
+ try
+ {
+ viewModel.DialogContext = ctx;
+ _ = await window.ShowDialog(GetOwner());
+ return await ctx.ResultTask;
+ }
+ finally
+ {
+ ctx.Close(false);
+ }
+ }
+
+ private static async Task HandleFrameworkDialogsAsync(IDialogViewModel viewModel)
+ {
+ Task? task = viewModel switch
+ {
+ FileOpenViewModel fileOpenViewModel => FileDialogAvalonia.FileOpen(GetOwner(), fileOpenViewModel),
+ FileSaveViewModel fileSaveViewModel => FileDialogAvalonia.FileSave(GetOwner(), fileSaveViewModel),
+ FolderOpenViewModel folderOpenViewModel => FileDialogAvalonia.FolderOpen(GetOwner(), folderOpenViewModel),
+ _ => null
+ };
+
+ if (task is null)
+ {
+ return false;
+ }
+
+ await task;
+ return true;
+ }
+
+ public override void Show(IDialogViewModel viewModel)
+ {
+ if (!Dispatcher.UIThread.CheckAccess())
+ {
+ Dispatcher.UIThread.Invoke(() => Show(viewModel));
+ return;
+ }
+
+ var window = CreateWindow(viewModel);
+ var ctx = new DialogContext(window, viewModel);
+ try
+ {
+ viewModel.DialogContext = ctx;
+ }
+ catch
+ {
+ ctx.Close(false);
+ throw;
+ }
+
+ window.Show(GetOwner());
+ }
+
+ private static Window CreateWindow(IDialogViewModel viewModel)
+ {
+ return new Window
+ {
+ Title = viewModel.Title,
+ CanMinimize = false,
+ CanResize = false,
+ Content = viewModel,
+ SizeToContent = SizeToContent.WidthAndHeight,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ };
+ }
+
+ private static Window GetOwner()
+ {
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ return desktop.MainWindow ?? throw new InvalidOperationException("Main window is null.");
+ }
+
+ throw new InvalidOperationException("ApplicationLifetime not supported");
+ }
+
+ private class DialogContext : IDialogContext
+ {
+ private readonly TaskCompletionSource _resultTcs;
+ private Window? _dialogWindow;
+ private IDialogViewModel? _viewModel;
+
+ public Task ResultTask => _resultTcs.Task;
+
+ public DialogContext(Window dialogWindow, IDialogViewModel viewModel)
+ {
+ _resultTcs = new TaskCompletionSource();
+ _dialogWindow = dialogWindow;
+ _viewModel = viewModel;
+ _dialogWindow.Closed += DialogWindowOnClosed;
+ }
+
+ private void DialogWindowOnClosed(object? sender, EventArgs e)
+ {
+ _dialogWindow?.Closed -= DialogWindowOnClosed;
+ Close(false);
+ }
+
+ public void Close(bool result = true)
+ {
+ if (!_resultTcs.TrySetResult(result))
+ {
+ // Already closed
+ return;
+ }
+
+ _viewModel?.DialogContext = null;
+ _viewModel = null;
+
+ var dialogWindow = _dialogWindow;
+ _dialogWindow = null;
+ if (dialogWindow is null)
+ {
+ return;
+ }
+
+ dialogWindow.Closed -= DialogWindowOnClosed;
+ if (dialogWindow.Dispatcher.CheckAccess())
+ {
+ dialogWindow.Close();
+ }
+ else
+ {
+ dialogWindow.Dispatcher.Invoke(dialogWindow.Close);
+ }
+ }
+ }
+}
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/Custom/DialogAvalonia.cs b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/Custom/DialogAvalonia.cs
deleted file mode 100644
index 73c1a0b..0000000
--- a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/Custom/DialogAvalonia.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System.Diagnostics;
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-using CommunityToolkit.Mvvm.Messaging;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.Custom;
-
-public static class DialogAvalonia
-{
- public static void Register(Window recipient)
- {
- Dialog.Messenger.Register(recipient, DialogShowHandler);
- }
-
- public static void Unregister(Window recipient)
- {
- Dialog.Messenger.Unregister(recipient);
- }
-
- private static void DialogShowHandler(Window recipient, DialogShowMessage message)
- {
- message.Reply(Show(recipient, message.ViewModel));
- return;
-
- static async Task Show(Window owner, DialogViewModel viewModel)
- {
- ArgumentNullException.ThrowIfNull(owner);
-
- using var cts = new CancellationTokenSource();
- var dialog = new Window
- {
- Title = viewModel.Title,
- CanMinimize = viewModel.CanMinimize,
- CanResize = viewModel.CanResize,
- Content = viewModel,
- SizeToContent = SizeToContent.WidthAndHeight,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Tag = cts.Token,
- };
-
- try
- {
- dialog.Loaded += DialogOnLoaded;
- _ = await dialog.ShowDialog(owner);
- }
- finally
- {
- dialog.Loaded -= DialogOnLoaded;
- await cts.CancelAsync();
- }
-
- return false;
-
- static async void DialogOnLoaded(object? sender, RoutedEventArgs e)
- {
- try
- {
- var window = sender as Window;
- if (window?.Content is not DialogViewModel viewModel ||
- window.Tag is not CancellationToken token)
- {
- return;
- }
-
- await viewModel.WaitForDialogResult().WaitAsync(token);
- window.Close();
- }
- catch (Exception ex)
- {
- Debug.WriteLine(ex.ToString());
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/File/FileDialogAvalonia.cs b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/File/FileDialogAvalonia.cs
index 9ddc32d..e359e0a 100644
--- a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/File/FileDialogAvalonia.cs
+++ b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/File/FileDialogAvalonia.cs
@@ -2,41 +2,20 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
-using CommunityToolkit.Mvvm.Messaging;
-
namespace EgoEngineLibrary.Frontend.Dialogs.File;
-public static class FileDialogAvalonia
+internal static class FileDialogAvalonia
{
- public static void Register(Visual recipient)
- {
- FileDialog.Messenger.Register(recipient, FileOpenHandler);
- FileDialog.Messenger.Register(recipient, FileSaveHandler);
- FileDialog.Messenger.Register(recipient, FolderOpenHandler);
- }
-
- public static void Unregister(Visual recipient)
- {
- FileDialog.Messenger.Unregister(recipient);
- FileDialog.Messenger.Unregister(recipient);
- FileDialog.Messenger.Unregister(recipient);
- }
-
- private static void FileOpenHandler(Visual recipient, FileOpenMessage message)
- {
- message.Reply(FileOpen(recipient, message));
- }
-
- private static async Task> FileOpen(Visual recipient, FileOpenMessage message)
+ public static async Task FileOpen(Visual recipient, FileOpenViewModel viewModel)
{
// Get a reference to our TopLevel (in our case the parent Window)
var topLevel = TopLevel.GetTopLevel(recipient);
if (topLevel is null)
{
- return [];
+ return;
}
- var openOptions = message.Options;
+ var openOptions = viewModel.Options;
var options = new FilePickerOpenOptions
{
Title = openOptions.Title,
@@ -50,24 +29,19 @@ private static async Task> FileOpen(Visual recipient, File
};
var storageFiles = await topLevel.StorageProvider.OpenFilePickerAsync(options);
- return storageFiles.Select(x => x.Path.LocalPath).ToArray();
+ viewModel.Result = storageFiles.Select(x => x.Path.LocalPath).ToArray();
}
- private static void FileSaveHandler(Visual recipient, FileSaveMessage message)
- {
- message.Reply(FileSave(recipient, message));
- }
-
- private static async Task FileSave(Visual recipient, FileSaveMessage message)
+ public static async Task FileSave(Visual recipient, FileSaveViewModel viewModel)
{
// Get a reference to our TopLevel (in our case the parent Window)
var topLevel = TopLevel.GetTopLevel(recipient);
if (topLevel is null)
{
- return null;
+ return;
}
- var saveOptions = message.Options;
+ var saveOptions = viewModel.Options;
var options = new FilePickerSaveOptions
{
Title = saveOptions.Title,
@@ -82,36 +56,30 @@ private static void FileSaveHandler(Visual recipient, FileSaveMessage message)
};
var storageFiles = await topLevel.StorageProvider.SaveFilePickerAsync(options);
- return storageFiles?.Path.LocalPath;
+ viewModel.Result = storageFiles?.Path.LocalPath;
}
- private static void FolderOpenHandler(Visual recipient, FolderOpenMessage message)
+ public static async Task FolderOpen(Visual recipient, FolderOpenViewModel viewModel)
{
- message.Reply(Handle(recipient, message));
- return;
-
- static async Task> Handle(Visual recipient, FolderOpenMessage message)
+ // Get a reference to our TopLevel (in our case the parent Window)
+ var topLevel = TopLevel.GetTopLevel(recipient);
+ if (topLevel is null)
{
- // Get a reference to our TopLevel (in our case the parent Window)
- var topLevel = TopLevel.GetTopLevel(recipient);
- if (topLevel is null)
- {
- return [];
- }
+ return;
+ }
- var openOptions = message.Options;
- var options = new FolderPickerOpenOptions
- {
- Title = openOptions.Title,
- AllowMultiple = openOptions.AllowMultiple,
- SuggestedFileName = openOptions.FileName,
- SuggestedStartLocation = openOptions.InitialDirectory is null
- ? null
- : await topLevel.StorageProvider.TryGetFolderFromPathAsync(openOptions.InitialDirectory),
- };
+ var openOptions = viewModel.Options;
+ var options = new FolderPickerOpenOptions
+ {
+ Title = openOptions.Title,
+ AllowMultiple = openOptions.AllowMultiple,
+ SuggestedFileName = openOptions.FileName,
+ SuggestedStartLocation = openOptions.InitialDirectory is null
+ ? null
+ : await topLevel.StorageProvider.TryGetFolderFromPathAsync(openOptions.InitialDirectory),
+ };
- var storageFiles = await topLevel.StorageProvider.OpenFolderPickerAsync(options);
- return storageFiles.Select(x => x.Path.LocalPath).ToArray();
- }
+ var storageFiles = await topLevel.StorageProvider.OpenFolderPickerAsync(options);
+ viewModel.Result = storageFiles.Select(x => x.Path.LocalPath).ToArray();
}
}
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxAvalonia.cs b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxAvalonia.cs
deleted file mode 100644
index c464edf..0000000
--- a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxAvalonia.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using Avalonia.Controls;
-
-using CommunityToolkit.Mvvm.Messaging;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
-
-public static class MessageBoxAvalonia
-{
- public static void Register(Window recipient)
- {
- MessageBox.Messenger.Register(recipient, MessageBoxShowHandler);
- }
-
- public static void Unregister(Window recipient)
- {
- MessageBox.Messenger.Unregister(recipient);
- }
-
- private static void MessageBoxShowHandler(Window recipient, MessageBoxShowMessage message)
- {
- message.Reply(Show(recipient, message.MessageBoxText, message.Caption, message.Button, message.Icon,
- message.DefaultResult));
- }
-
- private static Task Show(
- Window owner,
- string messageBoxText,
- string caption,
- MessageBoxButton button = MessageBoxButton.OK,
- MessageBoxImage icon = MessageBoxImage.None,
- MessageBoxResult defaultResult = MessageBoxResult.None)
- {
- ArgumentNullException.ThrowIfNull(owner);
-
- MessageBoxWindow box = new()
- {
- MinWidth = 114,
- MinHeight = 94,
- MaxWidth = owner.Width,
- MaxHeight = owner.Height,
- Title = caption,
- Message = messageBoxText,
- Buttons = button,
- ImageIcon = icon,
- DefaultResult = defaultResult
- };
- return box.ShowDialog(owner);
- }
-}
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxWindow.axaml b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxView.axaml
similarity index 74%
rename from src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxWindow.axaml
rename to src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxView.axaml
index 504a7e9..d8d4819 100644
--- a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxWindow.axaml
+++ b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxView.axaml
@@ -1,28 +1,37 @@
-
-
+
+
M12,2 C17.523,2 22,6.478 22,12 C22,17.522 17.523,22 12,22 C6.477,22 2,17.522 2,12 C2,6.478 6.477,2 12,2 Z M12,3.667 C7.405,3.667 3.667,7.405 3.667,12 C3.667,16.595 7.405,20.333 12,20.333 C16.595,20.333 20.333,16.595 20.333,12 C20.333,7.405 16.595,3.667 12,3.667 Z M11.9986626,14.5022358 C12.5502088,14.5022358 12.9973253,14.9493523 12.9973253,15.5008984 C12.9973253,16.0524446 12.5502088,16.4995611 11.9986626,16.4995611 C11.4471165,16.4995611 11,16.0524446 11,15.5008984 C11,14.9493523 11.4471165,14.5022358 11.9986626,14.5022358 Z M11.9944624,7 C12.3741581,6.99969679 12.6881788,7.28159963 12.7381342,7.64763535 L12.745062,7.7494004 L12.7486629,12.2509944 C12.7489937,12.6652079 12.4134759,13.0012627 11.9992625,13.0015945 C11.6195668,13.0018977 11.3055461,12.7199949 11.2555909,12.3539592 L11.2486629,12.2521941 L11.245062,7.7506001 C11.2447312,7.33638667 11.580249,7.00033178 11.9944624,7 Z
M14,2 C20.6274,2 26,7.37258 26,14 C26,20.6274 20.6274,26 14,26 C7.37258,26 2,20.6274 2,14 C2,7.37258 7.37258,2 14,2 Z M14,3.5 C8.20101,3.5 3.5,8.20101 3.5,14 C3.5,19.799 8.20101,24.5 14,24.5 C19.799,24.5 24.5,19.799 24.5,14 C24.5,8.20101 19.799,3.5 14,3.5 Z M14,11 C14.3796833,11 14.6934889,11.2821653 14.7431531,11.6482323 L14.75,11.75 L14.75,19.25 C14.75,19.6642 14.4142,20 14,20 C13.6203167,20 13.3065111,19.7178347 13.2568469,19.3517677 L13.25,19.25 L13.25,11.75 C13.25,11.3358 13.5858,11 14,11 Z M14,7 C14.5523,7 15,7.44772 15,8 C15,8.55228 14.5523,9 14,9 C13.4477,9 13,8.55228 13,8 C13,7.44772 13.4477,7 14,7 Z
M24 4C35.0457 4 44 12.9543 44 24C44 35.0457 35.0457 44 24 44C12.9543 44 4 35.0457 4 24C4 12.9543 12.9543 4 24 4ZM24 6.5C14.335 6.5 6.5 14.335 6.5 24C6.5 33.665 14.335 41.5 24 41.5C33.665 41.5 41.5 33.665 41.5 24C41.5 14.335 33.665 6.5 24 6.5ZM24.25 32C25.0784 32 25.75 32.6716 25.75 33.5C25.75 34.3284 25.0784 35 24.25 35C23.4216 35 22.75 34.3284 22.75 33.5C22.75 32.6716 23.4216 32 24.25 32ZM24.25 13C27.6147 13 30.5 15.8821 30.5 19.2488C30.502 21.3691 29.7314 22.7192 27.8216 24.7772L26.8066 25.8638C25.7842 27.0028 25.3794 27.7252 25.3409 28.5793L25.3379 28.7411L25.3323 28.8689L25.3143 28.9932C25.2018 29.5636 24.7009 29.9957 24.0968 30.0001C23.4065 30.0049 22.8428 29.4493 22.8379 28.7589C22.8251 26.9703 23.5147 25.7467 25.1461 23.9739L26.1734 22.8762C27.5312 21.3837 28.0012 20.503 28 19.25C28 17.2634 26.2346 15.5 24.25 15.5C22.3307 15.5 20.6142 17.1536 20.5055 19.0587L20.4935 19.3778C20.4295 20.0081 19.8972 20.5 19.25 20.5C18.5596 20.5 18 19.9404 18 19.25C18 15.8846 20.8864 13 24.25 13Z
M10.9093922,2.78216375 C11.9491636,2.20625071 13.2471955,2.54089334 13.8850247,3.52240345 L13.9678229,3.66023048 L21.7267791,17.6684928 C21.9115773,18.0021332 22.0085303,18.3772743 22.0085303,18.7586748 C22.0085303,19.9495388 21.0833687,20.9243197 19.9125791,21.003484 L19.7585303,21.0086748 L4.24277801,21.0086748 C3.86146742,21.0086748 3.48641186,20.9117674 3.15282824,20.7270522 C2.11298886,20.1512618 1.7079483,18.8734454 2.20150311,17.8120352 L2.27440063,17.668725 L10.0311968,3.66046274 C10.2357246,3.291099 10.5400526,2.98673515 10.9093922,2.78216375 Z M20.4146132,18.3952808 L12.6556571,4.3870185 C12.4549601,4.02467391 11.9985248,3.89363262 11.6361802,4.09432959 C11.5438453,4.14547244 11.4637001,4.21532637 11.4006367,4.29899869 L11.3434484,4.38709592 L3.58665221,18.3953582 C3.385998,18.7577265 3.51709315,19.2141464 3.87946142,19.4148006 C3.96285732,19.4609794 4.05402922,19.4906942 4.14802472,19.5026655 L4.24277801,19.5086748 L19.7585303,19.5086748 C20.1727439,19.5086748 20.5085303,19.1728883 20.5085303,18.7586748 C20.5085303,18.6633247 20.4903516,18.5691482 20.455275,18.4811011 L20.4146132,18.3952808 L12.6556571,4.3870185 L20.4146132,18.3952808 Z M12.0004478,16.0017852 C12.5519939,16.0017852 12.9991104,16.4489016 12.9991104,17.0004478 C12.9991104,17.5519939 12.5519939,17.9991104 12.0004478,17.9991104 C11.4489016,17.9991104 11.0017852,17.5519939 11.0017852,17.0004478 C11.0017852,16.4489016 11.4489016,16.0017852 12.0004478,16.0017852 Z M11.9962476,8.49954934 C12.3759432,8.49924613 12.689964,8.78114897 12.7399193,9.14718469 L12.7468472,9.24894974 L12.750448,13.7505438 C12.7507788,14.1647572 12.4152611,14.5008121 12.0010476,14.5011439 C11.621352,14.5014471 11.3073312,14.2195442 11.257376,13.8535085 L11.250448,13.7517435 L11.2468472,9.25014944 C11.2465164,8.83593601 11.5820341,8.49988112 11.9962476,8.49954934 Z
-
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxView.axaml.cs b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxView.axaml.cs
new file mode 100644
index 0000000..e8ea197
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxView.axaml.cs
@@ -0,0 +1,49 @@
+using Avalonia.Controls;
+using Avalonia.Media;
+
+namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
+
+public partial class MessageBoxView : UserControl
+{
+ public MessageBoxView()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+
+ if (DataContext is not MessageBoxViewModel dataContext)
+ {
+ return;
+ }
+
+ Image.IsVisible = true;
+ switch (dataContext.Icon)
+ {
+ case MessageBoxImage.None:
+ Image.Data = null;
+ Image.IsVisible = false;
+ break;
+ case MessageBoxImage.Error:
+ Image.Data = this.FindResource(null, "ErrorCircleRegular") as Geometry;
+ Image.Foreground = Brushes.Firebrick;
+ break;
+ case MessageBoxImage.Question:
+ Image.Data = this.FindResource(null, "QuestionCircleRegular") as Geometry;
+ Image.Foreground = Brushes.SteelBlue;
+ break;
+ case MessageBoxImage.Exclamation:
+ Image.Data = this.FindResource(null, "WarningRegular") as Geometry;
+ Image.Foreground = Brushes.Goldenrod;
+ break;
+ case MessageBoxImage.Asterisk:
+ Image.Data = this.FindResource(null, "InfoRegular") as Geometry;
+ Image.Foreground = Brushes.SteelBlue;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(dataContext.Icon), dataContext.Icon, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxWindow.axaml.cs b/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxWindow.axaml.cs
deleted file mode 100644
index fe21070..0000000
--- a/src/EgoEngineLibrary.Frontend.Avalonia/Dialogs/MessageBox/MessageBoxWindow.axaml.cs
+++ /dev/null
@@ -1,271 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-using Avalonia.Media;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
-
-internal partial class MessageBoxWindow : Window
-{
- private bool _closeByButton;
-
- public string? Message
- {
- get => MessageTextBlock.Text;
- set => MessageTextBlock.Text = value;
- }
-
- public MessageBoxResult DefaultResult
- {
- get;
- set
- {
- field = value;
- SetDefaultButton();
- }
- }
-
- public MessageBoxButton Buttons
- {
- get;
- set
- {
- field = value;
- Button cancelButton;
- switch (value)
- {
- case MessageBoxButton.OK:
- FirstButton.Content = "Ok";
- SecondButton.IsVisible = false;
- ThirdButton.IsVisible = false;
- cancelButton = FirstButton;
- break;
- case MessageBoxButton.OKCancel:
- FirstButton.Content = "Ok";
- SecondButton.Content = "Cancel";
- ThirdButton.IsVisible = false;
- cancelButton = SecondButton;
- break;
- case MessageBoxButton.AbortRetryIgnore:
- FirstButton.Content = "Abort";
- SecondButton.Content = "Retry";
- ThirdButton.Content = "Ignore";
- cancelButton = FirstButton;
- break;
- case MessageBoxButton.YesNoCancel:
- FirstButton.Content = "Yes";
- SecondButton.Content = "No";
- ThirdButton.Content = "Cancel";
- cancelButton = ThirdButton;
- break;
- case MessageBoxButton.YesNo:
- FirstButton.Content = "Yes";
- SecondButton.Content = "No";
- ThirdButton.IsVisible = false;
- cancelButton = SecondButton;
- break;
- case MessageBoxButton.RetryCancel:
- FirstButton.Content = "Retry";
- SecondButton.Content = "Cancel";
- ThirdButton.IsVisible = false;
- cancelButton = SecondButton;
- break;
- case MessageBoxButton.CancelTryContinue:
- FirstButton.Content = "Cancel";
- SecondButton.Content = "Try Again";
- ThirdButton.Content = "Continue";
- cancelButton = FirstButton;
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(Buttons), value, null);
- }
-
- FirstButton.IsCancel = false;
- SecondButton.IsCancel = false;
- ThirdButton.IsCancel = false;
- cancelButton.IsCancel = true;
-
- SetDefaultButton();
- }
- }
-
- public MessageBoxImage ImageIcon
- {
- get;
- set
- {
- field = value;
- Image.IsVisible = true;
- switch (value)
- {
- case MessageBoxImage.None:
- Image.Data = null;
- Image.IsVisible = false;
- break;
- case MessageBoxImage.Error:
- Image.Data = this.FindResource(this.ActualThemeVariant, "ErrorCircleRegular") as Geometry;
- Image.Foreground = Brushes.Firebrick;
- break;
- case MessageBoxImage.Question:
- Image.Data = this.FindResource(this.ActualThemeVariant, "QuestionCircleRegular") as Geometry;
- Image.Foreground = Brushes.SteelBlue;
- break;
- case MessageBoxImage.Exclamation:
- Image.Data = this.FindResource(this.ActualThemeVariant, "WarningRegular") as Geometry;
- Image.Foreground = Brushes.Goldenrod;
- break;
- case MessageBoxImage.Asterisk:
- Image.Data = this.FindResource(this.ActualThemeVariant, "InfoRegular") as Geometry;
- Image.Foreground = Brushes.SteelBlue;
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(ImageIcon), value, null);
- }
- }
- }
-
- public MessageBoxWindow()
- {
- InitializeComponent();
- Buttons = MessageBoxButton.OK;
- ImageIcon = MessageBoxImage.None;
- DefaultResult = MessageBoxResult.None;
- }
-
- private void SetDefaultButton()
- {
- FirstButton.IsDefault = false;
- SecondButton.IsDefault = false;
- ThirdButton.IsDefault = false;
-
- if (DefaultResult == MessageBoxResult.None)
- {
- FirstButton.IsDefault = true;
- return;
- }
-
- var defaultButton = Buttons switch
- {
- MessageBoxButton.OK => FirstButton,
- MessageBoxButton.OKCancel => DefaultResult is MessageBoxResult.Cancel ? SecondButton : FirstButton,
- MessageBoxButton.AbortRetryIgnore => DefaultResult switch
- {
- MessageBoxResult.Retry => SecondButton,
- MessageBoxResult.Ignore => ThirdButton,
- _ => FirstButton
- },
- MessageBoxButton.YesNoCancel => DefaultResult switch
- {
- MessageBoxResult.No => SecondButton,
- MessageBoxResult.Cancel => ThirdButton,
- _ => FirstButton
- },
- MessageBoxButton.YesNo => DefaultResult is MessageBoxResult.No ? SecondButton : FirstButton,
- MessageBoxButton.RetryCancel => DefaultResult is MessageBoxResult.Cancel ? SecondButton : FirstButton,
- MessageBoxButton.CancelTryContinue => DefaultResult switch
- {
- MessageBoxResult.TryAgain => SecondButton,
- MessageBoxResult.Continue => ThirdButton,
- _ => FirstButton
- },
- _ => FirstButton
- };
-
- defaultButton.IsDefault = true;
- }
-
- private void FirstButton_OnClick(object? sender, RoutedEventArgs e)
- {
- _closeByButton = true;
- switch (Buttons)
- {
- case MessageBoxButton.OK:
- Close(MessageBoxResult.OK);
- break;
- case MessageBoxButton.OKCancel:
- Close(MessageBoxResult.OK);
- break;
- case MessageBoxButton.AbortRetryIgnore:
- Close(MessageBoxResult.Abort);
- break;
- case MessageBoxButton.YesNoCancel:
- Close(MessageBoxResult.Yes);
- break;
- case MessageBoxButton.YesNo:
- Close(MessageBoxResult.Yes);
- break;
- case MessageBoxButton.RetryCancel:
- Close(MessageBoxResult.Retry);
- break;
- case MessageBoxButton.CancelTryContinue:
- Close(MessageBoxResult.Cancel);
- break;
- default:
- throw new InvalidOperationException("First button is not supported.");
- }
- }
-
- private void SecondButton_OnClick(object? sender, RoutedEventArgs e)
- {
- _closeByButton = true;
- switch (Buttons)
- {
- case MessageBoxButton.OKCancel:
- Close(MessageBoxResult.Cancel);
- break;
- case MessageBoxButton.AbortRetryIgnore:
- Close(MessageBoxResult.Retry);
- break;
- case MessageBoxButton.YesNoCancel:
- Close(MessageBoxResult.No);
- break;
- case MessageBoxButton.YesNo:
- Close(MessageBoxResult.No);
- break;
- case MessageBoxButton.RetryCancel:
- Close(MessageBoxResult.Cancel);
- break;
- case MessageBoxButton.CancelTryContinue:
- Close(MessageBoxResult.TryAgain);
- break;
- case MessageBoxButton.OK:
- default:
- throw new InvalidOperationException("Second button is not supported.");
- }
- }
-
- private void ThirdButton_OnClick(object? sender, RoutedEventArgs e)
- {
- _closeByButton = true;
- switch (Buttons)
- {
- case MessageBoxButton.AbortRetryIgnore:
- Close(MessageBoxResult.Ignore);
- break;
- case MessageBoxButton.YesNoCancel:
- Close(MessageBoxResult.Cancel);
- break;
- case MessageBoxButton.CancelTryContinue:
- Close(MessageBoxResult.Continue);
- break;
- case MessageBoxButton.OK:
- case MessageBoxButton.OKCancel:
- case MessageBoxButton.YesNo:
- case MessageBoxButton.RetryCancel:
- default:
- throw new InvalidOperationException("Third button is not supported.");
- }
- }
-
- protected override void OnClosing(WindowClosingEventArgs e)
- {
- if (_closeByButton)
- {
- base.OnClosing(e);
- return;
- }
-
- e.Cancel = true;
- var cancelButton = SecondButton.IsCancel ? SecondButton : (ThirdButton.IsCancel ? ThirdButton : FirstButton);
- cancelButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
- }
-}
diff --git a/src/EgoErpArchiver/ViewLocator.cs b/src/EgoEngineLibrary.Frontend.Avalonia/ViewLocator.cs
similarity index 73%
rename from src/EgoErpArchiver/ViewLocator.cs
rename to src/EgoEngineLibrary.Frontend.Avalonia/ViewLocator.cs
index 706564d..8f2a0ed 100644
--- a/src/EgoErpArchiver/ViewLocator.cs
+++ b/src/EgoEngineLibrary.Frontend.Avalonia/ViewLocator.cs
@@ -1,10 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
-
+using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
using EgoEngineLibrary.Frontend.ViewModels;
-namespace EgoErpArchiver;
+namespace EgoEngineLibrary.Frontend;
///
/// Given a view model, returns the corresponding view if possible.
@@ -14,10 +14,20 @@ namespace EgoErpArchiver;
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate
{
- public Control? Build(object? param)
+ public virtual Control? Build(object? param)
{
if (param is null)
return null;
+
+ Control? view = param switch
+ {
+ MessageBoxViewModel => new MessageBoxView(),
+ _ => null
+ };
+ if (view is not null)
+ {
+ return view;
+ }
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/Custom/Dialog.cs b/src/EgoEngineLibrary.Frontend/Dialogs/Custom/Dialog.cs
deleted file mode 100644
index 1ab60c0..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/Custom/Dialog.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.Custom;
-
-public static class Dialog
-{
- public static IMessenger Messenger { get; set; } = Messaging.Messenger.Default;
-
- public static async Task ShowDialog(DialogViewModel viewModel)
- {
- viewModel.ResetResult();
- await Messenger.Send(new DialogShowMessage(viewModel));
- return viewModel.DialogResult;
- }
-}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/Custom/DialogShowMessage.cs b/src/EgoEngineLibrary.Frontend/Dialogs/Custom/DialogShowMessage.cs
deleted file mode 100644
index a740b83..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/Custom/DialogShowMessage.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging.Messages;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.Custom;
-
-public class DialogShowMessage(DialogViewModel viewModel) : AsyncRequestMessage
-{
- public DialogViewModel ViewModel { get; } = viewModel;
-}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/Custom/DialogViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/Custom/DialogViewModel.cs
deleted file mode 100644
index d245864..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/Custom/DialogViewModel.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.Custom;
-
-public abstract class DialogViewModel : ObservableValidator
-{
- public abstract string Title { get; }
-
- public virtual bool CanMinimize => false;
-
- public virtual bool CanResize => false;
-
- ///
- /// Waits until a dialog result is set.
- ///
- public abstract Task WaitForDialogResult();
-}
-
-public abstract class DialogViewModel : DialogViewModel
-{
- private readonly AsyncManualResetEvent _closeEvent = new();
- internal T? DialogResult { get; private set; }
-
- internal void ResetResult()
- {
- DialogResult = default;
- _closeEvent.Reset();
- }
-
- protected void SetDialogResult(T? value)
- {
- DialogResult = value;
- _closeEvent.Set();
- }
-
- ///
- public override Task WaitForDialogResult() => _closeEvent.WaitAsync();
-
- private sealed class AsyncManualResetEvent
- {
- private volatile TaskCompletionSource _tcs = new();
-
- public Task WaitAsync() => _tcs.Task;
-
- public void Set() => _tcs.TrySetResult(true);
-
- public void Reset()
- {
- while (true)
- {
- var tcs = _tcs;
- if (!tcs.Task.IsCompleted ||
- Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource(), tcs) == tcs)
- {
- return;
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/DialogService.cs b/src/EgoEngineLibrary.Frontend/Dialogs/DialogService.cs
new file mode 100644
index 0000000..42be374
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/DialogService.cs
@@ -0,0 +1,10 @@
+namespace EgoEngineLibrary.Frontend.Dialogs;
+
+public static class DialogService
+{
+ public static IDialogService Instance
+ {
+ get => field ?? throw new InvalidOperationException("No dialog service configured.");
+ set;
+ }
+}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/DialogServiceBase.cs b/src/EgoEngineLibrary.Frontend/Dialogs/DialogServiceBase.cs
new file mode 100644
index 0000000..174946a
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/DialogServiceBase.cs
@@ -0,0 +1,34 @@
+namespace EgoEngineLibrary.Frontend.Dialogs;
+
+///
+public abstract class DialogServiceBase : IDialogService
+{
+ private readonly IServiceProvider _serviceProvider;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ protected DialogServiceBase(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ ///
+ public abstract Task ShowDialogAsync(IDialogViewModel viewModel);
+
+ ///
+ public abstract void Show(IDialogViewModel viewModel);
+
+ ///
+ public T CreateViewModel() where T : IDialogViewModel
+ {
+ var serviceType = typeof(T);
+ var service = _serviceProvider.GetService(serviceType);
+ if (service is not T viewModel)
+ {
+ throw new InvalidOperationException($"No service for type '{typeof(T)}' has been registered.");
+ }
+
+ return viewModel;
+ }
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileDialog.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileDialog.cs
index 02d5c06..b58a38d 100644
--- a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileDialog.cs
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileDialog.cs
@@ -1,23 +1,40 @@
-using CommunityToolkit.Mvvm.Messaging;
+namespace EgoEngineLibrary.Frontend.Dialogs.File;
-namespace EgoEngineLibrary.Frontend.Dialogs.File;
-
-public class FileDialog
+public static class FileDialog
{
- public static IMessenger Messenger { get; set; } = Messaging.Messenger.Default;
+ public static Task> ShowOpenFileDialog(FileOpenOptions openOptions)
+ {
+ return DialogService.Instance.ShowOpenFileDialog(openOptions);
+ }
+
+ public static async Task> ShowOpenFileDialog(this IDialogService dialogService, FileOpenOptions openOptions)
+ {
+ var vm = new FileOpenViewModel(openOptions);
+ await dialogService.ShowDialogAsync(vm);
+ return vm.Result;
+ }
+
+ public static Task ShowSaveFileDialog(FileSaveOptions saveOptions)
+ {
+ return DialogService.Instance.ShowSaveFileDialog(saveOptions);
+ }
- public static async Task> ShowOpenFileDialog(FileOpenOptions openOptions)
+ public static async Task ShowSaveFileDialog(this IDialogService dialogService, FileSaveOptions saveOptions)
{
- return await Messenger.Send(new FileOpenMessage(openOptions));
+ var vm = new FileSaveViewModel(saveOptions);
+ await dialogService.ShowDialogAsync(vm);
+ return vm.Result;
}
- public static async Task ShowSaveFileDialog(FileSaveOptions saveOptions)
+ public static Task> ShowOpenFolderDialog(FolderOpenOptions openOptions)
{
- return await Messenger.Send(new FileSaveMessage(saveOptions));
+ return DialogService.Instance.ShowOpenFolderDialog(openOptions);
}
- public static async Task> ShowOpenFolderDialog(FolderOpenOptions openOptions)
+ public static async Task> ShowOpenFolderDialog(this IDialogService dialogService, FolderOpenOptions openOptions)
{
- return await Messenger.Send(new FolderOpenMessage(openOptions));
+ var vm = new FolderOpenViewModel(openOptions);
+ await dialogService.ShowDialogAsync(vm);
+ return vm.Result;
}
}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileOpenMessage.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileOpenMessage.cs
deleted file mode 100644
index 893d19d..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileOpenMessage.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging.Messages;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.File;
-
-public class FileOpenMessage : AsyncRequestMessage>
-{
- public FileOpenOptions Options { get; }
-
- public FileOpenMessage(FileOpenOptions options)
- {
- Options = options;
- }
-}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileOpenViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileOpenViewModel.cs
new file mode 100644
index 0000000..99a926a
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileOpenViewModel.cs
@@ -0,0 +1,12 @@
+namespace EgoEngineLibrary.Frontend.Dialogs.File;
+
+public class FileOpenViewModel(FileOpenOptions options) : IDialogViewModel
+{
+ public string Title => "";
+
+ public IDialogContext? DialogContext { get; set; }
+
+ public FileOpenOptions Options { get; } = options;
+
+ public IReadOnlyList Result { get; set; } = [];
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileSaveMessage.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileSaveMessage.cs
deleted file mode 100644
index 36ed3fa..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileSaveMessage.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging.Messages;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.File;
-
-public class FileSaveMessage : AsyncRequestMessage
-{
- public FileSaveOptions Options { get; }
-
- public FileSaveMessage(FileSaveOptions options)
- {
- Options = options;
- }
-}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FileSaveViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileSaveViewModel.cs
new file mode 100644
index 0000000..48cc1ae
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/File/FileSaveViewModel.cs
@@ -0,0 +1,12 @@
+namespace EgoEngineLibrary.Frontend.Dialogs.File;
+
+public class FileSaveViewModel(FileSaveOptions options) : IDialogViewModel
+{
+ public string Title => "";
+
+ public IDialogContext? DialogContext { get; set; }
+
+ public FileSaveOptions Options { get; } = options;
+
+ public string? Result { get; set; }
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FolderOpenMessage.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FolderOpenMessage.cs
deleted file mode 100644
index 80f883d..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/File/FolderOpenMessage.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging.Messages;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.File;
-
-public class FolderOpenMessage : AsyncRequestMessage>
-{
- public FolderOpenOptions Options { get; }
-
- public FolderOpenMessage(FolderOpenOptions options)
- {
- Options = options;
- }
-}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/File/FolderOpenViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/File/FolderOpenViewModel.cs
new file mode 100644
index 0000000..2e08523
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/File/FolderOpenViewModel.cs
@@ -0,0 +1,12 @@
+namespace EgoEngineLibrary.Frontend.Dialogs.File;
+
+public class FolderOpenViewModel(FolderOpenOptions options) : IDialogViewModel
+{
+ public string Title => "";
+
+ public IDialogContext? DialogContext { get; set; }
+
+ public FolderOpenOptions Options { get; } = options;
+
+ public IReadOnlyList Result { get; set; } = [];
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/IDialogContext.cs b/src/EgoEngineLibrary.Frontend/Dialogs/IDialogContext.cs
new file mode 100644
index 0000000..2773d93
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/IDialogContext.cs
@@ -0,0 +1,13 @@
+namespace EgoEngineLibrary.Frontend.Dialogs;
+
+///
+/// A context created for each call to show a dialog.
+///
+public interface IDialogContext
+{
+ ///
+ /// Closes the dialog with the given result.
+ ///
+ /// true for dialog success.
+ void Close(bool result = true);
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/IDialogService.cs b/src/EgoEngineLibrary.Frontend/Dialogs/IDialogService.cs
new file mode 100644
index 0000000..a1e63ab
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/IDialogService.cs
@@ -0,0 +1,26 @@
+namespace EgoEngineLibrary.Frontend.Dialogs;
+
+///
+/// A service to interact with dialogs from view models without knowing specifics about the UI framework.
+///
+public interface IDialogService
+{
+ ///
+ /// Displays a modal dialog based on the given view model.
+ ///
+ /// The view model for the dialog.
+ /// A nullable bool that signifies how a dialog was closed.
+ Task ShowDialogAsync(IDialogViewModel viewModel);
+
+ ///
+ /// Displays a non-modal dialog based on the given view model.
+ ///
+ /// The view model for the dialog.
+ void Show(IDialogViewModel viewModel);
+
+ ///
+ /// Creates a view model of the specified type.
+ ///
+ /// The type of the view model to create.
+ T CreateViewModel() where T : IDialogViewModel;
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/IDialogViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/IDialogViewModel.cs
new file mode 100644
index 0000000..8c26552
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/IDialogViewModel.cs
@@ -0,0 +1,17 @@
+namespace EgoEngineLibrary.Frontend.Dialogs;
+
+///
+/// Represents a view model for a dialog.
+///
+public interface IDialogViewModel
+{
+ ///
+ /// The title of the dialog.
+ ///
+ string Title { get; }
+
+ ///
+ /// The dialog context used to control the dialog from the view model.
+ ///
+ IDialogContext? DialogContext { get; set; }
+}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBox.cs b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBox.cs
index 41f97eb..f252d31 100644
--- a/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBox.cs
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBox.cs
@@ -1,18 +1,27 @@
-using CommunityToolkit.Mvvm.Messaging;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
+namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
public static class MessageBox
{
- public static IMessenger Messenger { get; set; } = Messaging.Messenger.Default;
-
- public static async Task Show(
+ public static Task Show(
+ string messageBoxText,
+ string caption,
+ MessageBoxButton button = MessageBoxButton.OK,
+ MessageBoxImage icon = MessageBoxImage.None,
+ MessageBoxResult defaultResult = MessageBoxResult.None)
+ {
+ return DialogService.Instance.ShowMessageBox(messageBoxText, caption, button, icon, defaultResult);
+ }
+
+ public static async Task ShowMessageBox(
+ this IDialogService dialogService,
string messageBoxText,
string caption,
MessageBoxButton button = MessageBoxButton.OK,
MessageBoxImage icon = MessageBoxImage.None,
MessageBoxResult defaultResult = MessageBoxResult.None)
{
- return await Messenger.Send(new MessageBoxShowMessage(messageBoxText, caption, button, icon, defaultResult));
+ var vm = new MessageBoxViewModel(messageBoxText, caption, button, icon, defaultResult);
+ await dialogService.ShowDialogAsync(vm);
+ return vm.Result;
}
}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxButtonViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxButtonViewModel.cs
new file mode 100644
index 0000000..e97b7f2
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxButtonViewModel.cs
@@ -0,0 +1,20 @@
+namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
+
+public class MessageBoxButtonViewModel
+{
+ public static readonly MessageBoxButtonViewModel Ok = new MessageBoxButtonViewModel()
+ {
+ Text = "Ok",
+ Result = MessageBoxResult.OK,
+ IsDefault = true,
+ IsCancel = false,
+ };
+
+ public string Text { get; init; } = "";
+
+ public MessageBoxResult Result { get; init; }
+
+ public bool IsDefault { get; init; }
+
+ public bool IsCancel { get; init; }
+}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxShowMessage.cs b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxShowMessage.cs
deleted file mode 100644
index bf00388..0000000
--- a/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxShowMessage.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging.Messages;
-
-namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
-
-public class MessageBoxShowMessage : AsyncRequestMessage
-{
- public string MessageBoxText { get; }
-
- public string Caption { get; }
-
- public MessageBoxButton Button { get; }
-
- public MessageBoxImage Icon { get; }
-
- public MessageBoxResult DefaultResult { get; }
-
- public MessageBoxShowMessage(
- string messageBoxText,
- string caption,
- MessageBoxButton button = MessageBoxButton.OK,
- MessageBoxImage icon = MessageBoxImage.None,
- MessageBoxResult defaultResult = MessageBoxResult.None)
- {
- MessageBoxText = messageBoxText;
- Caption = caption;
- Button = button;
- Icon = icon;
- DefaultResult = defaultResult;
- }
-}
diff --git a/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxViewModel.cs b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxViewModel.cs
new file mode 100644
index 0000000..be5f1f5
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/Dialogs/MessageBox/MessageBoxViewModel.cs
@@ -0,0 +1,182 @@
+using System.Diagnostics;
+using CommunityToolkit.Mvvm.Input;
+using EgoEngineLibrary.Frontend.ViewModels;
+
+namespace EgoEngineLibrary.Frontend.Dialogs.MessageBox;
+
+public partial class MessageBoxViewModel : ViewModelBase, IDialogViewModel
+{
+ public string MessageBoxText { get; }
+
+ public string Title { get; }
+
+ public MessageBoxImage Icon { get; }
+
+ public MessageBoxResult Result { get; private set; }
+
+ public MessageBoxButtonViewModel[] Buttons { get; }
+
+ public IDialogContext? DialogContext { get; set; }
+
+ public MessageBoxViewModel(
+ string messageBoxText,
+ string caption,
+ MessageBoxButton button,
+ MessageBoxImage icon,
+ MessageBoxResult defaultResult)
+ {
+ MessageBoxText = messageBoxText;
+ Title = caption;
+ Icon = icon;
+
+ Buttons = button switch
+ {
+ MessageBoxButton.OK =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Ok",
+ Result = MessageBoxResult.OK,
+ IsDefault = true,
+ IsCancel = true,
+ },
+ ],
+ MessageBoxButton.OKCancel =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Ok",
+ Result = MessageBoxResult.OK,
+ IsDefault = defaultResult is not MessageBoxResult.Cancel,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Cancel",
+ Result = MessageBoxResult.Cancel,
+ IsDefault = defaultResult is MessageBoxResult.Cancel,
+ IsCancel = true,
+ },
+ ],
+ MessageBoxButton.AbortRetryIgnore =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Abort",
+ Result = MessageBoxResult.Abort,
+ IsDefault = defaultResult is not MessageBoxResult.Retry and not MessageBoxResult.Ignore,
+ IsCancel = true,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Retry",
+ Result = MessageBoxResult.Retry,
+ IsDefault = defaultResult is MessageBoxResult.Retry,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Ignore",
+ Result = MessageBoxResult.Ignore,
+ IsDefault = defaultResult is MessageBoxResult.Ignore,
+ IsCancel = false,
+ },
+ ],
+ MessageBoxButton.YesNoCancel =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Yes",
+ Result = MessageBoxResult.Yes,
+ IsDefault = defaultResult is not MessageBoxResult.No and not MessageBoxResult.Cancel,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "No",
+ Result = MessageBoxResult.No,
+ IsDefault = defaultResult is MessageBoxResult.No,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Cancel",
+ Result = MessageBoxResult.Cancel,
+ IsDefault = defaultResult is MessageBoxResult.Cancel,
+ IsCancel = true,
+ },
+ ],
+ MessageBoxButton.YesNo =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Yes",
+ Result = MessageBoxResult.Yes,
+ IsDefault = defaultResult is not MessageBoxResult.No,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "No",
+ Result = MessageBoxResult.No,
+ IsDefault = defaultResult is MessageBoxResult.No,
+ IsCancel = true,
+ },
+ ],
+ MessageBoxButton.RetryCancel =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Retry",
+ Result = MessageBoxResult.Retry,
+ IsDefault = defaultResult is not MessageBoxResult.Cancel,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Cancel",
+ Result = MessageBoxResult.Cancel,
+ IsDefault = defaultResult is MessageBoxResult.Cancel,
+ IsCancel = true,
+ },
+ ],
+ MessageBoxButton.CancelTryContinue =>
+ [
+ new MessageBoxButtonViewModel
+ {
+ Text = "Cancel",
+ Result = MessageBoxResult.Cancel,
+ IsDefault = defaultResult is not MessageBoxResult.TryAgain and not MessageBoxResult.Continue,
+ IsCancel = true,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Try Again",
+ Result = MessageBoxResult.TryAgain,
+ IsDefault = defaultResult is MessageBoxResult.TryAgain,
+ IsCancel = false,
+ },
+ new MessageBoxButtonViewModel
+ {
+ Text = "Continue",
+ Result = MessageBoxResult.Continue,
+ IsDefault = defaultResult is MessageBoxResult.Continue,
+ IsCancel = false,
+ },
+ ],
+ _ => throw new ArgumentOutOfRangeException(nameof(button), button, null)
+ };
+
+ Debug.Assert(Buttons.Count(x => x.IsDefault) == 1);
+ Debug.Assert(Buttons.Count(x => x.IsCancel) == 1);
+ Result = Buttons.First(x => x.IsCancel).Result;
+ }
+
+ [RelayCommand]
+ private void ButtonClick(MessageBoxButtonViewModel buttonViewModel)
+ {
+ Result = buttonViewModel.Result;
+ // context result unused
+ DialogContext?.Close();
+ }
+}
\ No newline at end of file
diff --git a/src/EgoEngineLibrary.Frontend/EgoEngineLibrary.Frontend.csproj b/src/EgoEngineLibrary.Frontend/EgoEngineLibrary.Frontend.csproj
index 6a56796..4062a41 100644
--- a/src/EgoEngineLibrary.Frontend/EgoEngineLibrary.Frontend.csproj
+++ b/src/EgoEngineLibrary.Frontend/EgoEngineLibrary.Frontend.csproj
@@ -5,6 +5,7 @@
+
diff --git a/src/EgoEngineLibrary.Frontend/ViewModels/ValidatableViewModelBase.cs b/src/EgoEngineLibrary.Frontend/ViewModels/ValidatableViewModelBase.cs
new file mode 100644
index 0000000..1feb487
--- /dev/null
+++ b/src/EgoEngineLibrary.Frontend/ViewModels/ValidatableViewModelBase.cs
@@ -0,0 +1,285 @@
+using System.Collections;
+using System.ComponentModel;
+using System.Linq.Expressions;
+using System.Runtime.CompilerServices;
+using FluentValidation;
+using FluentValidation.Internal;
+using FluentValidation.Results;
+
+namespace EgoEngineLibrary.Frontend.ViewModels;
+
+///
+/// A base class for objects implementing the interface. This class
+/// also inherits from , so it can be used for observable items too.
+///
+///
+/// Based on ObservableValidator in CommunityToolkit.Mvvm 4.2.1.
+///
+public abstract class ValidatableViewModelBase : ViewModelBase, INotifyDataErrorInfo
+ where TSelf : ValidatableViewModelBase
+{
+ ///
+ /// The instance used to store previous validation results.
+ ///
+ private readonly Dictionary> _errors = new();
+
+ ///
+ /// Indicates the total number of properties with errors (not total errors).
+ /// This is used to allow to operate in O(1) time, as it can just
+ /// check whether this value is not 0 instead of having to traverse .
+ ///
+ private int _totalErrors;
+
+ ///
+ public bool HasErrors => _totalErrors > 0;
+
+ ///
+ public event EventHandler? ErrorsChanged;
+
+ ///
+ /// The validator for the object.
+ ///
+ protected abstract IValidator Validator { get; }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ protected ValidatableViewModelBase()
+ {
+ }
+
+ ///
+ /// Clears the validation errors for a specified property or for the entire entity.
+ ///
+ ///
+ /// The name of the property to clear validation errors for.
+ /// If a or empty name is used, all entity-level errors will be cleared.
+ ///
+ protected void ClearErrors(string? propertyName = null)
+ {
+ // Clear entity-level errors when the target property is null or empty
+ if (string.IsNullOrEmpty(propertyName))
+ {
+ ClearAllErrors();
+ }
+ else
+ {
+ ClearErrorsForProperty(propertyName);
+ }
+ }
+
+ ///
+ public IEnumerable GetErrors(string? propertyName = null)
+ {
+ // Get entity-level errors when the target property is null or empty
+ if (string.IsNullOrEmpty(propertyName))
+ {
+ // Local function to gather all the entity-level errors
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ IEnumerable GetAllErrors()
+ {
+ return _errors.Values.SelectMany(static errors => errors);
+ }
+
+ return GetAllErrors();
+ }
+
+ // Property-level errors, if any
+ if (_errors.TryGetValue(propertyName, out var errors))
+ {
+ return errors;
+ }
+
+ // The INotifyDataErrorInfo.GetErrors method doesn't specify exactly what to
+ // return when the input property name is invalid, but given that the return
+ // type is marked as a non-nullable reference type, here we're returning an
+ // empty array to respect the contract. This also matches the behavior of
+ // this method whenever errors for a valid properties are retrieved.
+ return [];
+ }
+
+ ///
+ IEnumerable INotifyDataErrorInfo.GetErrors(string? propertyName) => GetErrors(propertyName);
+
+ ///
+ /// Validates all the properties in the current instance and updates all the tracked errors.
+ /// If any changes are detected, the event will be raised.
+ ///
+ protected void ValidateAllProperties()
+ {
+ var result = Validator.Validate((TSelf)this);
+ if (result.IsValid)
+ {
+ ClearAllErrors();
+ return;
+ }
+
+ foreach (var propertyGroup in result.Errors.GroupBy(x => x.PropertyName))
+ {
+ SetErrors(propertyGroup.Key, propertyGroup);
+ }
+ }
+
+ ///
+ /// Validates a property with a specified name.
+ /// If any changes are detected, the event will be raised.
+ ///
+ /// The name of the property to validate.
+ /// Thrown when is .
+ protected void ValidateProperty([CallerMemberName] string propertyName = null!)
+ {
+ ArgumentNullException.ThrowIfNull(propertyName);
+ var result = Validator.Validate((TSelf)this, options => options.IncludeProperties(propertyName));
+ if (result.IsValid)
+ {
+ ClearErrorsForProperty(propertyName);
+ return;
+ }
+
+ foreach (var propertyGroup in result.Errors.GroupBy(x => x.PropertyName))
+ {
+ SetErrors(propertyGroup.Key, propertyGroup);
+ }
+ }
+
+ ///
+ /// Validates a property with a specified name.
+ /// If any changes are detected, the event will be raised.
+ ///
+ /// The expression of the property to validate.
+ protected void ValidateProperty(Expression> propertyExpression)
+ {
+ var result = Validator.Validate((TSelf)this, options => options.IncludeProperties(propertyExpression));
+ if (result.IsValid)
+ {
+ ClearErrorsForProperty(MemberNameValidatorSelector.MemberNamesFromExpressions(propertyExpression)[0]);
+ return;
+ }
+
+ foreach (var propertyGroup in result.Errors.GroupBy(x => x.PropertyName))
+ {
+ SetErrors(propertyGroup.Key, propertyGroup);
+ }
+ }
+
+ private void SetErrors(string propertyName, IEnumerable errors)
+ {
+ // Check if the property had already been previously validated, and if so retrieve
+ // the reusable list of validation errors from the errors dictionary. This list is
+ // used to add new validation errors below, if any are produced by the validator.
+ // If the property isn't present in the dictionary, add it now to avoid allocations.
+ if (!_errors.TryGetValue(propertyName, out var propertyErrors))
+ {
+ propertyErrors = [];
+ _errors.Add(propertyName, propertyErrors);
+ }
+
+ // Clear the errors for the specified property, if any
+ var errorsChanged = false;
+ if (propertyErrors.Count > 0)
+ {
+ propertyErrors.Clear();
+ errorsChanged = true;
+ }
+
+ // Set the new errors
+ propertyErrors.AddRange(errors);
+ var isValid = propertyErrors.Count == 0;
+
+ // Update the shared counter for the number of errors, and raise the
+ // property changed event if necessary. We decrement the number of total
+ // errors if the current property is valid but it wasn't so before this
+ // validation, and we increment it if the validation failed after being
+ // correct before. The property changed event is raised whenever the
+ // number of total errors is either decremented to 0, or incremented to 1.
+ if (isValid)
+ {
+ if (errorsChanged)
+ {
+ _totalErrors--;
+ if (_totalErrors == 0)
+ {
+ OnPropertyChanged(ValidatableViewModelBaseStatic.HasErrorsChangedEventArgs);
+ }
+ }
+ }
+ else if (!errorsChanged)
+ {
+ _totalErrors++;
+ if (_totalErrors == 1)
+ {
+ OnPropertyChanged(ValidatableViewModelBaseStatic.HasErrorsChangedEventArgs);
+ }
+ }
+
+ // Only raise the event once if needed. This happens either when the target property
+ // had existing errors and is now valid, or if the validation has failed and there are
+ // new errors to broadcast, regardless of the previous validation state for the property.
+ if (errorsChanged || !isValid)
+ {
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
+ }
+ }
+
+ ///
+ /// Clears all the current errors for the entire entity.
+ ///
+ private void ClearAllErrors()
+ {
+ if (_totalErrors == 0)
+ {
+ return;
+ }
+
+ // Clear the errors for all properties with at least one error, and raise the
+ // ErrorsChanged event for those properties. Other properties will be ignored.
+ foreach (var propertyInfo in _errors)
+ {
+ var hasErrors = propertyInfo.Value.Count > 0;
+
+ propertyInfo.Value.Clear();
+
+ if (hasErrors)
+ {
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyInfo.Key));
+ }
+ }
+
+ _totalErrors = 0;
+
+ OnPropertyChanged(ValidatableViewModelBaseStatic.HasErrorsChangedEventArgs);
+ }
+
+ ///
+ /// Clears all the current errors for a target property.
+ ///
+ /// The name of the property to clear errors for.
+ private void ClearErrorsForProperty(string propertyName)
+ {
+ if (!_errors.TryGetValue(propertyName, out var propertyErrors) ||
+ propertyErrors.Count == 0)
+ {
+ return;
+ }
+
+ propertyErrors.Clear();
+
+ _totalErrors--;
+
+ if (_totalErrors == 0)
+ {
+ OnPropertyChanged(ValidatableViewModelBaseStatic.HasErrorsChangedEventArgs);
+ }
+
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
+ }
+}
+
+file static class ValidatableViewModelBaseStatic
+{
+ ///
+ /// The cached for .
+ ///
+ public static readonly PropertyChangedEventArgs HasErrorsChangedEventArgs =
+ new(nameof(ValidatableViewModelBase<>.HasErrors));
+}
diff --git a/src/EgoErpArchiver/App.axaml b/src/EgoErpArchiver/App.axaml
index 55f413e..6941fe8 100644
--- a/src/EgoErpArchiver/App.axaml
+++ b/src/EgoErpArchiver/App.axaml
@@ -1,10 +1,10 @@
-
+
diff --git a/src/EgoErpArchiver/Dialogs/Erp/ErpDialog.cs b/src/EgoErpArchiver/Dialogs/Erp/ErpDialog.cs
index 79e67be..1ccb0a6 100644
--- a/src/EgoErpArchiver/Dialogs/Erp/ErpDialog.cs
+++ b/src/EgoErpArchiver/Dialogs/Erp/ErpDialog.cs
@@ -1,15 +1,17 @@
-using CommunityToolkit.Mvvm.Messaging;
-
+using EgoEngineLibrary.Frontend.Dialogs;
using EgoErpArchiver.ViewModels;
namespace EgoErpArchiver.Dialogs.Erp;
-public class ErpDialog
+public static class ErpDialog
{
- public static IMessenger Messenger { get; set; } = EgoEngineLibrary.Frontend.Messaging.Messenger.Default;
+ public static Task ShowProgressDialog(ProgressDialogViewModel viewModel)
+ {
+ return DialogService.Instance.ShowProgressDialog(viewModel);
+ }
- public static async Task ShowProgressDialog(ProgressDialogViewModel viewModel)
+ public static Task ShowProgressDialog(this IDialogService dialogService, ProgressDialogViewModel viewModel)
{
- await Messenger.Send(new ProgressDialogMessage(viewModel));
+ return dialogService.ShowDialogAsync(viewModel);
}
}
diff --git a/src/EgoErpArchiver/Dialogs/Erp/ErpDialogAvalonia.cs b/src/EgoErpArchiver/Dialogs/Erp/ErpDialogAvalonia.cs
deleted file mode 100644
index 1a8cdde..0000000
--- a/src/EgoErpArchiver/Dialogs/Erp/ErpDialogAvalonia.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Avalonia.Controls;
-
-using CommunityToolkit.Mvvm.Messaging;
-
-using EgoErpArchiver.ViewModels;
-using EgoErpArchiver.Views;
-
-namespace EgoErpArchiver.Dialogs.Erp;
-
-public class ErpDialogAvalonia
-{
- public static void Register(Window recipient)
- {
- ErpDialog.Messenger.Register(recipient, ProgressDialogHandler);
- }
-
- public static void Unregister(Window recipient)
- {
- ErpDialog.Messenger.Unregister(recipient);
- }
-
- private static void ProgressDialogHandler(Window recipient, ProgressDialogMessage message)
- {
- message.Reply(Handle(recipient, message.ViewModel));
- return;
-
- static async Task Handle(Window recipient, ProgressDialogViewModel viewModel)
- {
- ArgumentNullException.ThrowIfNull(recipient);
-
- ProgressDialog win = new() { DataContext = viewModel };
- var res = await win.ShowDialog(recipient);
- return res;
- }
- }
-}
diff --git a/src/EgoErpArchiver/Dialogs/Erp/ProgressDialogMessage.cs b/src/EgoErpArchiver/Dialogs/Erp/ProgressDialogMessage.cs
deleted file mode 100644
index acbaae0..0000000
--- a/src/EgoErpArchiver/Dialogs/Erp/ProgressDialogMessage.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging.Messages;
-
-using EgoErpArchiver.ViewModels;
-
-namespace EgoErpArchiver.Dialogs.Erp;
-
-public class ProgressDialogMessage(ProgressDialogViewModel viewModel) : AsyncRequestMessage
-{
- public ProgressDialogViewModel ViewModel { get; } = viewModel;
-}
diff --git a/src/EgoErpArchiver/Program.cs b/src/EgoErpArchiver/Program.cs
index a70f788..b556108 100644
--- a/src/EgoErpArchiver/Program.cs
+++ b/src/EgoErpArchiver/Program.cs
@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using EgoEngineLibrary.Frontend.Configuration;
using EgoEngineLibrary.Frontend.DependencyInjection;
+using EgoEngineLibrary.Frontend.Dialogs;
using EgoErpArchiver.Configuration;
using EgoErpArchiver.ViewModels;
using Microsoft.Extensions.DependencyInjection;
@@ -84,7 +85,8 @@ private static void ConfigureServices(Serilog.ILogger logger)
.AddSingleton(typeof(ILogger<>), typeof(Logger<>))
.AddSingleton(_ => new SerilogLoggerProvider(logger))
.AddConfigOptions();
-
+
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
@@ -94,6 +96,8 @@ private static void ConfigureServices(Serilog.ILogger logger)
services.AddSingleton();
var serviceProvider = services.BuildServiceProvider();
+ DialogService.Instance = serviceProvider.GetRequiredService();
+
Ioc.Default.ConfigureServices(serviceProvider);
}
}
diff --git a/src/EgoErpArchiver/ViewModels/ProgressDialogViewModel.cs b/src/EgoErpArchiver/ViewModels/ProgressDialogViewModel.cs
index 889c9e3..3167b84 100644
--- a/src/EgoErpArchiver/ViewModels/ProgressDialogViewModel.cs
+++ b/src/EgoErpArchiver/ViewModels/ProgressDialogViewModel.cs
@@ -1,10 +1,10 @@
using System.Text;
-
+using EgoEngineLibrary.Frontend.Dialogs;
using EgoEngineLibrary.Frontend.ViewModels;
namespace EgoErpArchiver.ViewModels
{
- public class ProgressDialogViewModel : ViewModelBase
+ public class ProgressDialogViewModel : ViewModelBase, IDialogViewModel
{
private readonly StringBuilder _stringBuilder;
private int _percentage;
@@ -12,6 +12,9 @@ public class ProgressDialogViewModel : ViewModelBase
private string _percentageText;
private string _status;
+ public string Title => DisplayName;
+ public IDialogContext? DialogContext { get; set; }
+
public override string DisplayName
{
get
diff --git a/src/EgoErpArchiver/Views/MainWindow.axaml.cs b/src/EgoErpArchiver/Views/MainWindow.axaml.cs
index 2527c6e..2010830 100644
--- a/src/EgoErpArchiver/Views/MainWindow.axaml.cs
+++ b/src/EgoErpArchiver/Views/MainWindow.axaml.cs
@@ -2,11 +2,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using AvaloniaEdit.Folding;
-using EgoEngineLibrary.Frontend.Dialogs.File;
-using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
-
using EgoErpArchiver.Controls;
-using EgoErpArchiver.Dialogs.Erp;
using EgoErpArchiver.ViewModels;
namespace EgoErpArchiver.Views
@@ -21,10 +17,6 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
-
- FileDialogAvalonia.Register(this);
- MessageBoxAvalonia.Register(this);
- ErpDialogAvalonia.Register(this);
}
protected override void OnLoaded(RoutedEventArgs e)
diff --git a/src/EgoErpArchiver/Views/ProgressDialog.axaml b/src/EgoErpArchiver/Views/ProgressDialog.axaml
deleted file mode 100644
index ad4937f..0000000
--- a/src/EgoErpArchiver/Views/ProgressDialog.axaml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/EgoErpArchiver/Views/ProgressDialog.axaml.cs b/src/EgoErpArchiver/Views/ProgressDialog.axaml.cs
deleted file mode 100644
index cc0c79a..0000000
--- a/src/EgoErpArchiver/Views/ProgressDialog.axaml.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Avalonia.Controls;
-
-namespace EgoErpArchiver.Views
-{
- ///
- /// Interaction logic for ProgressDialog.xaml
- ///
- public partial class ProgressDialog : Window
- {
- public ProgressDialog()
- {
- InitializeComponent();
- }
-
- private void statusTextBox_TextChanged(object sender, TextChangedEventArgs e)
- {
- scrollViewer.ScrollToEnd();
- }
- }
-}
diff --git a/src/EgoErpArchiver/Views/ProgressDialogView.axaml b/src/EgoErpArchiver/Views/ProgressDialogView.axaml
new file mode 100644
index 0000000..cf0a629
--- /dev/null
+++ b/src/EgoErpArchiver/Views/ProgressDialogView.axaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/EgoErpArchiver/Views/ProgressDialogView.axaml.cs b/src/EgoErpArchiver/Views/ProgressDialogView.axaml.cs
new file mode 100644
index 0000000..bc7a66c
--- /dev/null
+++ b/src/EgoErpArchiver/Views/ProgressDialogView.axaml.cs
@@ -0,0 +1,16 @@
+using Avalonia.Controls;
+
+namespace EgoErpArchiver.Views;
+
+public partial class ProgressDialogView : UserControl
+{
+ public ProgressDialogView()
+ {
+ InitializeComponent();
+ }
+
+ private void statusTextBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ ScrollViewer.ScrollToEnd();
+ }
+}
\ No newline at end of file
diff --git a/src/EgoJpkArchiver/App.axaml b/src/EgoJpkArchiver/App.axaml
index a2ae825..efcb7a8 100644
--- a/src/EgoJpkArchiver/App.axaml
+++ b/src/EgoJpkArchiver/App.axaml
@@ -1,10 +1,10 @@
-
+
diff --git a/src/EgoJpkArchiver/EgoJpkArchiver.csproj b/src/EgoJpkArchiver/EgoJpkArchiver.csproj
index e13db58..fd61bca 100644
--- a/src/EgoJpkArchiver/EgoJpkArchiver.csproj
+++ b/src/EgoJpkArchiver/EgoJpkArchiver.csproj
@@ -28,6 +28,7 @@
+
diff --git a/src/EgoJpkArchiver/Program.cs b/src/EgoJpkArchiver/Program.cs
index 291e138..11c4423 100644
--- a/src/EgoJpkArchiver/Program.cs
+++ b/src/EgoJpkArchiver/Program.cs
@@ -1,5 +1,8 @@
using Avalonia;
using System.Globalization;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using EgoEngineLibrary.Frontend.Dialogs;
+using Microsoft.Extensions.DependencyInjection;
namespace EgoJpkArchiver;
@@ -18,6 +21,7 @@ public static void Main(string[] args)
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
+ ConfigureServices();
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
@@ -28,4 +32,16 @@ public static AppBuilder BuildAvaloniaApp()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
+
+ private static void ConfigureServices()
+ {
+ var services = new ServiceCollection();
+
+ services.AddSingleton();
+
+ var serviceProvider = services.BuildServiceProvider();
+ DialogService.Instance = serviceProvider.GetRequiredService();
+
+ Ioc.Default.ConfigureServices(serviceProvider);
+ }
}
\ No newline at end of file
diff --git a/src/EgoJpkArchiver/ViewLocator.cs b/src/EgoJpkArchiver/ViewLocator.cs
deleted file mode 100644
index 62323a9..0000000
--- a/src/EgoJpkArchiver/ViewLocator.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using Avalonia.Controls;
-using Avalonia.Controls.Templates;
-using EgoEngineLibrary.Frontend.ViewModels;
-
-namespace EgoJpkArchiver;
-
-///
-/// Given a view model, returns the corresponding view if possible.
-///
-[RequiresUnreferencedCode(
- "Default implementation of ViewLocator involves reflection which may be trimmed away.",
- Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
-public class ViewLocator : IDataTemplate
-{
- public Control? Build(object? param)
- {
- if (param is null)
- return null;
-
- var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
- var type = Type.GetType(name);
-
- if (type != null)
- {
- return (Control)Activator.CreateInstance(type)!;
- }
-
- return new TextBlock { Text = "Not Found: " + name };
- }
-
- public bool Match(object? data)
- {
- return data is ViewModelBase;
- }
-}
diff --git a/src/EgoJpkArchiver/Views/MainWindow.axaml.cs b/src/EgoJpkArchiver/Views/MainWindow.axaml.cs
index a59c2cb..6a0033a 100644
--- a/src/EgoJpkArchiver/Views/MainWindow.axaml.cs
+++ b/src/EgoJpkArchiver/Views/MainWindow.axaml.cs
@@ -1,7 +1,5 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
-using EgoEngineLibrary.Frontend.Dialogs.File;
-using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
using EgoJpkArchiver.ViewModels;
namespace EgoJpkArchiver.Views;
@@ -13,8 +11,6 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
- FileDialogAvalonia.Register(this);
- MessageBoxAvalonia.Register(this);
}
protected override void OnLoaded(RoutedEventArgs e)
diff --git a/src/EgoPssgEditor/Dialogs/Pssg/PssgDialog.cs b/src/EgoPssgEditor/Dialogs/Pssg/PssgDialog.cs
deleted file mode 100644
index 073a318..0000000
--- a/src/EgoPssgEditor/Dialogs/Pssg/PssgDialog.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using CommunityToolkit.Mvvm.Messaging;
-
-namespace EgoPssgEditor.Dialogs.Pssg;
-
-public class PssgDialog
-{
- public static IMessenger Messenger { get; set; } = EgoEngineLibrary.Frontend.Messaging.Messenger.Default;
-}
diff --git a/src/EgoPssgEditor/Dialogs/Pssg/PssgDialogAvalonia.cs b/src/EgoPssgEditor/Dialogs/Pssg/PssgDialogAvalonia.cs
deleted file mode 100644
index 601e7ad..0000000
--- a/src/EgoPssgEditor/Dialogs/Pssg/PssgDialogAvalonia.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Avalonia.Controls;
-
-namespace EgoPssgEditor.Dialogs.Pssg;
-
-public class PssgDialogAvalonia
-{
- public static void Register(Window recipient)
- {
- }
-
- public static void Unregister(Window recipient)
- {
- }
-}
diff --git a/src/EgoPssgEditor/EgoPssgEditor.csproj b/src/EgoPssgEditor/EgoPssgEditor.csproj
index 047c159..4ebb798 100644
--- a/src/EgoPssgEditor/EgoPssgEditor.csproj
+++ b/src/EgoPssgEditor/EgoPssgEditor.csproj
@@ -49,6 +49,7 @@
+
diff --git a/src/EgoPssgEditor/Program.cs b/src/EgoPssgEditor/Program.cs
index 443110f..cb5e11f 100644
--- a/src/EgoPssgEditor/Program.cs
+++ b/src/EgoPssgEditor/Program.cs
@@ -1,5 +1,8 @@
using Avalonia;
using System.Globalization;
+using CommunityToolkit.Mvvm.DependencyInjection;
+using EgoEngineLibrary.Frontend.Dialogs;
+using Microsoft.Extensions.DependencyInjection;
namespace EgoPssgEditor;
@@ -18,6 +21,7 @@ public static void Main(string[] args)
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
+ ConfigureServices();
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
@@ -28,4 +32,16 @@ public static AppBuilder BuildAvaloniaApp()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
+
+ private static void ConfigureServices()
+ {
+ var services = new ServiceCollection();
+
+ services.AddSingleton();
+
+ var serviceProvider = services.BuildServiceProvider();
+ DialogService.Instance = serviceProvider.GetRequiredService();
+
+ Ioc.Default.ConfigureServices(serviceProvider);
+ }
}
diff --git a/src/EgoPssgEditor/ViewLocator.cs b/src/EgoPssgEditor/ViewLocator.cs
index 975b133..a94b0a9 100644
--- a/src/EgoPssgEditor/ViewLocator.cs
+++ b/src/EgoPssgEditor/ViewLocator.cs
@@ -1,50 +1,21 @@
-using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
-using Avalonia.Controls.Templates;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
using EgoPssgEditor.ViewModels;
using EgoPssgEditor.Views;
namespace EgoPssgEditor;
-///
-/// Given a view model, returns the corresponding view if possible.
-///
-[RequiresUnreferencedCode(
- "Default implementation of ViewLocator involves reflection which may be trimmed away.",
- Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
-public class ViewLocator : IDataTemplate
+///
+public sealed class ViewLocator : EgoEngineLibrary.Frontend.ViewLocator
{
- public Control? Build(object? param)
+ ///
+ public override Control? Build(object? param)
{
- if (param is null)
- return null;
-
- Control? view = param switch
+ return param switch
{
AddAttributeViewModel => new AddAttributeView(),
AddElementViewModel => new AddElementView(),
DuplicateTextureViewModel => new DuplicateTextureView(),
- _ => null
+ _ => base.Build(param)
};
- if (view is not null)
- {
- return view;
- }
-
- var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
- var type = Type.GetType(name);
-
- if (type != null)
- {
- return (Control)Activator.CreateInstance(type)!;
- }
-
- return new TextBlock { Text = "Not Found: " + name };
- }
-
- public bool Match(object? data)
- {
- return data is ViewModelBase or DialogViewModel;
}
}
diff --git a/src/EgoPssgEditor/ViewModels/AddAttributeViewModel.cs b/src/EgoPssgEditor/ViewModels/AddAttributeViewModel.cs
index 0c78bc0..1fc4e91 100644
--- a/src/EgoPssgEditor/ViewModels/AddAttributeViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/AddAttributeViewModel.cs
@@ -1,16 +1,19 @@
using System.Collections.ObjectModel;
-using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.Input;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
+using EgoEngineLibrary.Frontend.Dialogs;
+using EgoEngineLibrary.Frontend.ViewModels;
using EgoEngineLibrary.Graphics.Pssg;
+using FluentValidation;
namespace EgoPssgEditor.ViewModels;
-public partial class AddAttributeViewModel : DialogViewModel
+public partial class AddAttributeViewModel : ValidatableViewModelBase, IDialogViewModel
{
private readonly PssgSchemaElement _schemaElement;
- public override string Title => "Add Attribute";
+ public string Title => "Add Attribute";
+
+ public IDialogContext? DialogContext { get; set; }
public ObservableCollection AttributeTypes { get; }
@@ -33,38 +36,39 @@ public PssgAttributeType SelectedAttributeType
set
{
SetProperty(ref field, value);
- ValidateProperty(Value, nameof(Value));
+ ValidateProperty(x => x.Value);
OkCommand.NotifyCanExecuteChanged();
}
}
public bool CanModifyType => SelectedSchemaAttribute is null;
- [Required]
- [CustomValidation(typeof(AddAttributeViewModel), nameof(ValidateValue))]
public string Value
{
get;
set
{
- SetProperty(ref field, value, true);
+ SetProperty(ref field, value);
+ ValidateProperty(x => x.Value);
OkCommand.NotifyCanExecuteChanged();
}
}
- [Required]
- [MinLength(1)]
public string AttributeName
{
get;
set
{
- SetProperty(ref field, value, true);
+ SetProperty(ref field, value);
+ ValidateProperty(x => x.AttributeName);
}
}
+ protected override IValidator Validator { get; }
+
public AddAttributeViewModel(PssgSchemaElement schemaElement)
{
+ Validator = new ClassValidator();
_schemaElement = schemaElement;
AttributeTypes = new ObservableCollection(Enum.GetValues());
Attributes = new ObservableCollection(GetAllAttributes());
@@ -88,24 +92,10 @@ private IEnumerable GetAllAttributes()
}
}
- public static ValidationResult? ValidateValue(string value, ValidationContext context)
- {
- AddAttributeViewModel instance = (AddAttributeViewModel)context.ObjectInstance;
- try
- {
- _ = value.ToPssgValue(instance.SelectedAttributeType);
- return ValidationResult.Success;
- }
- catch
- {
- return new ValidationResult("The value could not be converted to the selected data type.");
- }
- }
-
[RelayCommand(CanExecute = nameof(OkCanExecute))]
private void Ok()
{
- SetDialogResult(true);
+ DialogContext?.Close();
}
private bool OkCanExecute()
{
@@ -115,6 +105,28 @@ private bool OkCanExecute()
[RelayCommand]
private void Cancel()
{
- SetDialogResult(false);
+ DialogContext?.Close(false);
+ }
+
+ private class ClassValidator : AbstractValidator
+ {
+ public ClassValidator()
+ {
+ RuleFor(x => x.Value).NotNull().Custom(ValidateValue);
+ RuleFor(x => x.AttributeName).NotNull().MinimumLength(1);
+ }
+
+ private static void ValidateValue(string value, ValidationContext ctx)
+ {
+ AddAttributeViewModel instance = ctx.InstanceToValidate;
+ try
+ {
+ _ = value.ToPssgValue(instance.SelectedAttributeType);
+ }
+ catch
+ {
+ ctx.AddFailure("The value could not be converted to the selected data type.");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/EgoPssgEditor/ViewModels/AddElementViewModel.cs b/src/EgoPssgEditor/ViewModels/AddElementViewModel.cs
index 4a3623b..134404a 100644
--- a/src/EgoPssgEditor/ViewModels/AddElementViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/AddElementViewModel.cs
@@ -1,31 +1,36 @@
using System.Collections.ObjectModel;
-using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.Input;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
+using EgoEngineLibrary.Frontend.Dialogs;
+using EgoEngineLibrary.Frontend.ViewModels;
using EgoEngineLibrary.Graphics.Pssg;
+using FluentValidation;
namespace EgoPssgEditor.ViewModels;
-public partial class AddElementViewModel : DialogViewModel
+public partial class AddElementViewModel : ValidatableViewModelBase, IDialogViewModel
{
- public override string Title => "Add Element";
+ public string Title => "Add Element";
+
+ public IDialogContext? DialogContext { get; set; }
public ObservableCollection Elements { get; }
- [Required]
- [MinLength(1)]
public string ElementName
{
get;
set
{
- SetProperty(ref field, value, true);
+ SetProperty(ref field, value);
+ ValidateProperty(x => x.ElementName);
OkCommand.NotifyCanExecuteChanged();
}
}
+ protected override IValidator Validator { get; }
+
public AddElementViewModel()
{
+ Validator = new ClassValidator();
Elements = new ObservableCollection(GetAllElements());
ElementName = string.Empty;
}
@@ -38,7 +43,7 @@ private IEnumerable GetAllElements()
[RelayCommand(CanExecute = nameof(OkCanExecute))]
private void Ok()
{
- SetDialogResult(true);
+ DialogContext?.Close();
}
private bool OkCanExecute()
{
@@ -48,6 +53,14 @@ private bool OkCanExecute()
[RelayCommand]
private void Cancel()
{
- SetDialogResult(false);
+ DialogContext?.Close(false);
+ }
+
+ private class ClassValidator : AbstractValidator
+ {
+ public ClassValidator()
+ {
+ RuleFor(x => x.ElementName).NotNull().MinimumLength(1);
+ }
}
}
\ No newline at end of file
diff --git a/src/EgoPssgEditor/ViewModels/DuplicateTextureViewModel.cs b/src/EgoPssgEditor/ViewModels/DuplicateTextureViewModel.cs
index 8bde862..c6a7b88 100644
--- a/src/EgoPssgEditor/ViewModels/DuplicateTextureViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/DuplicateTextureViewModel.cs
@@ -1,34 +1,39 @@
-using System.ComponentModel.DataAnnotations;
-using CommunityToolkit.Mvvm.Input;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
+using CommunityToolkit.Mvvm.Input;
+using EgoEngineLibrary.Frontend.Dialogs;
+using EgoEngineLibrary.Frontend.ViewModels;
+using FluentValidation;
namespace EgoPssgEditor.ViewModels;
-public partial class DuplicateTextureViewModel : DialogViewModel
+public partial class DuplicateTextureViewModel : ValidatableViewModelBase, IDialogViewModel
{
- public override string Title => "Duplicate Texture";
+ public string Title => "Duplicate Texture";
+
+ public IDialogContext? DialogContext { get; set; }
- [Required]
- [MinLength(1)]
public string TextureName
{
get;
set
{
- SetProperty(ref field, value, true);
+ SetProperty(ref field, value);
+ ValidateProperty(x => x.TextureName);
OkCommand.NotifyCanExecuteChanged();
}
}
+ protected override IValidator Validator { get; }
+
public DuplicateTextureViewModel()
{
+ Validator = new ClassValidator();
TextureName = string.Empty;
}
[RelayCommand(CanExecute = nameof(OkCanExecute))]
private void Ok()
{
- SetDialogResult(true);
+ DialogContext?.Close();
}
private bool OkCanExecute()
@@ -39,6 +44,14 @@ private bool OkCanExecute()
[RelayCommand]
private void Cancel()
{
- SetDialogResult(false);
+ DialogContext?.Close(false);
+ }
+
+ private class ClassValidator : AbstractValidator
+ {
+ public ClassValidator()
+ {
+ RuleFor(x => x.TextureName).NotNull().MinimumLength(1);
+ }
}
}
\ No newline at end of file
diff --git a/src/EgoPssgEditor/ViewModels/ElementsWorkspaceViewModel.cs b/src/EgoPssgEditor/ViewModels/ElementsWorkspaceViewModel.cs
index e72e510..c879d45 100644
--- a/src/EgoPssgEditor/ViewModels/ElementsWorkspaceViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/ElementsWorkspaceViewModel.cs
@@ -4,7 +4,7 @@
using System.Xml.Linq;
using CommunityToolkit.Mvvm.Input;
using EgoEngineLibrary.Conversion;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
+using EgoEngineLibrary.Frontend.Dialogs;
using EgoEngineLibrary.Frontend.Dialogs.File;
using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
using EgoEngineLibrary.Graphics.Pssg;
@@ -262,8 +262,8 @@ private async Task AddElement(object parameter)
}
var vm = new AddElementViewModel();
- var ret = await Dialog.ShowDialog(vm);
- if (ret)
+ var ret = await DialogService.Instance.ShowDialogAsync(vm);
+ if (ret == true)
{
PssgElement newElement = elementView.Element.AppendChild(vm.ElementName);
PssgElementViewModel newElementView = new PssgElementViewModel(newElement, elementView);
@@ -321,8 +321,8 @@ private async Task AddAttribute(object parameter)
{
PssgElementViewModel elementView = (PssgElementViewModel)parameter;
var vm = new AddAttributeViewModel(elementView.Element.SchemaElement);
- var res = await Dialog.ShowDialog(vm);
- if (res)
+ var res = await DialogService.Instance.ShowDialogAsync(vm);
+ if (res == true)
{
PssgAttribute attr = elementView.Element.AddAttribute(vm.AttributeName,
vm.Value.ToPssgValue(vm.SelectedAttributeType));
diff --git a/src/EgoPssgEditor/ViewModels/MainViewModel.cs b/src/EgoPssgEditor/ViewModels/MainViewModel.cs
index 17718cb..34d30d0 100644
--- a/src/EgoPssgEditor/ViewModels/MainViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/MainViewModel.cs
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.Input;
using EgoEngineLibrary.Frontend.Dialogs.File;
using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
+using EgoEngineLibrary.Frontend.ViewModels;
using EgoEngineLibrary.Graphics.Pssg;
namespace EgoPssgEditor.ViewModels
diff --git a/src/EgoPssgEditor/ViewModels/PssgAttributeViewModel.cs b/src/EgoPssgEditor/ViewModels/PssgAttributeViewModel.cs
index 7d0fae7..214f3d2 100644
--- a/src/EgoPssgEditor/ViewModels/PssgAttributeViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/PssgAttributeViewModel.cs
@@ -1,9 +1,4 @@
-using EgoEngineLibrary.Graphics;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using EgoEngineLibrary.Frontend.ViewModels;
using EgoEngineLibrary.Graphics.Pssg;
using EgoEngineLibrary.Graphics.Pssg.Elements;
diff --git a/src/EgoPssgEditor/ViewModels/PssgElementViewModel.cs b/src/EgoPssgEditor/ViewModels/PssgElementViewModel.cs
index 82e2e51..db3a958 100644
--- a/src/EgoPssgEditor/ViewModels/PssgElementViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/PssgElementViewModel.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using EgoEngineLibrary.Frontend.ViewModels;
using EgoEngineLibrary.Graphics.Pssg;
namespace EgoPssgEditor.ViewModels
diff --git a/src/EgoPssgEditor/ViewModels/PssgTextureViewModel.cs b/src/EgoPssgEditor/ViewModels/PssgTextureViewModel.cs
index 66dcf75..8e0da0f 100644
--- a/src/EgoPssgEditor/ViewModels/PssgTextureViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/PssgTextureViewModel.cs
@@ -1,12 +1,11 @@
-using BCnEncoder.Decoder;
-using EgoEngineLibrary.Graphics;
-
-using SixLabors.ImageSharp.PixelFormats;
-
-using Avalonia;
+using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
+using BCnEncoder.Decoder;
+using EgoEngineLibrary.Frontend.ViewModels;
+using EgoEngineLibrary.Graphics;
using EgoEngineLibrary.Graphics.Pssg.Elements;
+using SixLabors.ImageSharp.PixelFormats;
namespace EgoPssgEditor.ViewModels
{
diff --git a/src/EgoPssgEditor/ViewModels/TexturesWorkspaceViewModel.cs b/src/EgoPssgEditor/ViewModels/TexturesWorkspaceViewModel.cs
index 49796c5..2b17233 100644
--- a/src/EgoPssgEditor/ViewModels/TexturesWorkspaceViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/TexturesWorkspaceViewModel.cs
@@ -1,5 +1,5 @@
using CommunityToolkit.Mvvm.Input;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
+using EgoEngineLibrary.Frontend.Dialogs;
using EgoEngineLibrary.Frontend.Dialogs.File;
using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
using EgoEngineLibrary.Graphics;
@@ -242,7 +242,7 @@ private async Task DuplicateTexture(object parameter)
TextureName = texView.DisplayName + "_2"
};
- if (await Dialog.ShowDialog(dtVm))
+ if (await DialogService.Instance.ShowDialogAsync(dtVm) == true)
{
// Copy and Edit Name
PssgElement elementToCopy = texView.Texture;
diff --git a/src/EgoPssgEditor/ViewModels/ViewModelBase.cs b/src/EgoPssgEditor/ViewModels/ViewModelBase.cs
deleted file mode 100644
index b3fa912..0000000
--- a/src/EgoPssgEditor/ViewModels/ViewModelBase.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace EgoPssgEditor.ViewModels;
-
-public abstract class ViewModelBase : ObservableObject
-{
- ///
- /// Returns the user-friendly name of this object.
- /// Child classes can set this property to a new value,
- /// or override it to determine the value on-demand.
- ///
- public virtual string DisplayName { get; protected set; } = string.Empty;
-}
diff --git a/src/EgoPssgEditor/ViewModels/WorkspaceViewModel.cs b/src/EgoPssgEditor/ViewModels/WorkspaceViewModel.cs
index 751a701..83e9825 100644
--- a/src/EgoPssgEditor/ViewModels/WorkspaceViewModel.cs
+++ b/src/EgoPssgEditor/ViewModels/WorkspaceViewModel.cs
@@ -1,9 +1,4 @@
-using EgoEngineLibrary.Graphics;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using EgoEngineLibrary.Frontend.ViewModels;
namespace EgoPssgEditor.ViewModels
{
diff --git a/src/EgoPssgEditor/Views/MainWindow.axaml.cs b/src/EgoPssgEditor/Views/MainWindow.axaml.cs
index a30235f..bb14085 100644
--- a/src/EgoPssgEditor/Views/MainWindow.axaml.cs
+++ b/src/EgoPssgEditor/Views/MainWindow.axaml.cs
@@ -1,10 +1,6 @@
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Interactivity;
-using EgoEngineLibrary.Frontend.Dialogs.Custom;
-using EgoEngineLibrary.Frontend.Dialogs.File;
-using EgoEngineLibrary.Frontend.Dialogs.MessageBox;
-using EgoPssgEditor.Dialogs.Pssg;
using EgoPssgEditor.ViewModels;
namespace EgoPssgEditor.Views
@@ -31,11 +27,6 @@ public partial class MainWindow : Window
public MainWindow()
{
InitializeComponent();
-
- DialogAvalonia.Register(this);
- FileDialogAvalonia.Register(this);
- MessageBoxAvalonia.Register(this);
- PssgDialogAvalonia.Register(this);
}
protected override void OnDataContextChanged(EventArgs e)