Merge pull request #71 from irihitech/dialog

Dialog System, and MessageBox remake
This commit is contained in:
Dong Bin
2024-01-27 23:03:49 +08:00
committed by GitHub
46 changed files with 2562 additions and 174 deletions

View File

@@ -0,0 +1,133 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ursa.Common;
namespace Ursa.Controls;
[TemplatePart(PART_OKButton, typeof(Button))]
[TemplatePart(PART_CancelButton, typeof(Button))]
[TemplatePart(PART_YesButton, typeof(Button))]
[TemplatePart(PART_NoButton, typeof(Button))]
[TemplatePart(PART_TitleArea, typeof(Panel))]
public class DefaultDialogControl: DialogControl
{
public const string PART_OKButton = "PART_OKButton";
public const string PART_CancelButton = "PART_CancelButton";
public const string PART_YesButton = "PART_YesButton";
public const string PART_NoButton = "PART_NoButton";
private Button? _okButton;
private Button? _cancelButton;
private Button? _yesButton;
private Button? _noButton;
public static readonly StyledProperty<string?> TitleProperty = AvaloniaProperty.Register<DefaultDialogControl, string?>(
nameof(Title));
public string? Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public static readonly StyledProperty<DialogButton> ButtonsProperty = AvaloniaProperty.Register<DefaultDialogControl, DialogButton>(
nameof(Buttons));
public DialogButton Buttons
{
get => GetValue(ButtonsProperty);
set => SetValue(ButtonsProperty, value);
}
public static readonly StyledProperty<DialogMode> ModeProperty = AvaloniaProperty.Register<DefaultDialogControl, DialogMode>(
nameof(Mode));
public DialogMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
EventHelper.UnregisterClickEvent(DefaultButtonsClose, _okButton, _cancelButton, _yesButton, _noButton);
_okButton = e.NameScope.Find<Button>(PART_OKButton);
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
_yesButton = e.NameScope.Find<Button>(PART_YesButton);
_noButton = e.NameScope.Find<Button>(PART_NoButton);
EventHelper.RegisterClickEvent(DefaultButtonsClose, _yesButton, _noButton, _okButton, _cancelButton);
SetButtonVisibility();
}
private void SetButtonVisibility()
{
switch (Buttons)
{
case DialogButton.None:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case DialogButton.OK:
SetVisibility(_okButton, true);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case DialogButton.OKCancel:
SetVisibility(_okButton, true);
SetVisibility(_cancelButton, true);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case DialogButton.YesNo:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, true);
SetVisibility(_noButton, true);
break;
case DialogButton.YesNoCancel:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, true);
SetVisibility(_yesButton, true);
SetVisibility(_noButton, true);
break;
}
}
private void SetVisibility(Button? button, bool visible)
{
if (button is not null) button.IsVisible = visible;
}
private void DefaultButtonsClose(object sender, RoutedEventArgs args)
{
if (sender is Button button)
{
if (button == _okButton)
{
OnDialogControlClosing(this, DialogResult.OK);
}
else if (button == _cancelButton)
{
OnDialogControlClosing(this, DialogResult.Cancel);
}
else if (button == _yesButton)
{
OnDialogControlClosing(this, DialogResult.Yes);
}
else if (button == _noButton)
{
OnDialogControlClosing(this, DialogResult.No);
}
}
}
}

View File

