Browse Source

Set TemplatedParent in popups.

Popups that appear in control templates should set the TemplatedParent
property of their child controls.
pull/72/merge
Steven Kirk 11 years ago
parent
commit
7b192505d7
  1. 10
      Perspex.Controls/Presenters/ContentPresenter.cs
  2. 8
      Perspex.Controls/Primitives/Popup.cs
  3. 48
      Perspex.Controls/Primitives/PopupRoot.cs
  4. 94
      Perspex.Controls/TopLevel.cs
  5. 178
      Tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs
  6. 2
      Tests/Perspex.Controls.UnitTests/TestRoot.cs

10
Perspex.Controls/Presenters/ContentPresenter.cs

@ -13,6 +13,12 @@ namespace Perspex.Controls.Presenters
/// </summary>
public class ContentPresenter : Control, IContentPresenter
{
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
public static readonly PerspexProperty<IControl> ChildProperty =
PerspexProperty.Register<ContentPresenter, IControl>("Child");
/// <summary>
/// Defines the <see cref="Content"/> property.
/// </summary>
@ -34,8 +40,8 @@ namespace Perspex.Controls.Presenters
/// </summary>
public IControl Child
{
get;
private set;
get { return this.GetValue(ChildProperty); }
private set { this.SetValue(ChildProperty, value); }
}
/// <summary>

8
Perspex.Controls/Primitives/Popup.cs

@ -177,9 +177,13 @@ namespace Perspex.Controls.Primitives
}
this.popupRoot.SetPosition(this.GetPosition());
this.topLevel.Deactivated += this.MaybeClose;
this.popupRoot.AddHandler(PopupRoot.PointerPressedEvent, this.MaybeClose, RoutingStrategies.Bubble, true);
this.topLevel.AddHandler(TopLevel.PointerPressedEvent, this.MaybeClose, RoutingStrategies.Tunnel);
if (this.topLevel != null)
{
this.topLevel.Deactivated += this.MaybeClose;
this.topLevel.AddHandler(TopLevel.PointerPressedEvent, this.MaybeClose, RoutingStrategies.Tunnel);
}
this.PopupRootCreated?.Invoke(this, EventArgs.Empty);

48
Perspex.Controls/Primitives/PopupRoot.cs

@ -8,6 +8,7 @@ namespace Perspex.Controls.Primitives
{
using System;
using Collections;
using Perspex.Controls.Presenters;
using Perspex.Interactivity;
using Perspex.Media;
using Perspex.Platform;
@ -19,6 +20,8 @@ namespace Perspex.Controls.Primitives
/// </summary>
public class PopupRoot : TopLevel, IInteractive, IHostedVisualTreeRoot
{
private IDisposable presenterSubscription;
/// <summary>
/// Initializes static members of the <see cref="PopupRoot"/> class.
/// </summary>
@ -98,8 +101,51 @@ namespace Perspex.Controls.Primitives
public void Show()
{
this.PlatformImpl.Show();
this.LayoutManager.ExecuteLayoutPass();
this.LayoutManager?.ExecuteLayoutPass();
this.IsVisible = true;
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
{
base.OnTemplateApplied();
if (this.Parent.TemplatedParent != null)
{
if (this.presenterSubscription != null)
{
this.presenterSubscription.Dispose();
this.presenterSubscription = null;
}
var presenter = this.Presenter;
if (presenter != null)
{
presenter.GetObservable(ContentPresenter.ChildProperty)
.Subscribe(this.SetTemplatedParentAndApplyChildTemplates);
}
}
}
private void SetTemplatedParentAndApplyChildTemplates(IControl control)
{
var templatedParent = this.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())
{
this.SetTemplatedParentAndApplyChildTemplates(child);
}
}
}
}
}

94
Perspex.Controls/TopLevel.cs

