Browse Source

Implemented NameScopes

Some tests still failing as controls are only registered with name scope
when attached to a rooted visual tree. Need to work around this by
explicitly registering controls when applying template.
pull/325/head
Steven Kirk 10 years ago
parent
commit
16c2242757
  1. 12
      src/Perspex.Controls/Button.cs
  2. 6
      src/Perspex.Controls/Carousel.cs
  3. 4
      src/Perspex.Controls/ContentControl.cs
  4. 36
      src/Perspex.Controls/Control.cs
  5. 4
      src/Perspex.Controls/DropDown.cs
  6. 4
      src/Perspex.Controls/GridSplitter.cs
  7. 4
      src/Perspex.Controls/ItemsControl.cs
  8. 22
      src/Perspex.Controls/Menu.cs
  9. 6
      src/Perspex.Controls/MenuItem.cs
  10. 20
      src/Perspex.Controls/Primitives/AccessText.cs
  11. 20
      src/Perspex.Controls/Primitives/Popup.cs
  12. 4
      src/Perspex.Controls/Primitives/PopupRoot.cs
  13. 7
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  14. 4
      src/Perspex.Controls/ProgressBar.cs
  15. 21
      src/Perspex.Controls/Templates/TemplateExtensions.cs
  16. 4
      src/Perspex.Controls/TextBox.cs
  17. 4
      src/Perspex.Controls/TopLevel.cs
  18. 4
      src/Perspex.Controls/TreeViewItem.cs
  19. 8
      src/Perspex.Input/InputElement.cs
  20. 31
      src/Perspex.SceneGraph/INameScope.cs
  21. 8
      src/Perspex.SceneGraph/INamed.cs
  22. 80
      src/Perspex.SceneGraph/NameScope.cs
  23. 68
      src/Perspex.SceneGraph/NameScopeExtensions.cs
  24. 5
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  25. 169
      src/Perspex.SceneGraph/Visual.cs
  26. 36
      src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs
  27. 1
      src/Perspex.Styling/Perspex.Styling.csproj
  28. 2
      tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs
  29. 7
      tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs
  30. 23
      tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  31. 4
      tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs
  32. 2
      tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs
  33. 19
      tests/Perspex.SceneGraph.UnitTests/TestRoot.cs
  34. 39
      tests/Perspex.SceneGraph.UnitTests/TestVisual.cs
  35. 87
      tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

12
src/Perspex.Controls/Button.cs

@ -147,13 +147,13 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
if (IsDefault)
{
var inputElement = root as IInputElement;
var inputElement = e.Root as IInputElement;
if (inputElement != null)
{
@ -198,13 +198,13 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(oldRoot);
base.OnDetachedFromVisualTree(e);
if (IsDefault)
{
var inputElement = oldRoot as IInputElement;
var inputElement = e.Root as IInputElement;
if (inputElement != null)
{

6
src/Perspex.Controls/Carousel.cs

@ -55,11 +55,5 @@ namespace Perspex.Controls
{
// Ignore pointer presses.
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
{
base.OnTemplateApplied();
}
}
}

4
src/Perspex.Controls/ContentControl.cs

@ -104,12 +104,12 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
// We allow ContentControls without ContentPresenters in the template. This can be
// useful for e.g. a simple ToggleButton that displays an image. There's no need to
// have a ContentPresenter in the visual tree for that.
Presenter = this.FindTemplateChild<ContentPresenter>("PART_ContentPresenter");
Presenter = nameScope.Find<ContentPresenter>("PART_ContentPresenter");
}
}
}

36
src/Perspex.Controls/Control.cs