@@ -0,0 +1,125 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Ursa.Common;
namespace Ursa.Controls;
[TemplatePart(PART_YesButton, typeof(Button))]
[TemplatePart(PART_NoButton, typeof(Button))]
[TemplatePart(PART_OKButton, typeof(Button))]
[TemplatePart(PART_CancelButton, typeof(Button))]
public class DefaultDialogWindow: DialogWindow
{
protected override Type StyleKeyOverride { get; } = typeof(DefaultDialogWindow);
public const string PART_YesButton = "PART_YesButton";
public const string PART_NoButton = "PART_NoButton";
public const string PART_OKButton = "PART_OKButton";
public const string PART_CancelButton = "PART_CancelButton";
private Button? _yesButton;
private Button? _noButton;
private Button? _okButton;
private Button? _cancelButton;
public static readonly StyledProperty<DialogButton> ButtonsProperty = AvaloniaProperty.Register<DefaultDialogWindow, DialogButton>(
nameof(Buttons));
public DialogButton Buttons
{
get => GetValue(ButtonsProperty);
set => SetValue(ButtonsProperty, value);
}
public static readonly StyledProperty<DialogMode> ModeProperty = AvaloniaProperty.Register<DefaultDialogWindow, DialogMode>(
nameof(Mode));
public DialogMode Mode
{
get => GetValue(ModeProperty);
set => SetValue(ModeProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
EventHelper.UnregisterClickEvent(OnDefaultClose, _okButton, _cancelButton, _yesButton, _noButton);
_okButton = e.NameScope.Find<Button>(PART_OKButton);
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
_yesButton = e.NameScope.Find<Button>(PART_YesButton);
_noButton = e.NameScope.Find<Button>(PART_NoButton);
EventHelper.RegisterClickEvent(OnDefaultClose, _yesButton, _noButton, _okButton, _cancelButton);
SetButtonVisibility();
}
private void OnDefaultClose(object sender, RoutedEventArgs e)
{
if (sender == _yesButton)
{
Close(DialogResult.Yes);
return;
}
if(sender == _noButton)
{
Close(DialogResult.No);
return;
}
if(sender == _okButton)
{
Close(DialogResult.OK);
return;
}
if(sender == _cancelButton)
{
Close(DialogResult.Cancel);
return;
}
}
private void SetButtonVisibility()
{
switch (Buttons)
{
case DialogButton.None:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case DialogButton.OK:
SetVisibility(_okButton, true);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case DialogButton.OKCancel:
SetVisibility(_okButton, true);
SetVisibility(_cancelButton, true);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case DialogButton.YesNo:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, true);
SetVisibility(_noButton, true);
break;
case DialogButton.YesNoCancel:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, true);
SetVisibility(_yesButton, true);
SetVisibility(_noButton, true);
break;
}
}
private void SetVisibility(Button? button, bool visible)
{
if (button is not null) button.IsVisible = visible;
}
}

View File

