Browse Source

Completely re-create PopupRoot on reopening Popup

pull/2778/head
Nikita Tsukanov 7 years ago
parent
commit
79cf3e5cea
  1. 196
      src/Avalonia.Controls/Primitives/Popup.cs
  2. 6
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  3. 1
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

196
src/Avalonia.Controls/Primitives/Popup.cs

@ -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);
}
}
}
}

6
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -2,9 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Text;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform;
@ -125,7 +129,7 @@ namespace Avalonia.Controls.Primitives
PlatformImpl?.PopupPositioner.Update(_positionerParameters);
}
public void ConfigurePosition(Control target, PlacementMode placement, Point offset,
public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
PopupPositioningEdge anchor = PopupPositioningEdge.None,
PopupPositioningEdge gravity = PopupPositioningEdge.None)
{

1
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -284,6 +284,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
new[]
{
"ContentPresenter",
"PopupContentHost",
"ContentPresenter",
"Border",
},

Loading…
Cancel
Save