@ -19,7 +19,6 @@ namespace Perspex.Controls
/// <remarks>
/// The control class extends <see cref="InputElement"/> and adds the following features:
///
/// - A <see cref="Name"/>.
/// - An inherited <see cref="DataContext"/>.
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - A collection of class strings for custom styling.
@ -71,7 +70,6 @@ namespace Perspex.Controls
private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates;
private IControl _focusAdorner;
private string _name;
private IPerspexList<ILogical> _logicalChildren;
private Styles _styles;
@ -165,36 +163,6 @@ namespace Perspex.Controls
}
}
/// <summary>
/// Gets or sets the name of the control.
/// </summary>
/// <remarks>
/// A control's name is used to uniquely identify a control within the control's name
/// scope. Once a control is added to a visual tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (_name != null)
{
throw new InvalidOperationException("Name already set.");
}
if (((IVisual)this).VisualParent != null)
{
throw new InvalidOperationException("Cannot set Name : control already added to tree.");
}
_name = value;
}
}
/// <summary>
/// Gets or sets the styles for the control.
/// </summary>
@ -404,9 +372,9 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
IStyler styler = PerspexLocator.Current.GetService<IStyler>();

4
src/Perspex.Controls/DropDown.cs

@ -125,14 +125,14 @@ namespace Perspex.Controls
base.OnPointerPressed(e);
}
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
if (_popup != null)
{
_popup.Opened -= PopupOpened;
}
_popup = this.GetTemplateChild<Popup>("PART_Popup");
_popup = nameScope.Get<Popup>("PART_Popup");
_popup.Opened += PopupOpened;
}

4
src/Perspex.Controls/GridSplitter.cs

@ -34,9 +34,9 @@ namespace Perspex.Controls
}
}
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
_grid = this.GetVisualParent<Grid>();
}
}

4
src/Perspex.Controls/ItemsControl.cs

@ -147,9 +147,9 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
Presenter = this.FindTemplateChild<IItemsPresenter>("PART_ItemsPresenter");
Presenter = nameScope.Find<IItemsPresenter>("PART_ItemsPresenter");
}
/// <summary>

22
src/Perspex.Controls/Menu.cs

@ -96,15 +96,12 @@ namespace Perspex.Controls
IsOpen = true;
}
/// <summary>
/// Called when the <see cref="MenuItem"/> is attached to the visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
var topLevel = root as TopLevel;
var topLevel = e.Root as TopLevel;
topLevel.Deactivated += Deactivated;
@ -117,7 +114,7 @@ namespace Perspex.Controls
pointerPress,
Disposable.Create(() => topLevel.Deactivated -= Deactivated));
var inputRoot = root as IInputRoot;
var inputRoot = e.Root as IInputRoot;
if (inputRoot != null && inputRoot.AccessKeyHandler != null)
{
@ -125,13 +122,10 @@ namespace Perspex.Controls
}
}
/// <summary>
/// Called when the <see cref="Menu"/> is detached from the visual tree.
/// </summary>
/// <param name="oldRoot">The root of the visual tree being detached from.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(oldRoot);
base.OnDetachedFromVisualTree(e);
_subscription.Dispose();
}

6
src/Perspex.Controls/MenuItem.cs

@ -376,11 +376,11 @@ namespace Perspex.Controls
/// <summary>
/// Called when the MenuItem's template has been applied.
/// </summary>
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
base.OnTemplateApplied();
base.OnTemplateApplied(nameScope);
_popup = this.GetTemplateChild<Popup>("PART_Popup");
_popup = nameScope.Get<Popup>("PART_Popup");
_popup.DependencyResolver = DependencyResolver.Instance;
_popup.PopupRootCreated += PopupRootCreated;
_popup.Opened += PopupOpened;

20
src/Perspex.Controls/Primitives/AccessText.cs

