diff --git a/DialogHost.Avalonia/DialogHost.axaml.cs b/DialogHost.Avalonia/DialogHost.axaml.cs index 6c68fbe..9e27b6b 100644 --- a/DialogHost.Avalonia/DialogHost.axaml.cs +++ b/DialogHost.Avalonia/DialogHost.axaml.cs @@ -39,7 +39,7 @@ public class DialogHost : ContentControl { /// /// Tracks all loaded instances of DialogHost. /// - private static readonly HashSet _loadedInstances = new(); + private static readonly HashSet _loadedInstances = []; /// /// Identifies the property. @@ -50,6 +50,14 @@ public class DialogHost : ContentControl { o => o.Identifier, (o, v) => o.Identifier = v); + /// + /// Identified the + /// + public static readonly DirectProperty IsMultipleDialogsEnabledProperty = + AvaloniaProperty.RegisterDirect(nameof(IsMultipleDialogsEnabled), + o => o.IsMultipleDialogsEnabled, + (o, v) => o.IsMultipleDialogsEnabled = v); + /// /// Identifies the property. /// @@ -191,9 +199,8 @@ public static readonly StyledProperty BlurBackgroundProperty /// public static readonly StyledProperty BlurBackgroundRadiusProperty = AvaloniaProperty.Register(nameof(BlurBackgroundRadius), DefaultBlurRadius); - - private DialogClosingEventHandler? _asyncShowClosingEventHandler; - private DialogOpenedEventHandler? _asyncShowOpenedEventHandler; + + private bool _isMultipleDialogsEnabled; private ICommand _closeDialogCommand; @@ -205,7 +212,6 @@ public static readonly StyledProperty BlurBackgroundRadiusProperty private DialogOpenedEventHandler? _dialogOpenedCallback; - private TaskCompletionSource? _dialogTaskCompletionSource; private bool _disableOpeningAnimation; private string? _identifier; @@ -213,14 +219,15 @@ public static readonly StyledProperty BlurBackgroundRadiusProperty private bool _isOpen; private ICommand _openDialogCommand; - private DialogOverlayPopupHost? _overlayPopupHost; + private readonly List _overlayPopupHosts = []; private IDialogPopupPositioner? _popupPositioner; private IInputElement? _restoreFocusDialogClose; - private Panel _root; + internal Panel Root { get; private set; } private IDisposable? _templateDisposables; + private readonly DisposeList _disposeList = new(); /// /// Initializes a new instance of the class. @@ -269,6 +276,14 @@ public string? Identifier { get => _identifier; set => SetAndRaise(IdentifierProperty, ref _identifier, value); } + + /// + /// Gets or sets is opening multiple dialogs at the same time enabled + /// + public bool IsMultipleDialogsEnabled { + get => _isMultipleDialogsEnabled; + set => SetAndRaise(IsMultipleDialogsEnabledProperty, ref _isMultipleDialogsEnabled, value); + } /// /// Gets or sets the content to display in the dialog. @@ -310,7 +325,7 @@ public bool IsOpen { set { if (SetAndRaise(IsOpenProperty, ref _isOpen, value)) { - IsOpenPropertyChangedCallback(this, value); + IsOpenPropertyChangedCallback(value); } } } @@ -364,9 +379,16 @@ public double BlurBackgroundRadius { } /// - /// Returns a DialogSession for the currently open dialog for managing it programmatically. If no dialog is open, CurrentSession will return null + /// Returns a DialogSession for the currently open dialog for managing it programmatically.
+ /// If no dialog is open, CurrentSession will return null.
+ /// If multiple dialogs are open, the last one will be returned. Refer to to see all. + ///
+ public DialogSession? CurrentSession => _overlayPopupHosts.LastOrDefault()?.Session; + + /// + /// Return a list of open dialogs /// - public DialogSession? CurrentSession { get; private set; } + public IReadOnlyList CurrentSessions => [.. _overlayPopupHosts.Select(item => item.Session)]; /// /// Gets or sets the callback for when the dialog is closing. @@ -381,7 +403,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// /// Content to show (can be a control or view model). /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content) + public static Task Show(object? content) => Show(content, dialogIdentifier: null); /// @@ -390,7 +412,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Content to show (can be a control or view model). /// Allows access to opened event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogOpenedEventHandler openedEventHandler) + public static Task Show(object? content, DialogOpenedEventHandler openedEventHandler) => Show(content, (string?)null, openedEventHandler, null); /// @@ -399,7 +421,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Content to show (can be a control or view model). /// Allows access to closing event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogClosingEventHandler closingEventHandler) + public static Task Show(object? content, DialogClosingEventHandler closingEventHandler) => Show(content, (string?)null, null, closingEventHandler); /// @@ -409,7 +431,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Allows access to opened event which would otherwise have been subscribed to on a instance. /// Allows access to closing event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogOpenedEventHandler? openedEventHandler, DialogClosingEventHandler? closingEventHandler) + public static Task Show(object? content, DialogOpenedEventHandler? openedEventHandler, DialogClosingEventHandler? closingEventHandler) => Show(content, (string?)null, openedEventHandler, closingEventHandler); /// @@ -418,7 +440,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Content to show (can be a control or view model). /// of the instance where the dialog should be shown. Typically this will match an identifier set in XAML. null is allowed. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, string? dialogIdentifier) + public static Task Show(object? content, string? dialogIdentifier) => Show(content, dialogIdentifier, null, null); /// @@ -428,7 +450,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// of the instance where the dialog should be shown. Typically this will match an identifier set in XAML. null is allowed. /// Allows access to opened event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, string? dialogIdentifier, DialogOpenedEventHandler openedEventHandler) + public static Task Show(object? content, string? dialogIdentifier, DialogOpenedEventHandler openedEventHandler) => Show(content, dialogIdentifier, openedEventHandler, null); /// @@ -438,30 +460,30 @@ public DialogClosingEventHandler? DialogClosingCallback { /// of the instance where the dialog should be shown. Typically this will match an identifier set in XAML. null is allowed. /// Allows access to closing event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, string? dialogIdentifier, DialogClosingEventHandler closingEventHandler) + public static Task Show(object? content, string? dialogIdentifier, DialogClosingEventHandler closingEventHandler) => Show(content, dialogIdentifier, null, closingEventHandler); /// /// Shows a modal dialog. To use, a instance must be in a visual tree (typically this may be specified towards the root of a Window's XAML). /// - /// Content to show (can be a control or view model). + /// Content to show (can be a control or view model). null to open dialog with a /// of the instance where the dialog should be shown. Typically this will match an identifier set in XAML. null is allowed. /// Allows access to opened event which would otherwise have been subscribed to on a instance. /// Allows access to closing event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, string? dialogIdentifier, DialogOpenedEventHandler? openedEventHandler, + public static Task Show(object? content, string? dialogIdentifier, DialogOpenedEventHandler? openedEventHandler, DialogClosingEventHandler? closingEventHandler) { - if (content is null) throw new ArgumentNullException(nameof(content)); + //if (content is null) throw new ArgumentNullException(nameof(content)); return GetInstance(dialogIdentifier).ShowCore(content, openedEventHandler, closingEventHandler); } /// /// Shows a modal dialog. To use, a instance must be in a visual tree (typically this may be specified towards the root of a Window's XAML). /// - /// Content to show (can be a control or view model). + /// Content to show (can be a control or view model). null to open dialog with a /// Instance of where the dialog should be shown. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogHost instance) + public static Task Show(object? content, DialogHost instance) => Show(content, instance, null, null); /// @@ -471,7 +493,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Instance of where the dialog should be shown. /// Allows access to opened event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogHost instance, DialogOpenedEventHandler openedEventHandler) + public static Task Show(object? content, DialogHost instance, DialogOpenedEventHandler openedEventHandler) => Show(content, instance, openedEventHandler, null); /// @@ -481,7 +503,7 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Instance of where the dialog should be shown. /// Allows access to closing event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogHost instance, DialogClosingEventHandler closingEventHandler) + public static Task Show(object? content, DialogHost instance, DialogClosingEventHandler closingEventHandler) => Show(content, instance, null, closingEventHandler); /// @@ -492,9 +514,9 @@ public DialogClosingEventHandler? DialogClosingCallback { /// Allows access to opened event which would otherwise have been subscribed to on a instance. /// Allows access to closing event which would otherwise have been subscribed to on a instance. /// Task result is the parameter used to close the dialog, typically what is passed to the command. - public static Task Show(object content, DialogHost instance, DialogOpenedEventHandler? openedEventHandler, + public static Task Show(object? content, DialogHost instance, DialogOpenedEventHandler? openedEventHandler, DialogClosingEventHandler? closingEventHandler) { - if (content is null) throw new ArgumentNullException(nameof(content)); + //if (content is null) throw new ArgumentNullException(nameof(content)); if (instance is null) throw new ArgumentNullException(nameof(instance)); return instance.ShowCore(content, openedEventHandler, closingEventHandler); } @@ -504,21 +526,73 @@ public DialogClosingEventHandler? DialogClosingCallback { public static void Close(string? dialogIdentifier) => Close(dialogIdentifier, null); + /// + /// Close a modal dialog, with content + /// + /// of the instance where the dialog should be closed. Typically this will match an identifier set in XAML. + /// to provide to close handler + public static void Close(string? dialogIdentifier, object? parameter) + => Close(dialogIdentifier, parameter, null); + /// /// Close a modal dialog. /// /// of the instance where the dialog should be closed. Typically this will match an identifier set in XAML. - /// to provide to close handler - public static void Close(string? dialogIdentifier, object? parameter) { + /// to provide to close handler + /// the open content + public static void Close(string? dialogIdentifier, object? parameter, object? content) { + var dialogHost = GetInstance(dialogIdentifier); + if (dialogHost != null) { + if (content == null) { + if (dialogHost.CurrentSession is { } currentSession) { + currentSession.Close(parameter); + return; + } + } + else { + foreach (var item in dialogHost._overlayPopupHosts) { + if (item.Content == content) { + item.Session.Close(parameter); + return; + } + } + } + } + + throw new InvalidOperationException("DialogHost is not open."); + } + + /// + /// Make the content pop in dialog + /// + /// of the instance where the dialog should be shown. Typically this will match an identifier set in XAML. null is allowed. + /// Content to show (can be a control or view model). + public static void Pop(string? dialogIdentifier, object? content) { var dialogHost = GetInstance(dialogIdentifier); - if (dialogHost.CurrentSession is { } currentSession) { - currentSession.Close(parameter); + if (dialogHost != null) { + dialogHost.PopCore(content); return; } throw new InvalidOperationException("DialogHost is not open."); } + private void PopCore(object? content) { + foreach (var item in _overlayPopupHosts) { + if (item.Content == content) { + PopCoreHost(item); + return; + } + } + } + + private void PopCoreHost(DialogOverlayPopupHost host) { + _overlayPopupHosts.Remove(host); + _overlayPopupHosts.Add(host); + host.Pop(); + return; + } + /// /// Retrieve the current dialog session for a DialogHost /// @@ -551,73 +625,46 @@ private static DialogHost GetInstance(string? dialogIdentifier) { return targets[0]; } - private async Task ShowCore(object content, DialogOpenedEventHandler? openedEventHandler, + private async Task ShowCore(object? content, DialogOpenedEventHandler? openedEventHandler, DialogClosingEventHandler? closingEventHandler) { - if (IsOpen) - throw new InvalidOperationException("DialogHost is already open."); - - _dialogTaskCompletionSource = new TaskCompletionSource(); + if (!IsMultipleDialogsEnabled && IsOpen) + throw new InvalidOperationException("DialogHost is already open and IsMultipleDialogsSupported is false."); - if (content != null) - DialogContent = content; + var task = AddHost(content, openedEventHandler, closingEventHandler); - _asyncShowOpenedEventHandler = openedEventHandler; - _asyncShowClosingEventHandler = closingEventHandler; IsOpen = true; - var result = await _dialogTaskCompletionSource.Task; - - _asyncShowOpenedEventHandler = null; - _asyncShowClosingEventHandler = null; + var result = await task.Task; return result; } - private static void IsOpenPropertyChangedCallback(DialogHost dialogHost, bool newValue) { + private void IsOpenPropertyChangedCallback(bool newValue) { if (newValue) { - dialogHost.CurrentSession = new DialogSession(dialogHost); - dialogHost._restoreFocusDialogClose = TopLevel.GetTopLevel(dialogHost)?.FocusManager?.GetFocusedElement(); + if (_overlayPopupHosts.Count == 0) { + AddHost(null); + } - if (dialogHost._overlayPopupHost != null) - dialogHost._overlayPopupHost.IsOpen = true; + _restoreFocusDialogClose = TopLevel.GetTopLevel(this)?.FocusManager?.GetFocusedElement(); //multiple ways of calling back that the dialog has opened: // * routed event // * straight forward dependency property // * handler provided to the async show method - var dialogOpenedEventArgs = new DialogOpenedEventArgs(dialogHost.CurrentSession, DialogOpenedEvent); - dialogHost.OnDialogOpened(dialogOpenedEventArgs); - dialogHost.DialogOpenedCallback?.Invoke(dialogHost, dialogOpenedEventArgs); - dialogHost._asyncShowOpenedEventHandler?.Invoke(dialogHost, dialogOpenedEventArgs); + var dialogOpenedEventArgs = new DialogOpenedEventArgs(CurrentSession!, DialogOpenedEvent); + OnDialogOpened(dialogOpenedEventArgs); + DialogOpenedCallback?.Invoke(this, dialogOpenedEventArgs); + CurrentSession?.ShowOpened(this, dialogOpenedEventArgs); // dialogHost._overlayPopupHost?.ConfigurePosition(dialogHost._root, PlacementMode.AnchorAndGravity, new Point()); } else { - object? closeParameter = null; - if (dialogHost.CurrentSession is { } session) { - if (!session.IsEnded) { - session.Close(session.CloseParameter); - } + RemoveAllHost(); - //DialogSession.Close may attempt to cancel the closing of the dialog. - //When the dialog is closed in this manner it is not valid - if (!session.IsEnded) { - throw new InvalidOperationException($"Cannot cancel dialog closing after {nameof(IsOpen)} property has been set to {bool.FalseString}"); - } - - closeParameter = session.CloseParameter; - dialogHost.CurrentSession = null; - } - - if (dialogHost._overlayPopupHost != null) - dialogHost._overlayPopupHost.IsOpen = false; - //NB: _dialogTaskCompletionSource is only set in the case where the dialog is shown with Show - dialogHost._dialogTaskCompletionSource?.TrySetResult(closeParameter); - - dialogHost._restoreFocusDialogClose?.Focus(); + _restoreFocusDialogClose?.Focus(); } - dialogHost.RaiseCommandsCanExecuteChanged(); + RaiseCommandsCanExecuteChanged(); } /// @@ -632,34 +679,134 @@ protected void RaiseCommandsCanExecuteChanged() { protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _templateDisposables?.Dispose(); - _root = e.NameScope.Find(DialogHostRoot) + Root = e.NameScope.Find(DialogHostRoot) ?? throw new InvalidOperationException($"No Panel with name {DialogHostRoot} found. " + $"Did you add the styles as stated in getting started?"); - _overlayPopupHost = new DialogOverlayPopupHost(_root) { - Content = DialogContent, ContentTemplate = DialogContentTemplate, Template = PopupTemplate, - Padding = DialogMargin, ClipToBounds = false, DisableOpeningAnimation = DisableOpeningAnimation, - PopupPositioner = PopupPositioner - }; if (IsOpen) { - _overlayPopupHost.IsOpen = true; + AddHost(null); + //_overlayPopupHost.IsOpen = true; // _overlayPopupHost?.ConfigurePosition(_root, PlacementMode.AnchorAndGravity, new Point()); } _templateDisposables = new CompositeDisposable() { // this.GetObservable(BoundsProperty) // .Subscribe(rect => _overlayPopupHost?.ConfigurePosition(_root, PlacementMode.AnchorAndGravity, new Point())), - _overlayPopupHost!.Bind(DisableOpeningAnimationProperty, this.GetBindingObservable(DisableOpeningAnimationProperty)), - _overlayPopupHost!.Bind(ContentProperty, this.GetBindingObservable(DialogContentProperty)), - _overlayPopupHost!.Bind(ContentTemplateProperty, this.GetBindingObservable(DialogContentTemplateProperty)), - _overlayPopupHost!.Bind(TemplateProperty, this.GetBindingObservable(PopupTemplateProperty)), - _overlayPopupHost!.Bind(PaddingProperty, this.GetBindingObservable(DialogMarginProperty)), - _overlayPopupHost!.Bind(PopupPositionerProperty, this.GetBindingObservable(PopupPositionerProperty)), + _disposeList, e.NameScope.Find(ContentCoverName)?.AddDisposableHandler(PointerReleasedEvent, ContentCoverGrid_OnPointerReleased) ?? EmptyDisposable.Instance }; base.OnApplyTemplate(e); } + private class DisposeList : IDisposable { + private readonly Dictionary> _hostDisposeList = []; + + public void Dispose() { + foreach (var list in _hostDisposeList.Values) { + foreach (var item in list) { + item.Dispose(); + } + } + _hostDisposeList.Clear(); + } + + public void AddDispose(DialogOverlayPopupHost host, params IDisposable[] disposables) { + _hostDisposeList[host] = [.. disposables]; + } + + public void RemoveDispose(DialogOverlayPopupHost host) { + if (_hostDisposeList.TryGetValue(host, out var list)) { + _hostDisposeList.Remove(host); + foreach (var item in list) { + item.Dispose(); + } + } + } + } + + private TaskCompletionSource AddHost(object? content, DialogOpenedEventHandler? open = null, DialogClosingEventHandler? closing = null) { + if (DialogContent == null && content == null) { + throw new ArgumentNullException(nameof(content), "DialogContent and content is null"); + } + + if (content != null) { + foreach (var item in _overlayPopupHosts) { + if (item.Content == content) { + PopCoreHost(item); + return item.DialogTaskCompletionSource; + } + } + } + else { + foreach (var item in _overlayPopupHosts) { + if (item.Content == DialogContent) { + PopCoreHost(item); + return item.DialogTaskCompletionSource; + } + } + } + + var host = new DialogOverlayPopupHost(this, open, closing) { + Content = content ?? DialogContent, + ContentTemplate = DialogContentTemplate, + Template = PopupTemplate, + Padding = DialogMargin, + ClipToBounds = false, + DisableOpeningAnimation = DisableOpeningAnimation, + PopupPositioner = PopupPositioner + }; + + _disposeList.AddDispose(host, + host.Bind(DisableOpeningAnimationProperty, this.GetBindingObservable(DisableOpeningAnimationProperty)), + content is null ? host.Bind(ContentProperty, this.GetBindingObservable(DialogContentProperty)) : EmptyDisposable.Instance, + host.Bind(ContentTemplateProperty, this.GetBindingObservable(DialogContentTemplateProperty)), + host.Bind(TemplateProperty, this.GetBindingObservable(PopupTemplateProperty)), + host.Bind(PaddingProperty, this.GetBindingObservable(DialogMarginProperty)), + host.Bind(PopupPositionerProperty, this.GetBindingObservable(PopupPositionerProperty))); + + host.IsOpen = true; + + _overlayPopupHosts.Add(host); + + return host.DialogTaskCompletionSource; + } + + private void RemoveHost(DialogOverlayPopupHost? host) { + if (host == null) { + return; + } + + var session = host.Session; + if (!session.IsEnded) { + session.Close(session.CloseParameter); + } + + //DialogSession.Close may attempt to cancel the closing of the dialog. + //When the dialog is closed in this manner it is not valid + if (!session.IsEnded) { + throw new InvalidOperationException($"Cannot cancel dialog closing after {nameof(IsOpen)} property has been set to {bool.FalseString}"); + } + + //NB: _dialogTaskCompletionSource is only set in the case where the dialog is shown with Show + host.DialogTaskCompletionSource.TrySetResult(session.CloseParameter); + host.IsOpen = false; + host.Content = null; + + _disposeList.RemoveDispose(host); + + _overlayPopupHosts.Remove(host); + + if (_overlayPopupHosts.Count == 0) { + SetAndRaise(IsOpenProperty, ref _isOpen, false); + } + } + + private void RemoveAllHost() { + foreach (var host in _overlayPopupHosts.ToArray().Reverse()) { + RemoveHost(host); + } + } + private void ContentCoverGrid_OnPointerReleased(object sender, PointerReleasedEventArgs e) { if (CloseOnClickAway && CurrentSession != null) { InternalClose(CloseOnClickAwayParameter); @@ -707,14 +854,15 @@ internal void InternalClose(object? parameter) { var dialogClosingEventArgs = new DialogClosingEventArgs(currentSession, DialogClosingEvent); OnDialogClosing(dialogClosingEventArgs); DialogClosingCallback?.Invoke(this, dialogClosingEventArgs); - _asyncShowClosingEventHandler?.Invoke(this, dialogClosingEventArgs); + CurrentSession?.ShowClosing(this, dialogClosingEventArgs); if (dialogClosingEventArgs.IsCancelled) { currentSession.IsEnded = false; return; } - IsOpen = false; + //IsOpen = false; + RemoveHost(currentSession.Host); } /// @@ -728,4 +876,4 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e base.OnDetachedFromVisualTree(e); _loadedInstances.Remove(this); } -} \ No newline at end of file +} diff --git a/DialogHost.Avalonia/DialogOverlayPopupHost.axaml.cs b/DialogHost.Avalonia/DialogOverlayPopupHost.axaml.cs index 62244b0..5833397 100644 --- a/DialogHost.Avalonia/DialogOverlayPopupHost.axaml.cs +++ b/DialogHost.Avalonia/DialogOverlayPopupHost.axaml.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Input; @@ -8,7 +9,7 @@ namespace DialogHostAvalonia; -public class DialogOverlayPopupHost(Panel root) : ContentControl, ICustomKeyboardNavigation { +public class DialogOverlayPopupHost : ContentControl, ICustomKeyboardNavigation { public static readonly DirectProperty IsOpenProperty = AvaloniaProperty.RegisterDirect( nameof(IsOpen), @@ -28,10 +29,21 @@ public class DialogOverlayPopupHost(Panel root) : ContentControl, ICustomKeyboar o => o.PopupPositioner, (o, v) => o.PopupPositioner = v); + private readonly DialogHost _host; + private bool _disableOpeningAnimation; private bool _isOpen; + private IDialogPopupPositioner? _popupPositioner; + internal readonly TaskCompletionSource DialogTaskCompletionSource = new(); + internal readonly DialogSession Session; + + public DialogOverlayPopupHost(DialogHost host, DialogOpenedEventHandler? open, DialogClosingEventHandler? closing) { + _host = host; + Session = new(host, this, open, closing); + } + public bool IsOpen { get => _isOpen; set { @@ -64,9 +76,9 @@ public IDialogPopupPositioner? PopupPositioner { } } - public void Show() { + internal void Show() { if (Parent == null) { - root.Children.Add(this); + _host.Root.Children.Add(this); } // Set the minimum priority to allow overriding it everywhere @@ -75,8 +87,8 @@ public void Show() { UpdatePosition(); } - public void Hide() { - root.Children.Remove(this); + internal void Hide() { + _host.Root.Children.Remove(this); } protected override Size MeasureOverride(Size availableSize) { @@ -139,4 +151,9 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang base.OnPropertyChanged(change); } + + internal void Pop() { + Hide(); + Show(); + } } \ No newline at end of file diff --git a/DialogHost.Avalonia/DialogSession.cs b/DialogHost.Avalonia/DialogSession.cs index 9a137f9..964354c 100644 --- a/DialogHost.Avalonia/DialogSession.cs +++ b/DialogHost.Avalonia/DialogSession.cs @@ -8,8 +8,16 @@ namespace DialogHostAvalonia; public class DialogSession { private readonly DialogHost _owner; - internal DialogSession(DialogHost owner) - => _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + private DialogClosingEventHandler? _asyncShowClosingEventHandler; + private DialogOpenedEventHandler? _asyncShowOpenedEventHandler; + + internal DialogSession(DialogHost owner, DialogOverlayPopupHost host, DialogOpenedEventHandler? open, DialogClosingEventHandler? closing) { + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + Host = host ?? throw new ArgumentNullException(nameof(host)); + + _asyncShowOpenedEventHandler = open; + _asyncShowClosingEventHandler = closing; + } /// /// Indicates if the dialog session has ended. Once ended no further method calls will be permitted. @@ -24,18 +32,22 @@ internal DialogSession(DialogHost owner) /// internal object? CloseParameter { get; set; } + /// + /// Dialog Overlay Popup Host, To set content + /// + public DialogOverlayPopupHost Host { get; private set; } + /// /// Gets the which is currently displayed, so this could be a view model or a UI element. /// - public object? Content => _owner.DialogContent; + public object? Content => Host.Content; /// /// Update the current content in the dialog. /// /// - public void UpdateContent(object content) - { - _owner.DialogContent = content ?? throw new ArgumentNullException(nameof(content)); + public void UpdateContent(object content) { + Host.Content = content ?? throw new ArgumentNullException(nameof(content)); } /// @@ -60,4 +72,12 @@ public void Close(object? parameter) _owner.InternalClose(parameter); } + + internal void ShowOpened(object obj, DialogOpenedEventArgs args) { + _asyncShowOpenedEventHandler?.Invoke(obj, args); + } + + internal void ShowClosing(object obj, DialogClosingEventArgs args) { + _asyncShowClosingEventHandler?.Invoke(obj, args); + } } \ No newline at end of file diff --git a/DialogHost.Demo/Views/MainWindow.axaml b/DialogHost.Demo/Views/MainWindow.axaml index ab5a60b..bff7d97 100644 --- a/DialogHost.Demo/Views/MainWindow.axaml +++ b/DialogHost.Demo/Views/MainWindow.axaml @@ -78,12 +78,23 @@ MinHeight="200" MinWidth="200" BorderThickness="1" BorderBrush="Black"> + + + It's dialog content + + + @@ -92,8 +103,11 @@ SAMPLE 3: Top level dialog with custom corner radius, using OpenDialog, passing content via the Parameter. You can pass a view model, provided a corresponding DataTemplate can be found in the scope of the root DialogHost. - + + + +