Browse Source

Move setting popup TemplatedParent...

...out of `PopupRoot` and into `Popup`. Fixes styling problems in `Popups` hosted in control templates.
Steven Kirk 6 years ago
parent
commit
c65b5de959

+ 4 - 0
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<TemplateAppliedEventArgs> TemplateApplied;
+
         void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
             PopupPositioningEdge anchor = PopupPositioningEdge.None,
             PopupPositioningEdge gravity = PopupPositioningEdge.None);

+ 73 - 20
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<Popup, bool>(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<IDisposable> _bindings = new List<IDisposable>();
 
@@ -110,7 +112,7 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         public event EventHandler Opened;
 
-        public IPopupHost Host => _popupRoot;
+        public IPopupHost Host => _popupHost;
 
         /// <summary>
         /// Gets or sets the control to display in the popup.
@@ -209,7 +211,7 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the root of the popup window.
         /// </summary>
-        IVisual IVisualTreeHost.Root => _popupRoot?.VisualRoot;
+        IVisual IVisualTreeHost.Root => _popupHost?.VisualRoot;
 
         /// <summary>
         /// 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
         /// </summary>
         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)
         {

+ 0 - 49
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;
 
         /// <summary>
@@ -84,48 +77,6 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
 
-        /// <inheritdoc/>
-        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);

+ 5 - 15
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