@ -108,14 +108,11 @@ namespace Perspex.Controls.Primitives
return result.WithHeight(result.Height + 1);
}
/// <summary>
/// Called when the control is attached to a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
_accessKeys = (root as IInputRoot)?.AccessKeyHandler;
base.OnAttachedToVisualTree(e);
_accessKeys = (e.Root as IInputRoot)?.AccessKeyHandler;
if (_accessKeys != null && AccessKey != 0)
{
@ -123,13 +120,10 @@ namespace Perspex.Controls.Primitives
}
}
/// <summary>
/// Called when the control is detached from a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot root)
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(root);
base.OnDetachedFromVisualTree(e);
if (_accessKeys != null && AccessKey != 0)
{

20
src/Perspex.Controls/Primitives/Popup.cs

@ -212,23 +212,17 @@ namespace Perspex.Controls.Primitives
return new Size();
}
/// <summary>
/// Called when the control is added to the visual tree.
/// </summary>
/// <param name="root">THe root of the visual tree.</param>
protected override void OnAttachedToVisualTree(IRenderRoot root)
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
_topLevel = root as TopLevel;
base.OnAttachedToVisualTree(e);
_topLevel = e.Root as TopLevel;
}
/// <summary>
/// Called when the control is removed to the visual tree.
/// </summary>
/// <param name="root">THe root of the visual tree.</param>
protected override void OnDetachedFromVisualTree(IRenderRoot root)
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(root);
base.OnDetachedFromVisualTree(e);
_topLevel = null;
}

4
src/Perspex.Controls/Primitives/PopupRoot.cs

@ -93,9 +93,9 @@ namespace Perspex.Controls.Primitives
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
base.OnTemplateApplied();
base.OnTemplateApplied(nameScope);
if (Parent.TemplatedParent != null)
{

7
src/Perspex.Controls/Primitives/TemplatedControl.cs

@ -195,6 +195,8 @@ namespace Perspex.Controls.Primitives
_templateLog.Verbose("Creating control template");
var child = Template.Build(this);
var nameScope = new NameScope();
NameScope.SetNameScope((Visual)child, nameScope);
// We need to call SetTemplatedParentAndApplyChildTemplates twice - once
// before the controls are added to the visual tree so that the logical
@ -207,7 +209,7 @@ namespace Perspex.Controls.Primitives
AddVisualChild((Visual)child);
SetTemplatedParentAndApplyChildTemplates(child);
OnTemplateApplied();
OnTemplateApplied(nameScope);
}
_templateApplied = true;
@ -217,7 +219,8 @@ namespace Perspex.Controls.Primitives
/// <summary>
/// Called when the control's template is applied.
/// </summary>
protected virtual void OnTemplateApplied()
/// <param name="nameScope">The template name scope.</param>
protected virtual void OnTemplateApplied(INameScope nameScope)
{
}

4
src/Perspex.Controls/ProgressBar.cs

@ -26,9 +26,9 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
_indicator = this.GetTemplateChild<Border>("PART_Indicator");
_indicator = nameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size);
}

21
src/Perspex.Controls/Templates/TemplateExtensions.cs

@ -41,27 +41,6 @@ namespace Perspex.Controls.Templates
return null;
}
public static T FindTemplateChild<T>(this ITemplatedControl control, string name) where T : INamed
{
return control.GetTemplateChildren().OfType<T>().SingleOrDefault(x => x.Name == name);
}
public static T GetTemplateChild<T>(this ITemplatedControl control, string name) where T : INamed
{
var result = control.FindTemplateChild<T>(name);
if (result == null)
{
throw new InvalidOperationException(string.Format(
"Could not find template child '{0}' of type '{1}' in template for '{2}'.",
name,
typeof(T).FullName,
control.GetType().FullName));
}
return result;
}
public static IEnumerable<Control> GetTemplateChildren(this ITemplatedControl control)
{
var visual = control as IVisual;

4
src/Perspex.Controls/TextBox.cs

@ -136,9 +136,9 @@ namespace Perspex.Controls
set { SetValue(TextWrappingProperty, value); }
}
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
_presenter = this.GetTemplateChild<TextPresenter>("PART_TextPresenter");
_presenter = nameScope.Get<TextPresenter>("PART_TextPresenter");
_presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
}

4
src/Perspex.Controls/TopLevel.cs

@ -287,9 +287,9 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
throw new InvalidOperationException(
$"Control '{GetType().Name}' is a top level control and cannot be added as a child.");

4
src/Perspex.Controls/TreeViewItem.cs

@ -83,9 +83,9 @@ namespace Perspex.Controls
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
_treeView = this.GetVisualAncestors().OfType<TreeView>().FirstOrDefault();
}

8
src/Perspex.Input/InputElement.cs