@@ -0,0 +1,173 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Ursa.Common;
namespace Ursa.Controls;
public static class Dialog
{
/// <summary>
/// Show a Window Modal Dialog that with all content fully customized.
/// </summary>
/// <param name="vm"></param>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public static async Task<TResult?> ShowCustomModalAsync<TView, TViewModel, TResult>(TViewModel vm)
where TView : Control, new()
{
var mainWindow = GetMainWindow();
return await ShowCustomModalAsync<TView, TViewModel, TResult>(mainWindow, vm);
}
/// <summary>
/// Show a Window Modal Dialog that with all content fully customized. And the owner of the dialog is specified.
/// </summary>
/// <param name="owner"></param>
/// <param name="vm"></param>
/// <typeparam name="TView"></typeparam>
/// <typeparam name="TViewModel"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public static async Task<TResult> ShowCustomModalAsync<TView, TViewModel, TResult>(Window? owner, TViewModel? vm)
where TView: Control, new()
{
var window = new DialogWindow
{
Content = new TView { DataContext = vm },
DataContext = vm,
};
if (owner is null)
{
window.Show();
return default;
}
else
{
var result = await window.ShowDialog<TResult>(owner);
return result;
}
}
public static async Task<DialogResult> ShowModalAsync<TView, TViewModel>(
Window? owner,
TViewModel vm,
string? title = null,
DialogMode mode = DialogMode.None,
DialogButton buttons = DialogButton.OKCancel)
where TView : Control, new()
{
var window = new DefaultDialogWindow()
{
Content = new TView() { DataContext = vm },
DataContext = vm,
Buttons = buttons,
Title = title,
Mode = mode,
};
if (owner is null)
{
window.Show();
return DialogResult.None;
}
else
{
var result = await window.ShowDialog<DialogResult>(owner);
return result;
}
}
public static async Task<DialogResult> ShowModalAsync<TView, TViewModel>(
TViewModel vm,
string? title = null,
DialogMode mode = DialogMode.None,
DialogButton buttons = DialogButton.OKCancel)
where TView: Control, new()
{
var mainWindow = GetMainWindow();
return await ShowModalAsync<TView, TViewModel>(mainWindow, vm, title, mode, buttons);
}
private static Window? GetMainWindow()
{
var lifetime = Application.Current?.ApplicationLifetime;
return lifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } w } ? w : null;
}
}
public static class OverlayDialog
{
public static Task<DialogResult> ShowModalAsync<TView, TViewModel>(
TViewModel vm,
string? hostId = null,
string? title = null,
DialogMode mode = DialogMode.None,
DialogButton buttons = DialogButton.OKCancel)
where TView : Control, new()
{
var t = new DefaultDialogControl()
{
Content = new TView(){ DataContext = vm },
DataContext = vm,
Buttons = buttons,
Title = title,
Mode = mode,
};
var host = OverlayDialogManager.GetHost(hostId);
host?.AddModalDialog(t);
return t.ShowAsync<DialogResult>();
}
public static Task<TResult> ShowCustomModalAsync<TView, TViewModel, TResult>(
TViewModel vm,
string? hostId = null)
where TView: Control, new()
{
var t = new DialogControl()
{
Content = new TView() { DataContext = vm },
DataContext = vm,
};
var host = OverlayDialogManager.GetHost(hostId);
host?.AddModalDialog(t);
return t.ShowAsync<TResult>();
}
public static void Show<TView, TViewModel>(
TViewModel vm,
string? hostId = null,
string? title = null,
DialogMode mode = DialogMode.None,
DialogButton buttons = DialogButton.OKCancel)
where TView: Control, new()
{
var t = new DefaultDialogControl()
{
Content = new TView() { DataContext = vm },
DataContext = vm,
Buttons = buttons,
Title = title,
Mode = mode,
};
var host = OverlayDialogManager.GetHost(hostId);
host?.AddDialog(t);
}
public static void ShowCustom<TView, TViewModel>(TViewModel vm, string? hostId = null)
where TView: Control, new()
{
var t = new DialogControl()
{
Content = new TView() { DataContext = vm },
DataContext = vm,
};
var host = OverlayDialogManager.GetHost(hostId);
host?.AddDialog(t);
}
}

View File

