Browse Source

Move setting popup TemplatedParent...

...out of `PopupRoot` and into `Popup`. Fixes styling problems in `Popups` hosted in control templates.
pull/2778/head
Steven Kirk 7 years ago
parent
commit
c65b5de959
  1. 4
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  2. 93
      src/Avalonia.Controls/Primitives/Popup.cs
  3. 49
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  4. 20
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

4
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);

93
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)
{

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

20
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

Loading…
Cancel
Save