@ -348,9 +348,9 @@ namespace Perspex.Input
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(oldRoot);
base.OnDetachedFromVisualTree(e);
if (IsFocused)
{
@ -359,9 +359,9 @@ namespace Perspex.Input
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(IRenderRoot root)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(root);
base.OnAttachedToVisualTree(e);
UpdateIsEnabledCore();
}

31
src/Perspex.SceneGraph/INameScope.cs

@ -0,0 +1,31 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex
{
/// <summary>
/// Defines a name scope.
/// </summary>
public interface INameScope
{
/// <summary>
/// Registers an element eith the name scope.
/// </summary>
/// <param name="name">The element name.</param>
/// <param name="element">The element.</param>
void Register(string name, object element);
/// <summary>
/// Finds a named element in the name scope.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
object Find(string name);
/// <summary>
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
void Unregister(string name);
}
}

8
src/Perspex.Styling/Styling/INamed.cs → src/Perspex.SceneGraph/INamed.cs

@ -1,10 +1,16 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Styling
namespace Perspex
{
/// <summary>
/// Interface for named elements.
/// </summary>
public interface INamed
{
/// <summary>
/// Gets the element name.
/// </summary>
string Name { get; }
}
}

80
src/Perspex.SceneGraph/NameScope.cs

@ -0,0 +1,80 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Perspex
{
/// <summary>
/// Implements a name scope.
/// </summary>
public class NameScope : INameScope
{
/// <summary>
/// Defines the NameScope attached property.
/// </summary>
public static readonly PerspexProperty<INameScope> NameScopeProperty =
PerspexProperty.RegisterAttached<NameScope, Visual, INameScope>("NameScope");
private Dictionary<string, object> _inner = new Dictionary<string, object>();
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(Visual visual)
{
return visual.GetValue(NameScopeProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="value">The value to set.</param>
public static void SetNameScope(Visual visual, INameScope value)
{
visual.SetValue(NameScopeProperty, value);
}
/// <summary>
/// Registers an element with the name scope.
/// </summary>
/// <param name="name">The element name.</param>
/// <param name="element">The element.</param>
public void Register(string name, object element)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(element != null);
_inner.Add(name, element);
}
/// <summary>
/// Finds a named element in the name scope.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
public object Find(string name)
{
Contract.Requires<ArgumentNullException>(name != null);
object result;
_inner.TryGetValue(name, out result);
return result;
}
/// <summary>
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
public void Unregister(string name)
{
Contract.Requires<ArgumentNullException>(name != null);
_inner.Remove(name);
}
}
}

68
src/Perspex.SceneGraph/NameScopeExtensions.cs

@ -0,0 +1,68 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Perspex
{
/// <summary>
/// Extension methods for <see cref="INameScope"/>.
/// </summary>
public static class NameScopeExtensions
{
/// <summary>
/// Finds a named element in an <see cref="INameScope"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="nameScope">The name scope.</param>
/// <param name="name">The name.</param>
/// <returns>The named element or null if not found.</returns>
public static T Find<T>(this INameScope nameScope, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(nameScope != null);
Contract.Requires<ArgumentNullException>(name != null);
var result = nameScope.Find(name);
if (result != null && !(result is T))
{
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
}
return (T)result;
}
/// <summary>
/// Gets a named element from an <see cref="INameScope"/> or throws if no element of the
/// requested name was found.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="nameScope">The name scope.</param>
/// <param name="name">The name.</param>
/// <returns>The named element.</returns>
public static T Get<T>(this INameScope nameScope, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(nameScope != null);
Contract.Requires<ArgumentNullException>(name != null);
var result = nameScope.Find(name);
if (result == null)
{
throw new KeyNotFoundException($"Could not find control '{name}'.");
}
if (!(result is T))
{
throw new InvalidOperationException(
$"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
}
return (T)result;
}
}
}

5
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@ -55,6 +55,8 @@
<Compile Include="Animation\PageSlide.cs" />
<Compile Include="Animation\CrossFade.cs" />
<Compile Include="Animation\IPageTransition.cs" />
<Compile Include="INamed.cs" />
<Compile Include="INameScope.cs" />
<Compile Include="Matrix.cs" />
<Compile Include="Media\AlignmentY.cs" />
<Compile Include="Media\AlignmentX.cs" />
@ -101,6 +103,8 @@
<Compile Include="Media\TileBrush.cs" />
<Compile Include="Media\ImageBush.cs" />
<Compile Include="Media\VisualBrush.cs" />
<Compile Include="NameScopeExtensions.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="RelativePoint.cs" />
<Compile Include="Platform\IFormattedTextImpl.cs" />
<Compile Include="Platform\IBitmapImpl.cs" />
@ -122,6 +126,7 @@
<Compile Include="Thickness.cs" />
<Compile Include="Vector.cs" />
<Compile Include="Visual.cs" />
<Compile Include="VisualTreeAttachmentEventArgs.cs" />
<Compile Include="VisualTree\IHostedVisualTreeRoot.cs" />
<Compile Include="VisualTree\IVisualTreeHost.cs" />
<Compile Include="VisualTree\TransformedBounds.cs" />

169
src/Perspex.SceneGraph/Visual.cs

@ -26,7 +26,7 @@ namespace Perspex
/// To traverse the scene graph (aka Visual Tree), use the extension methods defined
/// in <see cref="VisualExtensions"/>.
/// </remarks>
public class Visual : Animatable, IVisual
public class Visual : Animatable, IVisual, INamed
{
/// <summary>
/// Defines the <see cref="Bounds"/> property.
@ -76,6 +76,11 @@ namespace Perspex
public static readonly PerspexProperty<int> ZIndexProperty =
PerspexProperty.Register<Visual, int>(nameof(ZIndex));
/// <summary>
/// The name of the visual, if any.
/// </summary>
private string _name;
/// <summary>
/// Holds the children of the visual.
/// </summary>
@ -128,6 +133,16 @@ namespace Perspex
_visualChildren.CollectionChanged += VisualChildrenChanged;
}
/// <summary>
/// Raised when the control is attached to a rooted visual tree.
/// </summary>
public event EventHandler<VisualTreeAttachmentEventArgs> AttachedToVisualTree;
/// <summary>
/// Raised when the control is detached from a rooted visual tree.
/// </summary>
public event EventHandler<VisualTreeAttachmentEventArgs> DetachedFromVisualTree;
/// <summary>
/// Gets the bounds of the scene graph node relative to its parent.
/// </summary>
@ -163,6 +178,36 @@ namespace Perspex
set { SetValue(IsVisibleProperty, value); }
}
/// <summary>
/// Gets or sets the name of the visual.
/// </summary>
/// <remarks>
/// An element's name is used to uniquely identify a control within the control's name
/// scope. Once the element is added to a visual tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (value.Trim() == string.Empty)
{
throw new InvalidOperationException("Cannot set Name to empty string.");
}
if (_isAttachedToVisualTree)
{
throw new InvalidOperationException("Cannot set Name : control already added to tree.");
}
_name = value;
}
}
/// <summary>
/// Gets the opacity of the scene graph node.
/// </summary>
@ -340,17 +385,19 @@ namespace Perspex
/// <summary>
/// Called when the control is added to a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected virtual void OnAttachedToVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
AttachedToVisualTree?.Invoke(this, e);
}
/// <summary>
/// Called when the control is removed from a visual tree.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
protected virtual void OnDetachedFromVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DetachedFromVisualTree?.Invoke(this, e);
}
/// <summary>
@ -367,6 +414,60 @@ namespace Perspex
}
}
/// <summary>
/// Gets the event args for an <see cref="AttachedToVisualTree"/> or
/// <see cref="DetachedFromVisualTree"/> event.
/// </summary>
/// <returns>
/// A <see cref="VisualTreeAttachmentEventArgs"/> if the visual currently has a root;
/// otherwise null.
/// </returns>
private VisualTreeAttachmentEventArgs GetAttachmentEventArgs()
{
var e = (IVisual)this;
IRenderRoot root = null;
INameScope nameScope = null;
while (e != null)
{
if (nameScope == null)
{
nameScope = e as INameScope ?? NameScope.GetNameScope((Visual)e);
}
root = e as IRenderRoot;
if (root != null)
{
return new VisualTreeAttachmentEventArgs(root, nameScope);
}
e = e.VisualParent;
}
return null;
}
/// <summary>
/// Gets the <see cref="VisualTreeAttachmentEventArgs"/> for this element based on the
/// parent's args.
/// </summary>
/// <param name="e">The parent args.</param>
/// <returns>The args for this element.</returns>
private VisualTreeAttachmentEventArgs GetAttachmentEventArgs(VisualTreeAttachmentEventArgs e)
{
var childNameScope = (this as INameScope) ?? NameScope.GetNameScope(this);
if (childNameScope != null)
{
return new VisualTreeAttachmentEventArgs(e.Root, childNameScope);
}
else
{
return e;
}
}
/// <summary>
/// Gets the root of the controls visual tree and the distance from the root.
/// </summary>
@ -436,28 +537,23 @@ namespace Perspex
{
if (_visualParent != value)
{
var old = _visualParent;
var oldRoot = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
var newRoot = default(IRenderRoot);
if (value != null)
{
newRoot = value.GetSelfAndVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
}
var oldArgs = GetAttachmentEventArgs();
_visualParent = value;
if (oldRoot != null)
if (oldArgs != null)
{
NotifyDetachedFromVisualTree(oldRoot);
NotifyDetachedFromVisualTree(oldArgs);
}
if (newRoot != null)
var newArgs = GetAttachmentEventArgs();
if (newArgs != null)
{
NotifyAttachedToVisualTree(newRoot);
NotifyAttachedToVisualTree(newArgs);
}
RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue);
RaisePropertyChanged(VisualParentProperty, oldArgs, value, BindingPriority.LocalValue);
}
}
@ -491,43 +587,56 @@ namespace Perspex
}
/// <summary>
/// Calls the <see cref="OnAttachedToVisualTree(IRenderRoot)"/> method for this control
/// and all of its visual descendents.
/// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
private void NotifyAttachedToVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
private void NotifyAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_visualLogger.Verbose("Attached to visual tree");
_isAttachedToVisualTree = true;
OnAttachedToVisualTree(root);
if (Name != null && e.NameScope != null)
{
e.NameScope.Register(Name, this);
}
OnAttachedToVisualTree(e);
if (_visualChildren != null)
{
foreach (Visual child in _visualChildren.OfType<Visual>())
{
child.NotifyAttachedToVisualTree(root);
var ce = child.GetAttachmentEventArgs(e);
child.NotifyAttachedToVisualTree(ce);
}
}
}
/// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(IRenderRoot)"/> method for this control
/// and all of its visual descendents.
/// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents.
/// </summary>
/// <param name="root">The root of the visual tree.</param>
private void NotifyDetachedFromVisualTree(IRenderRoot root)
/// <param name="e">The event args.</param>
private void NotifyDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_visualLogger.Verbose("Detached from visual tree");
if (Name != null && e.NameScope != null)
{
e.NameScope.Unregister(Name);
}
_isAttachedToVisualTree = false;
OnDetachedFromVisualTree(root);
OnDetachedFromVisualTree(e);
if (_visualChildren != null)
{
foreach (Visual child in _visualChildren.OfType<Visual>())
{
child.NotifyDetachedFromVisualTree(root);
var ce = child.GetAttachmentEventArgs(e);
child.NotifyDetachedFromVisualTree(ce);
}
}
}

