diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs index 29eb4c3628..a1ca0e11a6 100644 --- a/src/Perspex.Controls/Button.cs +++ b/src/Perspex.Controls/Button.cs @@ -147,13 +147,13 @@ namespace Perspex.Controls } /// - 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 } /// - 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) { diff --git a/src/Perspex.Controls/Carousel.cs b/src/Perspex.Controls/Carousel.cs index 1c12b634d0..97745e1452 100644 --- a/src/Perspex.Controls/Carousel.cs +++ b/src/Perspex.Controls/Carousel.cs @@ -55,11 +55,5 @@ namespace Perspex.Controls { // Ignore pointer presses. } - - /// - protected override void OnTemplateApplied() - { - base.OnTemplateApplied(); - } } } diff --git a/src/Perspex.Controls/ContentControl.cs b/src/Perspex.Controls/ContentControl.cs index 1640853391..c4c1c94e31 100644 --- a/src/Perspex.Controls/ContentControl.cs +++ b/src/Perspex.Controls/ContentControl.cs @@ -104,12 +104,12 @@ namespace Perspex.Controls } /// - 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("PART_ContentPresenter"); + Presenter = nameScope.Find("PART_ContentPresenter"); } } } diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index c4d85e7908..b382e82675 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -19,7 +19,6 @@ namespace Perspex.Controls /// /// The control class extends and adds the following features: /// - /// - A . /// - An inherited . /// - A 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 _logicalChildren; private Styles _styles; @@ -165,36 +163,6 @@ namespace Perspex.Controls } } - /// - /// Gets or sets the name of the control. - /// - /// - /// 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. - /// - 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; - } - } - /// /// Gets or sets the styles for the control. /// @@ -404,9 +372,9 @@ namespace Perspex.Controls } /// - protected override void OnAttachedToVisualTree(IRenderRoot root) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToVisualTree(root); + base.OnAttachedToVisualTree(e); IStyler styler = PerspexLocator.Current.GetService(); diff --git a/src/Perspex.Controls/DropDown.cs b/src/Perspex.Controls/DropDown.cs index 4af34d689d..31a44ad7d0 100644 --- a/src/Perspex.Controls/DropDown.cs +++ b/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("PART_Popup"); + _popup = nameScope.Get("PART_Popup"); _popup.Opened += PopupOpened; } diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs index 1fc78549eb..9a0ef0b988 100644 --- a/src/Perspex.Controls/GridSplitter.cs +++ b/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(); } } diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 9da2ae45ba..5c06dbc04e 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/src/Perspex.Controls/ItemsControl.cs @@ -147,9 +147,9 @@ namespace Perspex.Controls } /// - protected override void OnTemplateApplied() + protected override void OnTemplateApplied(INameScope nameScope) { - Presenter = this.FindTemplateChild("PART_ItemsPresenter"); + Presenter = nameScope.Find("PART_ItemsPresenter"); } /// diff --git a/src/Perspex.Controls/Menu.cs b/src/Perspex.Controls/Menu.cs index 3513843c37..9e9ee451e8 100644 --- a/src/Perspex.Controls/Menu.cs +++ b/src/Perspex.Controls/Menu.cs @@ -96,15 +96,12 @@ namespace Perspex.Controls IsOpen = true; } - /// - /// Called when the is attached to the visual tree. - /// - /// The root of the visual tree. - protected override void OnAttachedToVisualTree(IRenderRoot root) + /// + 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 } } - /// - /// Called when the is detached from the visual tree. - /// - /// The root of the visual tree being detached from. - protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnDetachedFromVisualTree(oldRoot); + base.OnDetachedFromVisualTree(e); _subscription.Dispose(); } diff --git a/src/Perspex.Controls/MenuItem.cs b/src/Perspex.Controls/MenuItem.cs index dde7dc16b3..4d73240604 100644 --- a/src/Perspex.Controls/MenuItem.cs +++ b/src/Perspex.Controls/MenuItem.cs @@ -376,11 +376,11 @@ namespace Perspex.Controls /// /// Called when the MenuItem's template has been applied. /// - protected override void OnTemplateApplied() + protected override void OnTemplateApplied(INameScope nameScope) { - base.OnTemplateApplied(); + base.OnTemplateApplied(nameScope); - _popup = this.GetTemplateChild("PART_Popup"); + _popup = nameScope.Get("PART_Popup"); _popup.DependencyResolver = DependencyResolver.Instance; _popup.PopupRootCreated += PopupRootCreated; _popup.Opened += PopupOpened; diff --git a/src/Perspex.Controls/Primitives/AccessText.cs b/src/Perspex.Controls/Primitives/AccessText.cs index 0168974248..9ad2640534 100644 --- a/src/Perspex.Controls/Primitives/AccessText.cs +++ b/src/Perspex.Controls/Primitives/AccessText.cs @@ -108,14 +108,11 @@ namespace Perspex.Controls.Primitives return result.WithHeight(result.Height + 1); } - /// - /// Called when the control is attached to a visual tree. - /// - /// The root of the visual tree. - protected override void OnAttachedToVisualTree(IRenderRoot root) + /// + 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 } } - /// - /// Called when the control is detached from a visual tree. - /// - /// The root of the visual tree. - protected override void OnDetachedFromVisualTree(IRenderRoot root) + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnDetachedFromVisualTree(root); + base.OnDetachedFromVisualTree(e); if (_accessKeys != null && AccessKey != 0) { diff --git a/src/Perspex.Controls/Primitives/Popup.cs b/src/Perspex.Controls/Primitives/Popup.cs index 6396b29843..138c5b586f 100644 --- a/src/Perspex.Controls/Primitives/Popup.cs +++ b/src/Perspex.Controls/Primitives/Popup.cs @@ -212,23 +212,17 @@ namespace Perspex.Controls.Primitives return new Size(); } - /// - /// Called when the control is added to the visual tree. - /// - /// THe root of the visual tree. - protected override void OnAttachedToVisualTree(IRenderRoot root) + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToVisualTree(root); - _topLevel = root as TopLevel; + base.OnAttachedToVisualTree(e); + _topLevel = e.Root as TopLevel; } - /// - /// Called when the control is removed to the visual tree. - /// - /// THe root of the visual tree. - protected override void OnDetachedFromVisualTree(IRenderRoot root) + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnDetachedFromVisualTree(root); + base.OnDetachedFromVisualTree(e); _topLevel = null; } diff --git a/src/Perspex.Controls/Primitives/PopupRoot.cs b/src/Perspex.Controls/Primitives/PopupRoot.cs index 4e628f79f0..aeaa63dd0f 100644 --- a/src/Perspex.Controls/Primitives/PopupRoot.cs +++ b/src/Perspex.Controls/Primitives/PopupRoot.cs @@ -93,9 +93,9 @@ namespace Perspex.Controls.Primitives } /// - protected override void OnTemplateApplied() + protected override void OnTemplateApplied(INameScope nameScope) { - base.OnTemplateApplied(); + base.OnTemplateApplied(nameScope); if (Parent.TemplatedParent != null) { diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index bbe9836e03..868da9b670 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/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 /// /// Called when the control's template is applied. /// - protected virtual void OnTemplateApplied() + /// The template name scope. + protected virtual void OnTemplateApplied(INameScope nameScope) { } diff --git a/src/Perspex.Controls/ProgressBar.cs b/src/Perspex.Controls/ProgressBar.cs index f9a8d867d0..4bc1fdb9d2 100644 --- a/src/Perspex.Controls/ProgressBar.cs +++ b/src/Perspex.Controls/ProgressBar.cs @@ -26,9 +26,9 @@ namespace Perspex.Controls } /// - protected override void OnTemplateApplied() + protected override void OnTemplateApplied(INameScope nameScope) { - _indicator = this.GetTemplateChild("PART_Indicator"); + _indicator = nameScope.Get("PART_Indicator"); UpdateIndicator(Bounds.Size); } diff --git a/src/Perspex.Controls/Templates/TemplateExtensions.cs b/src/Perspex.Controls/Templates/TemplateExtensions.cs index e4e61c722f..b8cd9e25c2 100644 --- a/src/Perspex.Controls/Templates/TemplateExtensions.cs +++ b/src/Perspex.Controls/Templates/TemplateExtensions.cs @@ -41,27 +41,6 @@ namespace Perspex.Controls.Templates return null; } - public static T FindTemplateChild(this ITemplatedControl control, string name) where T : INamed - { - return control.GetTemplateChildren().OfType().SingleOrDefault(x => x.Name == name); - } - - public static T GetTemplateChild(this ITemplatedControl control, string name) where T : INamed - { - var result = control.FindTemplateChild(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 GetTemplateChildren(this ITemplatedControl control) { var visual = control as IVisual; diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index affa38c023..36fd8c0cb9 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/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("PART_TextPresenter"); + _presenter = nameScope.Get("PART_TextPresenter"); _presenter.Cursor = new Cursor(StandardCursorType.Ibeam); } diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 21247721bd..6aacac85ff 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -287,9 +287,9 @@ namespace Perspex.Controls } /// - 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."); diff --git a/src/Perspex.Controls/TreeViewItem.cs b/src/Perspex.Controls/TreeViewItem.cs index bd499e4f10..da263ca951 100644 --- a/src/Perspex.Controls/TreeViewItem.cs +++ b/src/Perspex.Controls/TreeViewItem.cs @@ -83,9 +83,9 @@ namespace Perspex.Controls } /// - protected override void OnAttachedToVisualTree(IRenderRoot root) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToVisualTree(root); + base.OnAttachedToVisualTree(e); _treeView = this.GetVisualAncestors().OfType().FirstOrDefault(); } diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs index 7e0907c9e8..5be37be76a 100644 --- a/src/Perspex.Input/InputElement.cs +++ b/src/Perspex.Input/InputElement.cs @@ -348,9 +348,9 @@ namespace Perspex.Input } /// - 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 } /// - protected override void OnAttachedToVisualTree(IRenderRoot root) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnAttachedToVisualTree(root); + base.OnAttachedToVisualTree(e); UpdateIsEnabledCore(); } diff --git a/src/Perspex.SceneGraph/INameScope.cs b/src/Perspex.SceneGraph/INameScope.cs new file mode 100644 index 0000000000..13b197f1ad --- /dev/null +++ b/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 +{ + /// + /// Defines a name scope. + /// + public interface INameScope + { + /// + /// Registers an element eith the name scope. + /// + /// The element name. + /// The element. + void Register(string name, object element); + + /// + /// Finds a named element in the name scope. + /// + /// The name. + /// The element, or null if the name was not found. + object Find(string name); + + /// + /// Unregisters an element with the name scope. + /// + /// The name. + void Unregister(string name); + } +} diff --git a/src/Perspex.Styling/Styling/INamed.cs b/src/Perspex.SceneGraph/INamed.cs similarity index 58% rename from src/Perspex.Styling/Styling/INamed.cs rename to src/Perspex.SceneGraph/INamed.cs index bd5b806d7e..5ceda5a265 100644 --- a/src/Perspex.Styling/Styling/INamed.cs +++ b/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 { + /// + /// Interface for named elements. + /// public interface INamed { + /// + /// Gets the element name. + /// string Name { get; } } } diff --git a/src/Perspex.SceneGraph/NameScope.cs b/src/Perspex.SceneGraph/NameScope.cs new file mode 100644 index 0000000000..403a51bf1f --- /dev/null +++ b/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 +{ + /// + /// Implements a name scope. + /// + public class NameScope : INameScope + { + /// + /// Defines the NameScope attached property. + /// + public static readonly PerspexProperty NameScopeProperty = + PerspexProperty.RegisterAttached("NameScope"); + + private Dictionary _inner = new Dictionary(); + + /// + /// Gets the value of the attached on a visual. + /// + /// The visual. + /// The value of the NameScope attached property. + public static INameScope GetNameScope(Visual visual) + { + return visual.GetValue(NameScopeProperty); + } + + /// + /// Sets the value of the attached on a visual. + /// + /// The visual. + /// The value to set. + public static void SetNameScope(Visual visual, INameScope value) + { + visual.SetValue(NameScopeProperty, value); + } + + /// + /// Registers an element with the name scope. + /// + /// The element name. + /// The element. + public void Register(string name, object element) + { + Contract.Requires(name != null); + Contract.Requires(element != null); + + _inner.Add(name, element); + } + + /// + /// Finds a named element in the name scope. + /// + /// The name. + /// The element, or null if the name was not found. + public object Find(string name) + { + Contract.Requires(name != null); + + object result; + _inner.TryGetValue(name, out result); + return result; + } + + /// + /// Unregisters an element with the name scope. + /// + /// The name. + public void Unregister(string name) + { + Contract.Requires(name != null); + + _inner.Remove(name); + } + } +} diff --git a/src/Perspex.SceneGraph/NameScopeExtensions.cs b/src/Perspex.SceneGraph/NameScopeExtensions.cs new file mode 100644 index 0000000000..3e80f8e838 --- /dev/null +++ b/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 +{ + /// + /// Extension methods for . + /// + public static class NameScopeExtensions + { + /// + /// Finds a named element in an . + /// + /// The element type. + /// The name scope. + /// The name. + /// The named element or null if not found. + public static T Find(this INameScope nameScope, string name) + where T : class + { + Contract.Requires(nameScope != null); + Contract.Requires(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; + } + + /// + /// Gets a named element from an or throws if no element of the + /// requested name was found. + /// + /// The element type. + /// The name scope. + /// The name. + /// The named element. + public static T Get(this INameScope nameScope, string name) + where T : class + { + Contract.Requires(nameScope != null); + Contract.Requires(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; + } + } +} diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index d2d897142f..871e14750f 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -55,6 +55,8 @@ + + @@ -101,6 +103,8 @@ + + @@ -122,6 +126,7 @@ + diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index a63b8b0aad..53c6b8e66d 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/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 . /// - public class Visual : Animatable, IVisual + public class Visual : Animatable, IVisual, INamed { /// /// Defines the property. @@ -76,6 +76,11 @@ namespace Perspex public static readonly PerspexProperty ZIndexProperty = PerspexProperty.Register(nameof(ZIndex)); + /// + /// The name of the visual, if any. + /// + private string _name; + /// /// Holds the children of the visual. /// @@ -128,6 +133,16 @@ namespace Perspex _visualChildren.CollectionChanged += VisualChildrenChanged; } + /// + /// Raised when the control is attached to a rooted visual tree. + /// + public event EventHandler AttachedToVisualTree; + + /// + /// Raised when the control is detached from a rooted visual tree. + /// + public event EventHandler DetachedFromVisualTree; + /// /// Gets the bounds of the scene graph node relative to its parent. /// @@ -163,6 +178,36 @@ namespace Perspex set { SetValue(IsVisibleProperty, value); } } + /// + /// Gets or sets the name of the visual. + /// + /// + /// 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. + /// + 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; + } + } + /// /// Gets the opacity of the scene graph node. /// @@ -340,17 +385,19 @@ namespace Perspex /// /// Called when the control is added to a visual tree. /// - /// The root of the visual tree. - protected virtual void OnAttachedToVisualTree(IRenderRoot root) + /// The event args. + protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { + AttachedToVisualTree?.Invoke(this, e); } /// /// Called when the control is removed from a visual tree. /// - /// The root of the visual tree. - protected virtual void OnDetachedFromVisualTree(IRenderRoot root) + /// The event args. + protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { + DetachedFromVisualTree?.Invoke(this, e); } /// @@ -367,6 +414,60 @@ namespace Perspex } } + /// + /// Gets the event args for an or + /// event. + /// + /// + /// A if the visual currently has a root; + /// otherwise null. + /// + 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; + } + + /// + /// Gets the for this element based on the + /// parent's args. + /// + /// The parent args. + /// The args for this element. + 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; + } + } + /// /// Gets the root of the controls visual tree and the distance from the root. /// @@ -436,28 +537,23 @@ namespace Perspex { if (_visualParent != value) { - var old = _visualParent; - var oldRoot = this.GetVisualAncestors().OfType().FirstOrDefault(); - var newRoot = default(IRenderRoot); - - if (value != null) - { - newRoot = value.GetSelfAndVisualAncestors().OfType().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 } /// - /// Calls the method for this control - /// and all of its visual descendents. + /// Calls the method + /// for this control and all of its visual descendents. /// - /// The root of the visual tree. - private void NotifyAttachedToVisualTree(IRenderRoot root) + /// The event args. + 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()) { - child.NotifyAttachedToVisualTree(root); + var ce = child.GetAttachmentEventArgs(e); + child.NotifyAttachedToVisualTree(ce); } } } /// - /// Calls the method for this control - /// and all of its visual descendents. + /// Calls the method + /// for this control and all of its visual descendents. /// - /// The root of the visual tree. - private void NotifyDetachedFromVisualTree(IRenderRoot root) + /// The event args. + 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()) { - child.NotifyDetachedFromVisualTree(root); + var ce = child.GetAttachmentEventArgs(e); + child.NotifyDetachedFromVisualTree(ce); } } } diff --git a/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs b/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs new file mode 100644 index 0000000000..41f37e26fc --- /dev/null +++ b/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 +{ + /// + /// Holds the event arguments for the and + /// events. + /// + public class VisualTreeAttachmentEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The root visual. + /// The name scope. + public VisualTreeAttachmentEventArgs(IRenderRoot root, INameScope nameScope) + { + Root = root; + NameScope = nameScope; + } + + /// + /// Gets the root of the visual tree that the visual is being attached to or detached from. + /// + public IRenderRoot Root { get; } + + /// + /// Gets the element's name scope. + /// + public INameScope NameScope { get; } + } +} diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index c0776e27c2..a8e997821e 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -46,7 +46,6 @@ - diff --git a/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs index 28da8d75e3..18d7867b0f 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs @@ -214,7 +214,7 @@ namespace Perspex.Controls.UnitTests.Primitives }; target.ApplyTemplate(); - var popup = target.GetTemplateChild("popup"); + var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup"); popup.Open(); var popupRoot = popup.PopupRoot; diff --git a/tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs index 6ba3adc899..eb19ac1493 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs +++ b/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"); + 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"); + 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"); + var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track"); target.Value = 25; track.Value = 50; diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 0138fe99fa..1d39e8d443 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/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() { diff --git a/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs b/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs index 596acd53d8..7e54fb0e84 100644 --- a/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs @@ -24,9 +24,7 @@ namespace Perspex.Controls.UnitTests target.ApplyTemplate(); - var presenter = target.GetTemplateChild("contentPresenter"); - - Assert.IsType(presenter.Child); + Assert.IsType(target.Presenter.Child); } [Fact] diff --git a/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs b/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs index 9456caa557..8cbf1dca3f 100644 --- a/tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs +++ b/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; } diff --git a/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs b/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs index 99a1fd0d4a..a1802b2cef 100644 --- a/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs +++ b/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); + } } } diff --git a/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs b/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs index cb3bc4328d..88f47d7876 100644 --- a/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs +++ b/tests/Perspex.SceneGraph.UnitTests/TestVisual.cs @@ -20,11 +20,28 @@ namespace Perspex.SceneGraph.UnitTests public class TestVisual : Visual { - public event EventHandler> AttachedToVisualTreeCalled; + public new PerspexObject InheritanceParent => base.InheritanceParent; - public event EventHandler> 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(root)); - } - } - - protected override void OnDetachedFromVisualTree(IRenderRoot oldRoot) - { - if (DetachedFromVisualTreeCalled != null) - { - DetachedFromVisualTreeCalled(this, new ParamEventArgs(oldRoot)); - } - } } } diff --git a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs index 31acf6f9dc..7756130c20 100644 --- a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs +++ b/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")); + } } }