Browse Source

Register templated controls with name scope.

Usually controls are only registered with their name scope when they get
added to a rooted visual tree, but this causes problems for tests, and
would mean the templated control wouldn't work if the client called
ApplyTemplate themselves before the control is added to the visual tree.
Work around this by explicitly registering template controls with the
name scope when the template is applied.

To allow this, we need to allow controls to be registered with a name
scope multiple times.
Steven Kirk 10 years ago
parent
commit
3307ef8712

+ 14 - 5
src/Perspex.Controls/Primitives/TemplatedControl.cs

@@ -202,12 +202,12 @@ namespace Perspex.Controls.Primitives
                     // before the controls are added to the visual tree so that the logical
                     // tree can be set up before styling is applied.
                     ((ISetLogicalParent)child).SetParent(this);
-                    SetTemplatedParentAndApplyChildTemplates(child);
+                    SetupTemplateControls(child, nameScope);
 
                     // And again after the controls are added to the visual tree, and have their
                     // styling and thus Template property set.
                     AddVisualChild((Visual)child);
-                    SetTemplatedParentAndApplyChildTemplates(child);
+                    SetupTemplateControls(child, nameScope);
 
                     OnTemplateApplied(nameScope);
                 }
@@ -226,14 +226,23 @@ namespace Perspex.Controls.Primitives
 
         /// <summary>
         /// Sets the TemplatedParent property for a control created from the control template and
-        /// applies the templates of nested templated controls.
+        /// applies the templates of nested templated controls. Also adds each control to its name
+        /// scope if it has a name.
         /// </summary>
         /// <param name="control">The control.</param>
-        private void SetTemplatedParentAndApplyChildTemplates(IControl control)
+        /// <param name="nameScope">The name scope.</param>
+        private void SetupTemplateControls(IControl control, INameScope nameScope)
         {
+            // If control.TemplatedParent is null at this point, then the control is our templated
+            // child so set its TemplatedParent and register it with its name scope.
             if (control.TemplatedParent == null)
             {
                 control.SetValue(TemplatedParentProperty, this);
+
+                if (control.Name != null)
+                {
+                    nameScope.Register(control.Name, control);
+                }
             }
 
             control.ApplyTemplate();
@@ -242,7 +251,7 @@ namespace Perspex.Controls.Primitives
             {
                 foreach (IControl child in control.GetVisualChildren())
                 {
-                    SetTemplatedParentAndApplyChildTemplates(child);
+                    SetupTemplateControls(child, nameScope);
                 }
             }
         }

+ 13 - 1
src/Perspex.SceneGraph/NameScope.cs

@@ -49,7 +49,19 @@ namespace Perspex
             Contract.Requires<ArgumentNullException>(name != null);
             Contract.Requires<ArgumentNullException>(element != null);
 
-            _inner.Add(name, element);
+            object existing;
+
+            if (_inner.TryGetValue(name, out existing))
+            {
+                if (existing != element)
+                {
+                    throw new ArgumentException($"Control with the name '{name}' already registered.");
+                }
+            }
+            else
+            {
+                _inner.Add(name, element);
+            }
         }
 
         /// <summary>

+ 2 - 1
src/Perspex.Themes.Default/ScrollViewer.paml

@@ -2,7 +2,8 @@
   <Setter Property="Template">
     <ControlTemplate>
       <Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
-        <ScrollContentPresenter Content="{TemplateBinding Content}"
+        <ScrollContentPresenter Name="PART_ContentPresenter"
+                                Content="{TemplateBinding Content}"
                                 Extent="{TemplateBinding Path=Extent, Mode=TwoWay}"
                                 Offset="{TemplateBinding Path=Offset, Mode=TwoWay}"
                                 Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}"

+ 1 - 1
tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs

@@ -63,7 +63,7 @@ namespace Perspex.Controls.UnitTests
                 {
                     new ScrollContentPresenter
                     {
-                        Name = "contentPresenter",
+                        Name = "PART_ContentPresenter",
                         [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
                         [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
                         [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],

+ 16 - 16
tests/Perspex.Controls.UnitTests/TabControlTests.cs

@@ -97,23 +97,23 @@ namespace Perspex.Controls.UnitTests
         public void Removal_Should_Set_Next_Tab()
         {
             var collection = new ObservableCollection<TabItem>()
+            {
+                new TabItem
                 {
-                    new TabItem
-                    {
-                        Name = "first",
-                        Content = "foo",
-                    },
-                    new TabItem
-                    {
-                        Name = "second",
-                        Content = "bar",
-                    },
-                    new TabItem
-                    {
-                        Name = "3rd",
-                        Content = "barf",
-                    },
-                };
+                    Name = "first",
+                    Content = "foo",
+                },
+                new TabItem
+                {
+                    Name = "second",
+                    Content = "bar",
+                },
+                new TabItem
+                {
+                    Name = "3rd",
+                    Content = "barf",
+                },
+            };
 
             var target = new TabControl
             {

+ 55 - 0
tests/Perspex.SceneGraph.UnitTests/NameScopeTests.cs

@@ -0,0 +1,55 @@
+// 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 Xunit;
+
+namespace Perspex.SceneGraph.UnitTests
+{
+    public class NameScopeTests
+    {
+        [Fact]
+        public void Register_Registers_Element()
+        {
+            var target = new NameScope();
+            var element = new object();
+
+            target.Register("foo", element);
+
+            Assert.Same(element, target.Find("foo"));
+        }
+
+        [Fact]
+        public void Unregister_Unregisters_Element()
+        {
+            var target = new NameScope();
+            var element = new object();
+
+            target.Register("foo", element);
+            target.Unregister("foo");
+
+            Assert.Null(target.Find("foo"));
+        }
+
+        [Fact]
+        public void Cannot_Register_New_Element_With_Existing_Name()
+        {
+            var target = new NameScope();
+
+            target.Register("foo", new object());
+            Assert.Throws<ArgumentException>(() => target.Register("foo", new object()));
+        }
+
+        [Fact]
+        public void Can_Register_Same_Element_More_Than_Once()
+        {
+            var target = new NameScope();
+            var element = new object();
+
+            target.Register("foo", element);
+            target.Register("foo", element);
+
+            Assert.Same(element, target.Find("foo"));
+        }
+    }
+}

+ 1 - 0
tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj

@@ -72,6 +72,7 @@
     <Otherwise />
   </Choose>
   <ItemGroup>
+    <Compile Include="NameScopeTests.cs" />
     <Compile Include="ThicknessTests.cs" />
     <Compile Include="Media\BrushTests.cs" />
     <Compile Include="Media\ColorTests.cs" />