36
src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs

@ -0,0 +1,36 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Rendering;
namespace Perspex
{
/// <summary>
/// Holds the event arguments for the <see cref="Visual.AttachedToVisualTree"/> and
/// <see cref="Visual.DetachedFromVisualTree"/> events.
/// </summary>
public class VisualTreeAttachmentEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="VisualTreeAttachmentEventArgs"/> class.
/// </summary>
/// <param name="root">The root visual.</param>
/// <param name="nameScope">The name scope.</param>
public VisualTreeAttachmentEventArgs(IRenderRoot root, INameScope nameScope)
{
Root = root;
NameScope = nameScope;
}
/// <summary>
/// Gets the root of the visual tree that the visual is being attached to or detached from.
/// </summary>
public IRenderRoot Root { get; }
/// <summary>
/// Gets the element's name scope.
/// </summary>
public INameScope NameScope { get; }
}
}

1
src/Perspex.Styling/Perspex.Styling.csproj

@ -46,7 +46,6 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\Classes.cs" />
<Compile Include="Styling\IGlobalStyles.cs" />
<Compile Include="Styling\INamed.cs" />
<Compile Include="Styling\ISetter.cs" />
<Compile Include="Styling\IStyle.cs" />
<Compile Include="Styling\IStyleable.cs" />

