|
|
@@ -2,7 +2,11 @@
|
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
|
|
using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Diagnostics;
|
|
|
using System.Linq;
|
|
|
+using System.Reactive.Disposables;
|
|
|
+using Avalonia.Data;
|
|
|
using Avalonia.Input;
|
|
|
using Avalonia.Input.Raw;
|
|
|
using Avalonia.Interactivity;
|
|
|
@@ -79,6 +83,8 @@ namespace Avalonia.Controls.Primitives
|
|
|
private TopLevel _topLevel;
|
|
|
private IDisposable _nonClientListener;
|
|
|
bool _ignoreIsOpenChanged = false;
|
|
|
+ private List<IDisposable> _bindings = new List<IDisposable>();
|
|
|
+ private PopupContentHost _decorator = new PopupContentHost();
|
|
|
|
|
|
/// <summary>
|
|
|
/// Initializes static members of the <see cref="Popup"/> class.
|
|
|
@@ -91,6 +97,11 @@ namespace Avalonia.Controls.Primitives
|
|
|
TopmostProperty.Changed.AddClassHandler<Popup>((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
|
|
|
}
|
|
|
|
|
|
+ public Popup()
|
|
|
+ {
|
|
|
+ _decorator[~PopupContentHost.ChildProperty] = this[~ChildProperty];
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Raised when the popup closes.
|
|
|
/// </summary>
|
|
|
@@ -215,38 +226,48 @@ namespace Avalonia.Controls.Primitives
|
|
|
/// </summary>
|
|
|
public void Open()
|
|
|
{
|
|
|
- if (PlacementTarget == null && PlacementMode != PlacementMode.Pointer)
|
|
|
- throw new InvalidOperationException("It's not valid to show a popup without a PlacementTarget with PlacementMode != Pointer");
|
|
|
+ // Popup is currently open
|
|
|
+ if (_topLevel != null)
|
|
|
+ return;
|
|
|
+ CloseCurrent();
|
|
|
+ var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault();
|
|
|
+ if (placementTarget == null)
|
|
|
+ throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null");
|
|
|
|
|
|
- if (_topLevel == null && PlacementTarget != null)
|
|
|
- _topLevel = PlacementTarget.GetSelfAndLogicalAncestors().FirstOrDefault(x => x is TopLevel) as TopLevel;
|
|
|
+ _topLevel = placementTarget.GetVisualRoot() as TopLevel;
|
|
|
|
|
|
if (_topLevel == null)
|
|
|
{
|
|
|
- if (PlacementTarget == null)
|
|
|
- throw new InvalidOperationException(
|
|
|
- "Attempted to open a popup not attached to a TopLevel and PlacementTarget is null");
|
|
|
throw new InvalidOperationException(
|
|
|
- "Attempted to open a popup not attached to a TopLevel and PlacementTarget is also not attached to a TopLevel");
|
|
|
+ "Attempted to open a popup not attached to a TopLevel");
|
|
|
}
|
|
|
|
|
|
- if (_popupRoot == null)
|
|
|
+
|
|
|
+ _popupRoot = new PopupRoot(_topLevel, DependencyResolver)
|
|
|
{
|
|
|
- _popupRoot = new PopupRoot(_topLevel, DependencyResolver)
|
|
|
- {
|
|
|
- [~ContentControl.ContentProperty] = this[~ChildProperty],
|
|
|
- [~WidthProperty] = this[~WidthProperty],
|
|
|
- [~HeightProperty] = this[~HeightProperty],
|
|
|
- [~MinWidthProperty] = this[~MinWidthProperty],
|
|
|
- [~MaxWidthProperty] = this[~MaxWidthProperty],
|
|
|
- [~MinHeightProperty] = this[~MinHeightProperty],
|
|
|
- [~MaxHeightProperty] = this[~MaxHeightProperty],
|
|
|
- };
|
|
|
-
|
|
|
- ((ISetLogicalParent)_popupRoot).SetParent(this);
|
|
|
- }
|
|
|
+ [~WidthProperty] = this[~WidthProperty],
|
|
|
+ [~HeightProperty] = this[~HeightProperty],
|
|
|
+ [~MinWidthProperty] = this[~MinWidthProperty],
|
|
|
+ [~MaxWidthProperty] = this[~MaxWidthProperty],
|
|
|
+ [~MinHeightProperty] = this[~MinHeightProperty],
|
|
|
+ [~MaxHeightProperty] = this[~MaxHeightProperty],
|
|
|
+ };
|
|
|
+
|
|
|
+ void Bind(AvaloniaProperty prop) => _bindings.Add(_popupRoot.Bind(prop, this[~prop]));
|
|
|
+
|
|
|
+ Bind(WidthProperty);
|
|
|
+ Bind(MinWidthProperty);
|
|
|
+ Bind(MaxWidthProperty);
|
|
|
+ Bind(HeightProperty);
|
|
|
+ Bind(MinHeightProperty);
|
|
|
+ Bind(MaxHeightProperty);
|
|
|
+
|
|
|
+ _popupRoot.Content = _decorator;
|
|
|
+
|
|
|
|
|
|
- _popupRoot.ConfigurePosition(PlacementTarget ?? this.GetVisualParent<Control>(),
|
|
|
+ ((ISetLogicalParent)_popupRoot).SetParent(this);
|
|
|
+
|
|
|
+ _popupRoot.ConfigurePosition(placementTarget,
|
|
|
PlacementMode, new Point(HorizontalOffset, VerticalOffset));
|
|
|
|
|
|
var window = _topLevel as Window;
|
|
|
@@ -282,35 +303,47 @@ namespace Avalonia.Controls.Primitives
|
|
|
/// </summary>
|
|
|
public void Close()
|
|
|
{
|
|
|
- if (_popupRoot != null)
|
|
|
+ CloseCurrent();
|
|
|
+ using (BeginIgnoringIsOpen())
|
|
|
+ {
|
|
|
+ IsOpen = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ Closed?.Invoke(this, EventArgs.Empty);
|
|
|
+ }
|
|
|
+ void CloseCurrent()
|
|
|
+ {
|
|
|
+ if (_topLevel != null)
|
|
|
{
|
|
|
- if (_topLevel != null)
|
|
|
+ _topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
|
|
|
+ var window = _topLevel as Window;
|
|
|
+ if (window != null)
|
|
|
+ window.Deactivated -= WindowDeactivated;
|
|
|
+ else
|
|
|
{
|
|
|
- _topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
|
|
|
- var window = _topLevel as Window;
|
|
|
- if (window != null)
|
|
|
- window.Deactivated -= WindowDeactivated;
|
|
|
- else
|
|
|
+ var parentPopuproot = _topLevel as PopupRoot;
|
|
|
+ if (parentPopuproot?.Parent is Popup popup)
|
|
|
{
|
|
|
- var parentPopuproot = _topLevel as PopupRoot;
|
|
|
- if (parentPopuproot?.Parent is Popup popup)
|
|
|
- {
|
|
|
- popup.Closed -= ParentClosed;
|
|
|
- }
|
|
|
+ popup.Closed -= ParentClosed;
|
|
|
}
|
|
|
- _nonClientListener?.Dispose();
|
|
|
- _nonClientListener = null;
|
|
|
}
|
|
|
-
|
|
|
- _popupRoot.Hide();
|
|
|
+ _nonClientListener?.Dispose();
|
|
|
+ _nonClientListener = null;
|
|
|
+
|
|
|
+ _topLevel = null;
|
|
|
}
|
|
|
-
|
|
|
- using (BeginIgnoringIsOpen())
|
|
|
+ if (_popupRoot != null)
|
|
|
{
|
|
|
- IsOpen = false;
|
|
|
+ foreach(var b in _bindings)
|
|
|
+ b.Dispose();
|
|
|
+ _bindings.Clear();
|
|
|
+ _popupRoot.Content = null;
|
|
|
+ _popupRoot.Hide();
|
|
|
+ ((ISetLogicalParent)_popupRoot).SetParent(null);
|
|
|
+ _popupRoot.Dispose();
|
|
|
+ _popupRoot = null;
|
|
|
}
|
|
|
|
|
|
- Closed?.Invoke(this, EventArgs.Empty);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -323,27 +356,14 @@ namespace Avalonia.Controls.Primitives
|
|
|
return new Size();
|
|
|
}
|
|
|
|
|
|
- /// <inheritdoc/>
|
|
|
- protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
|
|
- {
|
|
|
- base.OnAttachedToLogicalTree(e);
|
|
|
- _topLevel = e.Root as TopLevel;
|
|
|
- }
|
|
|
-
|
|
|
/// <inheritdoc/>
|
|
|
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
|
|
{
|
|
|
base.OnDetachedFromLogicalTree(e);
|
|
|
- _topLevel = null;
|
|
|
-
|
|
|
- if (_popupRoot != null)
|
|
|
- {
|
|
|
- ((ISetLogicalParent)_popupRoot).SetParent(null);
|
|
|
- _popupRoot.Dispose();
|
|
|
- _popupRoot = null;
|
|
|
- }
|
|
|
+ Close();
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Called when the <see cref="IsOpen"/> property changes.
|
|
|
/// </summary>
|
|
|
@@ -449,5 +469,65 @@ namespace Avalonia.Controls.Primitives
|
|
|
_owner._ignoreIsOpenChanged = false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // For some reason when PopupRoot.Content is bound directly to Child weird stuff happens
|
|
|
+ class PopupContentHost : Control
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Defines the <see cref="Child"/> property.
|
|
|
+ /// </summary>
|
|
|
+ public static readonly StyledProperty<IControl> ChildProperty =
|
|
|
+ AvaloniaProperty.Register<Decorator, IControl>(nameof(Child));
|
|
|
+
|
|
|
+ static PopupContentHost()
|
|
|
+ {
|
|
|
+ ChildProperty.Changed.AddClassHandler<PopupContentHost>(x => x.ChildChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ public IControl Child
|
|
|
+ {
|
|
|
+ get { return GetValue(ChildProperty); }
|
|
|
+ set { SetValue(ChildProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ protected override Size MeasureOverride(Size availableSize)
|
|
|
+ {
|
|
|
+ if (Child == null)
|
|
|
+ return Size.Empty;
|
|
|
+ Child.Measure(availableSize);
|
|
|
+ return Child.DesiredSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ protected override Size ArrangeOverride(Size finalSize)
|
|
|
+ {
|
|
|
+ Child?.Arrange(new Rect(finalSize));
|
|
|
+ return finalSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
+ {
|
|
|
+ if (Child != null)
|
|
|
+ VisualChildren.Add(Child);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
+ {
|
|
|
+ if (Child != null)
|
|
|
+ VisualChildren.Remove(Child);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ var oldChild = (Control)e.OldValue;
|
|
|
+ var newChild = (Control)e.NewValue;
|
|
|
+
|
|
|
+ if (oldChild != null) VisualChildren.Remove(oldChild);
|
|
|
+
|
|
|
+ if (newChild != null && VisualRoot != null)
|
|
|
+ VisualChildren.Add(newChild);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|