From c65b5de959382713d8869ad2670ac22a3d2b5140 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 29 Jul 2019 22:53:42 +0200 Subject: [PATCH] Move setting popup TemplatedParent... ...out of `PopupRoot` and into `Popup`. Fixes styling problems in `Popups` hosted in control templates. --- .../Primitives/IPopupHost.cs | 4 + src/Avalonia.Controls/Primitives/Popup.cs | 93 +++++++++++++++---- src/Avalonia.Controls/Primitives/PopupRoot.cs | 49 ---------- .../Primitives/PopupTests.cs | 20 +--- 4 files changed, 82 insertions(+), 84 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index df68eab6a4..912879af03 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.VisualTree; @@ -7,8 +8,11 @@ namespace Avalonia.Controls.Primitives public interface IPopupHost : IDisposable { void SetContent(IControl control); + IContentPresenter Presenter { get; } IVisual VisualRoot { get; } + event EventHandler TemplateApplied; + void ConfigurePosition(IVisual target, PlacementMode placement, Point offset, PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 18d90c0315..2537f239fe 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; +using Avalonia.Controls.Presenters; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Raw; @@ -79,9 +80,10 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.Register(nameof(Topmost)); private bool _isOpen; - private IPopupHost _popupRoot; + private IPopupHost _popupHost; private TopLevel _topLevel; private IDisposable _nonClientListener; + private IDisposable _presenterSubscription; bool _ignoreIsOpenChanged = false; private List _bindings = new List(); @@ -110,7 +112,7 @@ namespace Avalonia.Controls.Primitives /// public event EventHandler Opened; - public IPopupHost Host => _popupRoot; + public IPopupHost Host => _popupHost; /// /// Gets or sets the control to display in the popup. @@ -209,7 +211,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets the root of the popup window. /// - IVisual IVisualTreeHost.Root => _popupRoot?.VisualRoot; + IVisual IVisualTreeHost.Root => _popupHost?.VisualRoot; /// /// Opens the popup. @@ -232,17 +234,16 @@ namespace Avalonia.Controls.Primitives "Attempted to open a popup not attached to a TopLevel"); } - _popupRoot = PopupHost.CreatePopupHost(placementTarget, DependencyResolver); + _popupHost = PopupHost.CreatePopupHost(placementTarget, DependencyResolver); - _bindings.Add(_popupRoot.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty, + _bindings.Add(_popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty, HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty)); - _popupRoot.SetContent(Child); - - ((ISetLogicalParent)_popupRoot).SetParent(this); - - _popupRoot.ConfigurePosition(placementTarget, + _popupHost.SetContent(Child); + ((ISetLogicalParent)_popupHost).SetParent(this); + _popupHost.ConfigurePosition(placementTarget, PlacementMode, new Point(HorizontalOffset, VerticalOffset)); + _popupHost.TemplateApplied += RootTemplateApplied; var window = _topLevel as Window; if (window != null) @@ -261,7 +262,7 @@ namespace Avalonia.Controls.Primitives _nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick); - _popupRoot.Show(); + _popupHost.Show(); using (BeginIgnoringIsOpen()) { @@ -276,6 +277,13 @@ namespace Avalonia.Controls.Primitives /// public void Close() { + if (_popupHost != null) + { + _popupHost.TemplateApplied -= RootTemplateApplied; + } + + _presenterSubscription?.Dispose(); + CloseCurrent(); using (BeginIgnoringIsOpen()) { @@ -284,6 +292,7 @@ namespace Avalonia.Controls.Primitives Closed?.Invoke(this, EventArgs.Empty); } + void CloseCurrent() { if (_topLevel != null) @@ -305,16 +314,16 @@ namespace Avalonia.Controls.Primitives _topLevel = null; } - if (_popupRoot != null) + if (_popupHost != null) { foreach(var b in _bindings) b.Dispose(); _bindings.Clear(); - _popupRoot.SetContent(null); - _popupRoot.Hide(); - ((ISetLogicalParent)_popupRoot).SetParent(null); - _popupRoot.Dispose(); - _popupRoot = null; + _popupHost.SetContent(null); + _popupHost.Hide(); + ((ISetLogicalParent)_popupHost).SetParent(null); + _popupHost.Dispose(); + _popupHost = null; } } @@ -395,17 +404,61 @@ namespace Avalonia.Controls.Primitives } } + private void RootTemplateApplied(object sender, TemplateAppliedEventArgs e) + { + _popupHost.TemplateApplied -= RootTemplateApplied; + + if (_presenterSubscription != null) + { + _presenterSubscription.Dispose(); + _presenterSubscription = null; + } + + // If the Popup appears in a control template, then the child controls + // that appear in the popup host need to have their TemplatedParent + // properties set. + if (TemplatedParent != null) + { + _popupHost.Presenter?.ApplyTemplate(); + _popupHost.Presenter?.GetObservable(ContentPresenter.ChildProperty) + .Subscribe(SetTemplatedParentAndApplyChildTemplates); + } + } + + private void SetTemplatedParentAndApplyChildTemplates(IControl control) + { + if (control != null) + { + var templatedParent = TemplatedParent; + + if (control.TemplatedParent == null) + { + control.SetValue(TemplatedParentProperty, templatedParent); + } + + control.ApplyTemplate(); + + if (!(control is IPresenter) && control.TemplatedParent == templatedParent) + { + foreach (IControl child in control.GetVisualChildren()) + { + SetTemplatedParentAndApplyChildTemplates(child); + } + } + } + } + private bool IsChildOrThis(IVisual child) { - return ((IVisual)_popupRoot)?.FindCommonVisualAncestor(child) == _popupRoot; + return ((IVisual)_popupHost)?.FindCommonVisualAncestor(child) == _popupHost; } public bool IsInsidePopup(IVisual visual) { - return ((IVisual)_popupRoot)?.FindCommonVisualAncestor(visual) == _popupRoot; + return ((IVisual)_popupHost)?.FindCommonVisualAncestor(visual) == _popupHost; } - public bool IsPointerOverPopup => ((IInputElement)_popupRoot).IsPointerOver; + public bool IsPointerOverPopup => ((IInputElement)_popupHost).IsPointerOver; private void WindowDeactivated(object sender, EventArgs e) { diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 0955800263..8c4fbc7370 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -3,14 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reactive.Disposables; -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; @@ -26,7 +20,6 @@ namespace Avalonia.Controls.Primitives public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost { private readonly TopLevel _parent; - private IDisposable _presenterSubscription; private PopupPositionerParameters _positionerParameters; /// @@ -84,48 +77,6 @@ namespace Avalonia.Controls.Primitives /// public void Dispose() => PlatformImpl?.Dispose(); - /// - protected override void OnTemplateApplied(TemplateAppliedEventArgs e) - { - base.OnTemplateApplied(e); - - if (Parent?.TemplatedParent != null) - { - if (_presenterSubscription != null) - { - _presenterSubscription.Dispose(); - _presenterSubscription = null; - } - - Presenter?.ApplyTemplate(); - Presenter?.GetObservable(ContentPresenter.ChildProperty) - .Subscribe(SetTemplatedParentAndApplyChildTemplates); - } - } - - private void SetTemplatedParentAndApplyChildTemplates(IControl control) - { - if (control != null) - { - var templatedParent = Parent.TemplatedParent; - - if (control.TemplatedParent == null) - { - control.SetValue(TemplatedParentProperty, templatedParent); - } - - control.ApplyTemplate(); - - if (!(control is IPresenter) && control.TemplatedParent == templatedParent) - { - foreach (IControl child in control.GetVisualChildren()) - { - SetTemplatedParentAndApplyChildTemplates(child); - } - } - } - } - void UpdatePosition() { PlatformImpl?.PopupPositioner.Update(_positionerParameters); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index c61e3d1f68..f5d68be02a 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -227,10 +227,6 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent() { - if(UsePopupHost) - // For some reason with overlay popups templates don't get applied in test mode but - // everything works perfectly fine at runtime. I leave this one to you @grokys - return; using (CreateServices()) { PopupContentControl target; @@ -242,9 +238,13 @@ namespace Avalonia.Controls.UnitTests.Primitives root.Show(); target.ApplyTemplate(); + var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); popup.Open(); - var popupRoot = (Visual)popup.Host; + + var popupRoot = (Control)popup.Host; + popupRoot.Measure(Size.Infinity); + popupRoot.Arrange(new Rect(popupRoot.DesiredSize)); var children = popupRoot.GetVisualDescendants().ToList(); var types = children.Select(x => x.GetType().Name).ToList(); @@ -326,7 +326,6 @@ namespace Avalonia.Controls.UnitTests.Primitives } } - private IDisposable CreateServices() { return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: @@ -339,15 +338,6 @@ namespace Avalonia.Controls.UnitTests.Primitives }))); } - private static IControl PopupRootTemplate(PopupRoot control, INameScope scope) - { - return new ContentPresenter - { - Name = "PART_ContentPresenter", - [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty], - }.RegisterInNameScope(scope); - } - private static IControl PopupContentControlTemplate(PopupContentControl control, INameScope scope) { return new Popup