2
tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs

@ -214,7 +214,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
var popup = target.GetTemplateChild<Popup>("popup");
var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
popup.Open();
var popupRoot = popup.PopupRoot;

7
tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Perspex.Controls.Primitives;
using Perspex.Controls.Templates;
using Perspex.Media;
@ -20,7 +21,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track");
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
target.Value = 50;
Assert.Equal(track.Value, 50);
@ -35,7 +36,7 @@ namespace Perspex.Controls.UnitTests.Primitives
};
target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track");
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
track.Value = 50;
Assert.Equal(target.Value, 50);
@ -51,7 +52,7 @@ namespace Perspex.Controls.UnitTests.Primitives
target.ApplyTemplate();
var track = target.GetTemplateChild<Track>("track");
var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
target.Value = 25;
track.Value = 50;

23
tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -50,6 +50,29 @@ namespace Perspex.Controls.UnitTests.Primitives
Assert.Empty(target.GetLogicalChildren());
}
[Fact]
public void Templated_Child_Should_Be_NameScope()
{
var target = new TemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator
{
Child = new Panel
{
Children = new Controls
{
new TextBlock(),
new Border(),
}
}
}),
};
target.ApplyTemplate();
Assert.NotNull(NameScope.GetNameScope((Visual)target.GetVisualChildren().Single()));
}
[Fact]
public void Templated_Children_Should_Have_TemplatedParent_Set()
{

4
tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs

@ -24,9 +24,7 @@ namespace Perspex.Controls.UnitTests
target.ApplyTemplate();
var presenter = target.GetTemplateChild<ScrollContentPresenter>("contentPresenter");
Assert.IsType<TextBlock>(presenter.Child);
Assert.IsType<TextBlock>(target.Presenter.Child);
}
[Fact]