@@ -0,0 +1,131 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ursa.Common;
namespace Ursa.Controls;
[TemplatePart(PART_CloseButton, typeof(Button))]
[TemplatePart(PART_TitleArea, typeof(Panel))]
[PseudoClasses(PC_Modal)]
public class DialogControl: ContentControl
{
public const string PART_CloseButton = "PART_CloseButton";
public const string PART_TitleArea = "PART_TitleArea";
public const string PC_Modal = ":modal";
protected internal Button? _closeButton;
private Panel? _titleArea;
public event EventHandler<DialogLayerChangeEventArgs>? LayerChanged;
public event EventHandler<object?>? DialogControlClosing;
static DialogControl()
{
DataContextProperty.Changed.AddClassHandler<DialogControl, object?>((o, e) => o.OnDataContextChange(e));
}
private void OnDataContextChange(AvaloniaPropertyChangedEventArgs<object?> args)
{
if (args.OldValue.Value is IDialogContext oldContext)
{
oldContext.Closed-= CloseFromContext;
}
if (args.NewValue.Value is IDialogContext newContext)
{
newContext.Closed += CloseFromContext;
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
EventHelper.UnregisterClickEvent(Close, _closeButton);
_titleArea?.RemoveHandler(PointerMovedEvent, OnTitlePointerMove);
_titleArea?.RemoveHandler(PointerPressedEvent, OnTitlePointerPressed);
_closeButton = e.NameScope.Find<Button>(PART_CloseButton);
_titleArea = e.NameScope.Find<Panel>(PART_TitleArea);
_titleArea?.AddHandler(PointerMovedEvent, OnTitlePointerMove, RoutingStrategies.Bubble);
_titleArea?.AddHandler(PointerPressedEvent, OnTitlePointerPressed, RoutingStrategies.Bubble);
EventHelper.RegisterClickEvent(Close, _closeButton);
}
private void OnTitlePointerPressed(object sender, PointerPressedEventArgs e)
{
e.Source = this;
}
private void OnTitlePointerMove(object sender, PointerEventArgs e)
{
e.Source = this;
}
public Task<T> ShowAsync<T>()
{
var tcs = new TaskCompletionSource<T>();
void OnCloseHandler(object sender, object? args)
{
if (args is T result)
{
tcs.SetResult(result);
DialogControlClosing-= OnCloseHandler;
}
else
{
tcs.SetResult(default(T));
DialogControlClosing-= OnCloseHandler;
}
}
this.DialogControlClosing += OnCloseHandler;
return tcs.Task;
}
private void Close(object sender, RoutedEventArgs args)
{
if (this.DataContext is IDialogContext context)
{
context.Close();
}
else
{
DialogControlClosing?.Invoke(this, DialogResult.None);
}
}
private void CloseFromContext(object sender, object? args)
{
if (this.DataContext is IDialogContext context)
{
DialogControlClosing?.Invoke(this, args);
}
}
public void UpdateLayer(object? o)
{
if (o is DialogLayerChangeType t)
{
LayerChanged?.Invoke(this, new DialogLayerChangeEventArgs(t));
}
}
protected virtual void OnDialogControlClosing(object sender, object? args)
{
DialogControlClosing?.Invoke(this, args);
}
internal void SetAsModal(bool modal)
{
PseudoClasses.Set(PC_Modal, modal);
}
}

View File

@@ -0,0 +1,18 @@
namespace Ursa.Controls;
public class DialogLayerChangeEventArgs
{
public DialogLayerChangeType ChangeType { get; }
public DialogLayerChangeEventArgs(DialogLayerChangeType type)
{
ChangeType = type;
}
}
public enum DialogLayerChangeType
{
BringForward,
SendBackward,
BringToFront,
SendToBack
}

View File

@@ -0,0 +1,11 @@
namespace Ursa.Controls;
public enum DialogMode
{
Info,
Warning,
Error,
Question,
None,
Success,
}

View File

@@ -0,0 +1,67 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ursa.Common;
namespace Ursa.Controls;
[TemplatePart(PART_CloseButton, typeof(Button))]
public class DialogWindow: Window
{
public const string PART_CloseButton = "PART_CloseButton";
protected override Type StyleKeyOverride { get; } = typeof(DialogWindow);
private Button? _closeButton;
static DialogWindow()
{
DataContextProperty.Changed.AddClassHandler<DialogWindow, object?>((o, e) => o.OnDataContextChange(e));
}
private void OnDataContextChange(AvaloniaPropertyChangedEventArgs<object?> args)
{
if (args.OldValue.Value is IDialogContext oldContext)
{
oldContext.Closed-= OnDialogContextClose;
}
if (args.NewValue.Value is IDialogContext newContext)
{
newContext.Closed += OnDialogContextClose;
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
EventHelper.UnregisterClickEvent(OnDefaultClose, _closeButton);
_closeButton = e.NameScope.Find<Button>(PART_CloseButton);
EventHelper.RegisterClickEvent(OnDefaultClose, _closeButton);
}
private void OnDialogContextClose(object? sender, object? args)
{
Close(args);
}
private void OnDefaultClose(object sender, RoutedEventArgs args)
{
if (DataContext is IDialogContext context)
{
context.Close();
}
else
{
Close(null);
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
this.BeginMoveDrag(e);
}
}

View File

@@ -0,0 +1,7 @@
namespace Ursa.Controls;
public interface IDialogContext
{
void Close();
event EventHandler<object?> Closed;
}

View File

@@ -0,0 +1,223 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Ursa.Controls;
public class OverlayDialogHost : Canvas
{
private readonly List<DialogControl> _dialogs = new();
private readonly List<DialogControl> _modalDialogs = new();
private readonly List<Border> _masks = new();
public string? HostId { get; set; }
private Point _lastPoint;
public static readonly StyledProperty<IBrush?> OverlayMaskBrushProperty =
AvaloniaProperty.Register<OverlayDialogHost, IBrush?>(
nameof(OverlayMaskBrush));
public IBrush? OverlayMaskBrush
{
get => GetValue(OverlayMaskBrushProperty);
set => SetValue(OverlayMaskBrushProperty, value);
}
private Border CreateOverlayMask() => new()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Width = this.Bounds.Width,
Height = this.Bounds.Height,
[!BackgroundProperty] = this[!OverlayMaskBrushProperty],
IsVisible = true,
};
protected sealed override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
OverlayDialogManager.RegisterHost(this, HostId);
}
protected sealed override void OnSizeChanged(SizeChangedEventArgs e)
{
base.OnSizeChanged(e);
for (int i = 0; i < _masks.Count; i++)
{
_masks[i].Width = this.Bounds.Width;
_masks[i].Height = this.Bounds.Height;
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
OverlayDialogManager.UnregisterHost(HostId);
base.OnDetachedFromVisualTree(e);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
if (e.Source is DialogControl item)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
var p = e.GetPosition(this);
var left = p.X - _lastPoint.X;
var top = p.Y - _lastPoint.Y;
left = MathUtilities.Clamp(left, 0, Bounds.Width - item.Bounds.Width);
top = MathUtilities.Clamp(top, 0, Bounds.Height - item.Bounds.Height);
Canvas.SetLeft(item, left);
Canvas.SetTop(item, top);
}
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
// base.OnPointerPressed(e);
if (e.Source is DialogControl item)
{
_lastPoint = e.GetPosition(item);
}
}
internal void AddDialog(DialogControl control)
{
this.Children.Add(control);
_dialogs.Add(control);
control.Measure(this.Bounds.Size);
control.Arrange(new Rect(control.DesiredSize));
SetToCenter(control);
control.DialogControlClosing += OnDialogControlClosing;
control.LayerChanged += OnDialogLayerChanged;
ResetZIndices();
}
private void OnDialogControlClosing(object sender, object? e)
{
if (sender is DialogControl control)
{
this.Children.Remove(control);
control.DialogControlClosing -= OnDialogControlClosing;
control.LayerChanged -= OnDialogLayerChanged;
if (_dialogs.Contains(control))
{
_dialogs.Remove(control);
}
else if (_modalDialogs.Contains(control))
{
_modalDialogs.Remove(control);
if (_masks.Count > 0)
{
var last = _masks.Last();
this.Children.Remove(last);
_masks.Remove(last);
if (_masks.Count > 0)
{
_masks.Last().IsVisible = true;
}
}
}
ResetZIndices();
}
}
/// <summary>
/// Add a dialog as a modal dialog to the host
/// </summary>
/// <param name="control"></param>
internal void AddModalDialog(DialogControl control)
{
var mask = CreateOverlayMask();
_masks.Add(mask);
_modalDialogs.Add(control);
control.SetAsModal(true);
for (int i = 0; i < _masks.Count-1; i++)
{
_masks[i].Opacity = 0.5;
}
ResetZIndices();
this.Children.Add(mask);
this.Children.Add(control);
control.Measure(this.Bounds.Size);
control.Arrange(new Rect(control.DesiredSize));
SetToCenter(control);
control.DialogControlClosing += OnDialogControlClosing;
control.LayerChanged += OnDialogLayerChanged;
}
// Handle dialog layer change event
private void OnDialogLayerChanged(object sender, DialogLayerChangeEventArgs e)
{
if (sender is not DialogControl control)
return;
if (!_dialogs.Contains(control))
return;
int index = _dialogs.IndexOf(control);
_dialogs.Remove(control);
int newIndex = index;
switch (e.ChangeType)
{
case DialogLayerChangeType.BringForward:
newIndex = MathUtilities.Clamp(index + 1, 0, _dialogs.Count);
break;
case DialogLayerChangeType.SendBackward:
newIndex = MathUtilities.Clamp(index - 1, 0, _dialogs.Count);
break;
case DialogLayerChangeType.BringToFront:
newIndex = _dialogs.Count;
break;
case DialogLayerChangeType.SendToBack:
newIndex = 0;
break;
}
_dialogs.Insert(newIndex, control);
for (int i = 0; i < _dialogs.Count; i++)
{
_dialogs[i].ZIndex = i;
}
for (int i = 0; i < _masks.Count * 2; i += 2)
{
_masks[i].ZIndex = _dialogs.Count + i;
_modalDialogs[i].ZIndex = _dialogs.Count + i + 1;
}
}
private void ResetZIndices()
{
int index = 0;
for ( int i = 0; i< _dialogs.Count; i++)
{
_dialogs[i].ZIndex = index;
index++;
}
for(int i = 0; i< _masks.Count; i++)
{
_masks[i].ZIndex = index;
index++;
_modalDialogs[i].ZIndex = index;
index++;
}
}
private void SetToCenter(DialogControl? control)
{
// return;
if (control is null) return;
double left = (this.Bounds.Width - control.Bounds.Width) / 2;
double top = (this.Bounds.Height - control.Bounds.Height) / 2;
left = MathUtilities.Clamp(left, 0, Bounds.Width);
top = MathUtilities.Clamp(top, 0, Bounds.Height);
Canvas.SetLeft(control, left);
Canvas.SetTop(control, top);
}
}

View File

@@ -0,0 +1,42 @@
using System.Collections.Concurrent;
namespace Ursa.Controls;
internal static class OverlayDialogManager
{
private static OverlayDialogHost? _defaultHost;
private static readonly ConcurrentDictionary<string, OverlayDialogHost> Hosts = new();
public static void RegisterHost(OverlayDialogHost host, string? id)
{
if (id == null)
{
if (_defaultHost != null)
{
throw new InvalidOperationException("Cannot register multiple OverlayDialogHost with empty HostId");
}
_defaultHost = host;
return;
}
Hosts.TryAdd(id, host);
}
public static void UnregisterHost(string? id)
{
if (id is null)
{
_defaultHost = null;
return;
}
Hosts.TryRemove(id, out _);
}
public static OverlayDialogHost? GetHost(string? id)
{
if (id is null)
{
return _defaultHost;
}
return Hosts.TryGetValue(id, out var host) ? host : null;
}
}

View File

@@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Styling;
namespace Ursa.Controls;
@@ -56,4 +57,25 @@ public static class MessageBox
var result = await messageWindow.ShowDialog<MessageBoxResult>(owner);
return result;
}
public static async Task<MessageBoxResult> ShowOverlayAsync(
string message,
string? title = null,
string? hostId = null,
MessageBoxIcon icon = MessageBoxIcon.None,
MessageBoxButton button = MessageBoxButton.OKCancel)
{
var host = OverlayDialogManager.GetHost(hostId);
if (host is null) return MessageBoxResult.None;
var messageControl = new MessageBoxControl()
{
Content = message,
Title = title,
Buttons = button,
MessageIcon = icon,
};
host.AddModalDialog(messageControl);
var result = await messageControl.ShowAsync<MessageBoxResult>();
return result;
}
}