@ -103,56 +103,22 @@ namespace Perspex.Controls
/// </param>
public TopLevel(ITopLevelImpl impl, IDependencyResolver dependencyResolver)
{
dependencyResolver = dependencyResolver ?? Locator.Current;
IPlatformRenderInterface renderInterface = dependencyResolver.GetService<IPlatformRenderInterface>();
this.PlatformImpl = impl;
this.accessKeyHandler = dependencyResolver.GetService<IAccessKeyHandler>();
this.inputManager = dependencyResolver.GetService<IInputManager>();
this.keyboardNavigationHandler = dependencyResolver.GetService<IKeyboardNavigationHandler>();
this.LayoutManager = dependencyResolver.GetService<ILayoutManager>();
this.renderManager = dependencyResolver.GetService<IRenderManager>();
if (renderInterface == null)
{
throw new InvalidOperationException(
"Could not create an interface to the rendering subsystem: maybe no rendering subsystem was initialized?");
}
if (this.PlatformImpl == null)
if (impl == null)
{
throw new InvalidOperationException(
"Could not create window implementation: maybe no windowing subsystem was initialized?");
}
if (this.inputManager == null)
{
throw new InvalidOperationException(
"Could not create input manager: maybe Application.RegisterServices() wasn't called?");
}
if (this.LayoutManager == null)
{
throw new InvalidOperationException(
"Could not create layout manager: maybe Application.RegisterServices() wasn't called?");
}
if (this.renderManager == null)
{
throw new InvalidOperationException(
"Could not create render manager: maybe Application.RegisterServices() wasn't called?");
}
if (this.accessKeyHandler != null)
{
this.accessKeyHandler.SetOwner(this);
}
this.PlatformImpl = impl;
if (this.keyboardNavigationHandler != null)
{
this.keyboardNavigationHandler.SetOwner(this);
}
dependencyResolver = dependencyResolver ?? Locator.Current;
var renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
var styler = TryGetService<IStyler>(dependencyResolver);
this.accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
this.inputManager = TryGetService<IInputManager>(dependencyResolver);
this.keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver);
this.LayoutManager = TryGetService<ILayoutManager>(dependencyResolver);
this.renderManager = TryGetService<IRenderManager>(dependencyResolver);
this.PlatformImpl.SetOwner(this);
this.PlatformImpl.Activated = this.HandleActivated;
@ -165,19 +131,43 @@ namespace Perspex.Controls
Size clientSize = this.ClientSize = this.PlatformImpl.ClientSize;
this.dispatcher = Dispatcher.UIThread;
this.renderer = renderInterface.CreateRenderer(this.PlatformImpl.Handle, clientSize.Width, clientSize.Height);
this.LayoutManager.Root = this;
this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded());
this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted());
this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded());
if (renderInterface != null)
{
this.renderer = renderInterface.CreateRenderer(this.PlatformImpl.Handle, clientSize.Width, clientSize.Height);
}
IStyler styler = dependencyResolver.GetService<IStyler>();
styler.ApplyStyles(this);
if (this.LayoutManager != null)
{
this.LayoutManager.Root = this;
this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded());
this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted());
}
if (this.renderManager != null)
{
this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded());
}
styler?.ApplyStyles(this);
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);
}
private static T TryGetService<T>(IDependencyResolver resolver) where T : class
{
var result = resolver.GetService<T>();
if (result == null)
{
System.Diagnostics.Debug.WriteLineIf(
result == null,
$"Could not create {typeof(T).Name} : maybe Application.RegisterServices() wasn't called?");
}
return result;
}
/// <summary>
/// Fired when the window is activated.
/// </summary>
@ -383,7 +373,7 @@ namespace Perspex.Controls
/// </summary>
private void HandleLayoutCompleted()
{
this.renderManager.InvalidateRender(this);
this.renderManager?.InvalidateRender(this);
}
/// <summary>

178
Tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs

@ -6,10 +6,19 @@
namespace Perspex.Controls.UnitTests.Primitives
{
using System;
using System.Collections.Specialized;
using System.Linq;
using Layout;
using Moq;
using Perspex.Controls.Presenters;
using Perspex.Controls.Primitives;
using Perspex.LogicalTree;
using Perspex.Platform;
using Perspex.Styling;
using Perspex.VisualTree;
using Splat;
using Templates;
using Xunit;
public class PopupTests
@ -121,5 +130,174 @@ namespace Perspex.Controls.UnitTests.Primitives
Assert.Null(((IVisual)child).VisualParent);
}
[Fact]
public void PopupRoot_Should_Initially_Be_Null()
{
using (CreateServices())
{
var target = new Popup();
Assert.Null(target.PopupRoot);
}
}
[Fact]
public void PopupRoot_Should_Have_Null_VisualParent()
{
using (CreateServices())
{
var target = new Popup();
target.Open();
Assert.Null(target.PopupRoot.GetVisualParent());
}
}
[Fact]
public void PopupRoot_Should_Have_Popup_As_LogicalParent()
{
using (CreateServices())
{
var target = new Popup();
target.Open();
Assert.Equal(target, target.PopupRoot.Parent);
Assert.Equal(target, target.PopupRoot.GetLogicalParent());
}
}
[Fact]
public void PopupRoot_Should_Have_Template_Applied()
{
using (CreateServices())
{
var target = new Popup();
var child = new Control();
target.Open();
Assert.Equal(1, target.PopupRoot.GetVisualChildren().Count());
var templatedChild = target.PopupRoot.GetVisualChildren().Single();
Assert.IsType<ContentPresenter>(templatedChild);
Assert.Equal(target.PopupRoot, ((IControl)templatedChild).TemplatedParent);
}
}
[Fact]
public void PopupRoot_Should_Have_Child_As_LogicalChild()
{
using (CreateServices())
{
var target = new Popup();
var child = new Control();
target.Child = child;
target.Open();
Assert.Equal(new[] { child }, target.PopupRoot.GetLogicalChildren());
}
}
[Fact]
public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent()
{
using (CreateServices())
{
PopupContentControl target;
var root = new TestRoot
{
Child = target = new PopupContentControl
{
Content = new Border(),
Template = new ControlTemplate<PopupContentControl>(PopupContentControlTemplate),
}
};
target.ApplyTemplate();
var popup = target.GetTemplateChild<Popup>("popup");
popup.Open();
var popupRoot = popup.PopupRoot;
var children = popupRoot.GetVisualDescendents().ToList();
var types = children.Select(x => x.GetType().Name).ToList();
Assert.Equal(
new[]
{
"ContentPresenter",
"ContentPresenter",
"Border",
},
types);
var templatedParents = children
.OfType<IControl>()
.Select(x => x.TemplatedParent).ToList();
Assert.Equal(
new object[]
{
popupRoot,
target,
null,
},
templatedParents);
}
}
private static IDisposable CreateServices()
{
var result = Locator.Current.WithResolver();
var styles = new Styles
{
new Style(x => x.OfType<PopupRoot>())
{
Setters = new[]
{
new Setter(TemplatedControl.TemplateProperty, new ControlTemplate<PopupRoot>(PopupRootTemplate)),
}
},
};
var globalStyles = new Mock<IGlobalStyles>();
globalStyles.Setup(x => x.Styles).Returns(styles);
Locator.CurrentMutable.Register(() => globalStyles.Object, typeof(IGlobalStyles));
Locator.CurrentMutable.Register(() => new LayoutManager(), typeof(ILayoutManager));
Locator.CurrentMutable.Register(() => new Mock<IPlatformThreadingInterface>().Object, typeof(IPlatformThreadingInterface));
Locator.CurrentMutable.Register(() => new Mock<IPopupImpl>().Object, typeof(IPopupImpl));
Locator.CurrentMutable.Register(() => new Styler(), typeof(IStyler));
return result;
}
private static IControl PopupRootTemplate(PopupRoot control)
{
return new ContentPresenter
{
Name = "contentPresenter",
[~ContentPresenter.ContentProperty] = control[~PopupRoot.ContentProperty],
};
}
private static IControl PopupContentControlTemplate(PopupContentControl control)
{
return new Popup
{
Name = "popup",
Child = new ContentPresenter
{
[~ContentPresenter.ContentProperty] = control[~PopupRoot.ContentProperty],
}
};
}
private class PopupContentControl : ContentControl
{
}
}
}

2
Tests/Perspex.Controls.UnitTests/TestRoot.cs

@ -36,7 +36,7 @@ namespace Perspex.Controls.UnitTests
public Point TranslatePointToScreen(Point p)
{
throw new NotImplementedException();
return new Point();
}
}
}

Loading…
Cancel
Save