2
tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs

@ -14,7 +14,7 @@ namespace Perspex.Controls.UnitTests
base.AddVisualChild(visual);
}
protected override void OnTemplateApplied()
protected override void OnTemplateApplied(INameScope nameScope)
{
OnTemplateAppliedCalled = true;
}

19
tests/Perspex.SceneGraph.UnitTests/TestRoot.cs

@ -7,8 +7,10 @@ using Perspex.Rendering;
namespace Perspex.SceneGraph.UnitTests
{
public class TestRoot : TestVisual, IRenderRoot
public class TestRoot : TestVisual, IRenderRoot, INameScope
{
private NameScope nameScope = new NameScope();
public IRenderTarget RenderTarget
{
get { throw new NotImplementedException(); }
@ -23,5 +25,20 @@ namespace Perspex.SceneGraph.UnitTests
{
throw new NotImplementedException();
}
public void Register(string name, object element)
{
nameScope.Register(name, element);
}
public object Find(string name)
{
return nameScope.Find(name);
}
public void Unregister(string name)
{
nameScope.Unregister(name);
}
}
}

39
tests/Perspex.SceneGraph.UnitTests/TestVisual.cs

@ -20,11 +20,28 @@ namespace Perspex.SceneGraph.UnitTests
public class TestVisual : Visual
{
public event EventHandler<ParamEventArgs<IRenderRoot>> AttachedToVisualTreeCalled;
public new PerspexObject InheritanceParent => base.InheritanceParent;
public event EventHandler<ParamEventArgs<IRenderRoot>> DetachedFromVisualTreeCalled;
public IVisual Child
{
get
{
return ((IVisual)this).VisualChildren.FirstOrDefault();
}
public new PerspexObject InheritanceParent => base.InheritanceParent;
set
{
if (Child != null)
{
RemoveVisualChild(Child);
}
if (value != null)
{
AddVisualChild(value);
}
}
}
public void AddChild(Visual v)
{
@ -45,21 +62,5 @@ namespace Perspex.SceneGraph.UnitTests
{
ClearVisualChildren();
}
protected override void OnAttachedToVisualTree(IRenderRoot root)
{
if (AttachedToVisualTreeCalled != null)
{
AttachedToVisualTreeCalled(this, new ParamEventArgs<IRenderRoot>(root));
}
}
protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot)
{
if (DetachedFromVisualTreeCalled != null)
{
DetachedFromVisualTreeCalled(this, new ParamEventArgs<IRenderRoot>(oldRoot));
}
}
}
}

