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.
Steven Kirk 10 years ago
parent
commit
16c2242757
35 changed files with 581 additions and 194 deletions
  1. 6 6
      src/Perspex.Controls/Button.cs
  2. 0 6
      src/Perspex.Controls/Carousel.cs
  3. 2 2
      src/Perspex.Controls/ContentControl.cs
  4. 2 34
      src/Perspex.Controls/Control.cs
  5. 2 2
      src/Perspex.Controls/DropDown.cs
  6. 2 2
      src/Perspex.Controls/GridSplitter.cs
  7. 2 2
      src/Perspex.Controls/ItemsControl.cs
  8. 8 14
      src/Perspex.Controls/Menu.cs
  9. 3 3
      src/Perspex.Controls/MenuItem.cs
  10. 7 13
      src/Perspex.Controls/Primitives/AccessText.cs
  11. 7 13
      src/Perspex.Controls/Primitives/Popup.cs
  12. 2 2
      src/Perspex.Controls/Primitives/PopupRoot.cs
  13. 5 2
      src/Perspex.Controls/Primitives/TemplatedControl.cs
  14. 2 2
      src/Perspex.Controls/ProgressBar.cs
  15. 0 21
      src/Perspex.Controls/Templates/TemplateExtensions.cs
  16. 2 2
      src/Perspex.Controls/TextBox.cs
  17. 2 2
      src/Perspex.Controls/TopLevel.cs
  18. 2 2
      src/Perspex.Controls/TreeViewItem.cs
  19. 4 4
      src/Perspex.Input/InputElement.cs
  20. 31 0
      src/Perspex.SceneGraph/INameScope.cs
  21. 7 1
      src/Perspex.SceneGraph/INamed.cs
  22. 80 0
      src/Perspex.SceneGraph/NameScope.cs
  23. 68 0
      src/Perspex.SceneGraph/NameScopeExtensions.cs
  24. 5 0
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  25. 139 30
      src/Perspex.SceneGraph/Visual.cs
  26. 36 0
      src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs
  27. 0 1
      src/Perspex.Styling/Perspex.Styling.csproj
  28. 1 1
      tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs
  29. 4 3
      tests/Perspex.Controls.UnitTests/Primitives/ScrollBarTests.cs
  30. 23 0
      tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  31. 1 3
      tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs
  32. 1 1
      tests/Perspex.Controls.UnitTests/TestTemplatedControl.cs
  33. 18 1
      tests/Perspex.SceneGraph.UnitTests/TestRoot.cs
  34. 20 19
      tests/Perspex.SceneGraph.UnitTests/TestVisual.cs
  35. 87 0
      tests/Perspex.SceneGraph.UnitTests/VisualTests.cs

+ 6 - 6
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)
                 {

+ 0 - 6
src/Perspex.Controls/Carousel.cs

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

+ 2 - 2
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");
         }
     }
 }

+ 2 - 34
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>();
 

+ 2 - 2
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;
         }
 

+ 2 - 2
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>();
         }
     }

+ 2 - 2
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>

+ 8 - 14
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();
         }
 

+ 3 - 3
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;

+ 7 - 13
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)
             {

+ 7 - 13
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;
         }
 

+ 2 - 2
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)
             {

+ 5 - 2
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)
         {
         }
 

+ 2 - 2
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);
         }
 

+ 0 - 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;

+ 2 - 2
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);
         }
 

+ 2 - 2
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.");

+ 2 - 2
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();
         }
 

+ 4 - 4
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 - 0
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);
+    }
+}

+ 7 - 1
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 - 0
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 - 0
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 - 0
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" />

+ 139 - 30
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 - 0
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; }
+    }
+}

+ 0 - 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" />

+ 1 - 1
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;
 

+ 4 - 3
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 - 0
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()
         {

+ 1 - 3
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]

+ 1 - 1
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;
         }

+ 18 - 1
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);
+        }
     }
 }

+ 20 - 19
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 - 0
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"));
+        }
     }
 }