View File

@@ -0,0 +1,135 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using Ursa.Common;
namespace Ursa.Controls;
/// <summary>
/// The messageBox used to display in OverlayDialogHost.
/// </summary>
[TemplatePart(PART_CloseButton, typeof(Button))]
[TemplatePart(PART_NoButton, typeof(Button))]
[TemplatePart(PART_OKButton, typeof(Button))]
[TemplatePart(PART_CancelButton, typeof(Button))]
[TemplatePart(PART_YesButton, typeof(Button))]
public class MessageBoxControl: DialogControl
{
public const string PART_YesButton = "PART_YesButton";
public const string PART_NoButton = "PART_NoButton";
public const string PART_OKButton = "PART_OKButton";
public const string PART_CancelButton = "PART_CancelButton";
private Button? _yesButton;
private Button? _noButton;
private Button? _okButton;
private Button? _cancelButton;
public static readonly StyledProperty<MessageBoxIcon> MessageIconProperty =
AvaloniaProperty.Register<MessageBoxWindow, MessageBoxIcon>(
nameof(MessageIcon));
public MessageBoxIcon MessageIcon
{
get => GetValue(MessageIconProperty);
set => SetValue(MessageIconProperty, value);
}
public static readonly StyledProperty<MessageBoxButton> ButtonsProperty = AvaloniaProperty.Register<MessageBoxControl, MessageBoxButton>(
nameof(Buttons), MessageBoxButton.OK);
public MessageBoxButton Buttons
{
get => GetValue(ButtonsProperty);
set => SetValue(ButtonsProperty, value);
}
public static readonly StyledProperty<string?> TitleProperty = AvaloniaProperty.Register<MessageBoxControl, string?>(
nameof(Title));
public string? Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
static MessageBoxControl()
{
ButtonsProperty.Changed.AddClassHandler<MessageBoxControl>((o, e) => { o.SetButtonVisibility(); });
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
EventHelper.UnregisterClickEvent(DefaultButtonsClose, _okButton, _cancelButton, _yesButton, _noButton);
_okButton = e.NameScope.Find<Button>(PART_OKButton);
_cancelButton = e.NameScope.Find<Button>(PART_CancelButton);
_yesButton = e.NameScope.Find<Button>(PART_YesButton);
_noButton = e.NameScope.Find<Button>(PART_NoButton);
EventHelper.RegisterClickEvent(DefaultButtonsClose, _okButton, _cancelButton, _yesButton, _noButton);
SetButtonVisibility();
}
private void DefaultButtonsClose(object sender, RoutedEventArgs e)
{
if (sender is Button button)
{
if (button == _okButton)
{
OnDialogControlClosing(this, MessageBoxResult.OK);
}
else if (button == _cancelButton)
{
OnDialogControlClosing(this, MessageBoxResult.Cancel);
}
else if (button == _yesButton)
{
OnDialogControlClosing(this, MessageBoxResult.Yes);
}
else if (button == _noButton)
{
OnDialogControlClosing(this, MessageBoxResult.No);
}
}
}
private void SetButtonVisibility()
{
switch (Buttons)
{
case MessageBoxButton.OK:
SetVisibility(_okButton, true);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case MessageBoxButton.OKCancel:
SetVisibility(_okButton, true);
SetVisibility(_cancelButton, true);
SetVisibility(_yesButton, false);
SetVisibility(_noButton, false);
break;
case MessageBoxButton.YesNo:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, false);
SetVisibility(_yesButton, true);
SetVisibility(_noButton, true);
break;
case MessageBoxButton.YesNoCancel:
SetVisibility(_okButton, false);
SetVisibility(_cancelButton, true);
SetVisibility(_yesButton, true);
SetVisibility(_noButton, true);
break;
}
}
private void SetVisibility(Button? button, bool visible)
{
if (button is not null) button.IsVisible = visible;
}
}

View File

@@ -5,6 +5,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Ursa.Common;
namespace Ursa.Controls;