87
tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Controls;
using Perspex.VisualTree;
using Xunit;
@ -84,5 +85,91 @@ namespace Perspex.SceneGraph.UnitTests
Assert.Equal(new Visual[] { null, null }, result);
}
[Fact]
public void Adding_Children_Should_Fire_OnAttachedToVisualTree()
{
var child2 = new TestVisual();
var child1 = new TestVisual { Child = child2 };
var root = new TestRoot();
var called1 = false;
var called2 = false;
child1.AttachedToVisualTree += (s, e) => called1 = true;
child2.AttachedToVisualTree += (s, e) => called2 = true;
root.Child = child1;
Assert.True(called1);
Assert.True(called2);
}
[Fact]
public void Removing_Children_Should_Fire_OnDetachedFromVisualTree()
{
var child2 = new TestVisual();
var child1 = new TestVisual { Child = child2 };
var root = new TestRoot();
var called1 = false;
var called2 = false;
root.Child = child1;
child1.DetachedFromVisualTree += (s, e) => called1 = true;
child2.DetachedFromVisualTree += (s, e) => called2 = true;
root.Child = null;
Assert.True(called1);
Assert.True(called2);
}
[Fact]
public void Controls_Should_Add_Themselves_To_Root_NameScope()
{
var child2 = new TestVisual { Name = "bar" };
var child1 = new TestVisual { Name = "foo", Child = child2 };
var root = new TestRoot { Child = child1 };
Assert.Same(child1, root.Find("foo"));
Assert.Same(child2, root.Find("bar"));
}
[Fact]
public void Controls_Should_Add_Themselves_To_NameScopes_In_Attached_Property()
{
var child2 = new TestVisual { Name = "bar", [NameScope.NameScopeProperty] = new NameScope() };
var child1 = new TestVisual { Name = "foo", Child = child2};
var root = new TestRoot { Child = child1 };
Assert.Same(child1, root.Find("foo"));
Assert.Null(root.Find("bar"));
Assert.Same(child2, NameScope.GetNameScope(child2).Find("bar"));
}
[Fact]
public void Controls_Should_Remove_Themselves_From_Root_NameScope()
{
var child2 = new TestVisual { Name = "bar" };
var child1 = new TestVisual { Name = "foo", Child = child2 };
var root = new TestRoot { Child = child1 };
root.Child = null;
Assert.Null(root.Find("foo"));
Assert.Null(root.Find("bar"));
}
[Fact]
public void Controls_Should_Remove_Themselves_From_NameScopes_In_Attached_Property()
{
var child2 = new TestVisual { Name = "bar" };
var child1 = new TestVisual { Name = "foo", Child = child2,[NameScope.NameScopeProperty] = new NameScope() };
var root = new TestRoot { Child = child1 };
root.Child = null;
Assert.Null(root.Find("foo"));
Assert.Null(root.Find("bar"));
Assert.Null(NameScope.GetNameScope(child1).Find("bar"));
}
}
}

Loading…
Cancel
Save