diff --git a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
new file mode 100644
index 0000000000..11d3b1792a
--- /dev/null
+++ b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs
@@ -0,0 +1,23 @@
+using System;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Diagnostics
+{
+ ///
+ /// Diagnostics interface to retrieve an associated .
+ ///
+ public interface IPopupHostProvider
+ {
+ ///
+ /// The popup host.
+ ///
+ IPopupHost? PopupHost { get; }
+
+ ///
+ /// Raised when the popup host changes.
+ ///
+ event Action? PopupHostChanged;
+ }
+}
diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs
new file mode 100644
index 0000000000..58174b1039
--- /dev/null
+++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs
@@ -0,0 +1,12 @@
+#nullable enable
+
+namespace Avalonia.Controls.Diagnostics
+{
+ ///
+ /// Helper class to provide some diagnostics insides into .
+ ///
+ public static class ToolTipDiagnostics
+ {
+ public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty;
+ }
+}
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index e4b68c62fd..6b72b8c887 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
@@ -1,16 +1,16 @@
using System;
using System.ComponentModel;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
-using Avalonia.Rendering;
#nullable enable
namespace Avalonia.Controls.Primitives
{
- public abstract class FlyoutBase : AvaloniaObject
+ public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider
{
static FlyoutBase()
{
@@ -55,6 +55,7 @@ namespace Avalonia.Controls.Primitives
private Rect? _enlargedPopupRect;
private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable;
+ private Action? _popupHostChangedHandler;
protected Popup? Popup { get; private set; }
@@ -94,6 +95,14 @@ namespace Avalonia.Controls.Primitives
private set => SetAndRaise(TargetProperty, ref _target, value);
}
+ IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
+
public event EventHandler? Closed;
public event EventHandler? Closing;
public event EventHandler? Opened;
@@ -322,9 +331,11 @@ namespace Avalonia.Controls.Primitives
private void InitPopup()
{
- Popup = new Popup();
- Popup.WindowManagerAddShadowHint = false;
- Popup.IsLightDismissEnabled = true;
+ Popup = new Popup
+ {
+ WindowManagerAddShadowHint = false,
+ IsLightDismissEnabled = true
+ };
Popup.Opened += OnPopupOpened;
Popup.Closed += OnPopupClosed;
@@ -333,11 +344,15 @@ namespace Avalonia.Controls.Primitives
private void OnPopupOpened(object sender, EventArgs e)
{
IsOpen = true;
+
+ _popupHostChangedHandler?.Invoke(Popup!.Host);
}
private void OnPopupClosed(object sender, EventArgs e)
{
HideCore();
+
+ _popupHostChangedHandler?.Invoke(null);
}
private void PositionPopup(bool showAtPointer)
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index ab507d07a2..ab310d60ef 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -1,9 +1,8 @@
#nullable enable
using System;
-using System.Reactive.Linq;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -17,7 +16,7 @@ namespace Avalonia.Controls
/// assigning the content that you want displayed.
///
[PseudoClasses(":open")]
- public class ToolTip : ContentControl
+ public class ToolTip : ContentControl, IPopupHostProvider
{
///
/// Defines the ToolTip.Tip attached property.
@@ -61,7 +60,8 @@ namespace Avalonia.Controls
internal static readonly AttachedProperty ToolTipProperty =
AvaloniaProperty.RegisterAttached("ToolTip");
- private IPopupHost? _popup;
+ private IPopupHost? _popupHost;
+ private Action? _popupHostChangedHandler;
///
/// Initializes static members of the class.
@@ -251,35 +251,45 @@ namespace Avalonia.Controls
tooltip.RecalculatePosition(control);
}
+
+ IPopupHost? IPopupHostProvider.PopupHost => _popupHost;
+
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
+ remove => _popupHostChangedHandler -= value;
+ }
internal void RecalculatePosition(Control control)
{
- _popup?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
+ _popupHost?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
}
private void Open(Control control)
{
Close();
- _popup = OverlayPopupHost.CreatePopupHost(control, null);
- _popup.SetChild(this);
- ((ISetLogicalParent)_popup).SetParent(control);
+ _popupHost = OverlayPopupHost.CreatePopupHost(control, null);
+ _popupHost.SetChild(this);
+ ((ISetLogicalParent)_popupHost).SetParent(control);
- _popup.ConfigurePosition(control, GetPlacement(control),
+ _popupHost.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
- WindowManagerAddShadowHintChanged(_popup, false);
+ WindowManagerAddShadowHintChanged(_popupHost, false);
- _popup.Show();
+ _popupHost.Show();
+ _popupHostChangedHandler?.Invoke(_popupHost);
}
private void Close()
{
- if (_popup != null)
+ if (_popupHost != null)
{
- _popup.SetChild(null);
- _popup.Dispose();
- _popup = null;
+ _popupHost.SetChild(null);
+ _popupHost.Dispose();
+ _popupHost = null;
+ _popupHostChangedHandler?.Invoke(null);
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
index f4c04dbca6..9667751e54 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
@@ -4,6 +4,7 @@ using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Controls;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Media;
@@ -19,12 +20,11 @@ namespace Avalonia.Diagnostics.ViewModels
protected TreeNode(IVisual visual, TreeNode? parent)
{
+ _classes = string.Empty;
Parent = parent;
Type = visual.GetType().Name;
Visual = visual;
- _classes = string.Empty;
-
- FontWeight = Visual is TopLevel || Visual is Popup ? FontWeight.Bold : FontWeight.Normal;
+ FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal;
if (visual is IControl control)
{
@@ -56,6 +56,11 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
+ private bool IsRoot => Visual is TopLevel ||
+ Visual is Popup ||
+ Visual is ContextMenu ||
+ Visual is IPopupHost;
+
public FontWeight FontWeight { get; }
public abstract TreeNodeCollection Children
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
index b9981babf7..6d803a1a7b 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
@@ -1,5 +1,10 @@
using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Security.Cryptography.X509Certificates;
using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -13,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
Children = new VisualTreeNodeCollection(this, visual);
- if ((Visual is IStyleable styleable))
+ if (Visual is IStyleable styleable)
{
IsInTemplate = styleable.TemplatedParent != null;
}
@@ -25,13 +30,15 @@ namespace Avalonia.Diagnostics.ViewModels
public static VisualTreeNode[] Create(object control)
{
- return control is IVisual visual ? new[] { new VisualTreeNode(visual, null) } : Array.Empty();
+ return control is IVisual visual ?
+ new[] { new VisualTreeNode(visual, null) } :
+ Array.Empty();
}
internal class VisualTreeNodeCollection : TreeNodeCollection
{
private readonly IVisual _control;
- private IDisposable? _subscription;
+ private readonly CompositeDisposable _subscriptions = new CompositeDisposable(2);
public VisualTreeNodeCollection(TreeNode owner, IVisual control)
: base(owner)
@@ -41,33 +48,79 @@ namespace Avalonia.Diagnostics.ViewModels
public override void Dispose()
{
- _subscription?.Dispose();
+ _subscriptions.Dispose();
}
- protected override void Initialize(AvaloniaList nodes)
+ private static IObservable? GetHostedPopupRootObservable(IVisual visual)
{
- if (_control is Popup p)
+ static IObservable GetPopupHostObservable(IPopupHostProvider popupHostProvider)
{
- _subscription = p.GetObservable(Popup.ChildProperty).Subscribe(child =>
- {
- if (child != null)
- {
- nodes.Add(new VisualTreeNode(child, Owner));
- }
- else
- {
- nodes.Clear();
- }
-
- });
+ return Observable.FromEvent(
+ x => popupHostProvider.PopupHostChanged += x,
+ x => popupHostProvider.PopupHostChanged -= x)
+ .StartWith(popupHostProvider.PopupHost)
+ .Select(x => x is IControl c ? c : null);
}
- else
+
+ return visual switch
{
- _subscription = _control.VisualChildren.ForEachItem(
+ Popup p => p.GetObservable(Popup.ChildProperty),
+ Control c => Observable.CombineLatest(
+ c.GetObservable(Control.ContextFlyoutProperty),
+ c.GetObservable(Control.ContextMenuProperty),
+ c.GetObservable(FlyoutBase.AttachedFlyoutProperty),
+ c.GetObservable(ToolTipDiagnostics.ToolTipProperty),
+ (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip) =>
+ {
+ if (ContextMenu != null)
+ {
+ //Note: ContextMenus are special since all the items are added as visual children.
+ //So we don't need to go via Popup
+ return Observable.Return(ContextMenu);
+ }
+
+ if ((ContextFlyout ?? (IPopupHostProvider?) AttachedFlyout ?? ToolTip) is { } popupHostProvider)
+ {
+ return GetPopupHostObservable(popupHostProvider);
+ }
+
+ return Observable.Return(null);
+ })
+ .Switch(),
+ _ => null
+ };
+ }
+
+ protected override void Initialize(AvaloniaList nodes)
+ {
+ _subscriptions.Clear();
+
+ if (GetHostedPopupRootObservable(_control) is { } popupRootObservable)
+ {
+ VisualTreeNode? childNode = null;
+
+ _subscriptions.Add(
+ popupRootObservable
+ .Subscribe(root =>
+ {
+ if (root != null)
+ {
+ childNode = new VisualTreeNode(root, Owner);
+
+ nodes.Add(childNode);
+ }
+ else if (childNode != null)
+ {
+ nodes.Remove(childNode);
+ }
+ }));
+ }
+
+ _subscriptions.Add(
+ _control.VisualChildren.ForEachItem(
(i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
(i, item) => nodes.RemoveAt(i),
- () => nodes.Clear());
- }
+ () => nodes.Clear()));
}
}
}