Browse Source

Merge pull request #843 from AvaloniaUI/fixes/829-findcontrol-returns-null-for-usercontrol

Register namescoped controls with parent namescope.
Steven Kirk 9 years ago
parent
commit
0e1877cc21

+ 17 - 0
src/Avalonia.Controls/Control.cs

@@ -671,6 +671,23 @@ namespace Avalonia.Controls
             if (Name != null)
             {
                 _nameScope?.Register(Name, this);
+
+                var visualParent = Parent as Visual;
+
+                if (this is INameScope && visualParent != null)
+                {
+                    // If we have e.g. a named UserControl in a window then we want that control
+                    // to be findable by name from the Window, so register with both name scopes.
+                    // This differs from WPF's behavior in that XAML manually registers controls 
+                    // with name scopes based on the XAML file in which the name attribute appears,
+                    // but we're trying to avoid XAML magic in Avalonia in order to made code-
+                    // created UIs easy. This will cause problems if a UserControl declares a name
+                    // in its XAML and that control is included multiple times in a parent control
+                    // (as the name will be duplicated), however at the moment I'm fine with saying
+                    // "don't do that".
+                    var parentNameScope = NameScope.FindNameScope(visualParent);
+                    parentNameScope?.Register(Name, this);
+                }
             }
         }
 

+ 31 - 0
src/Avalonia.Styling/Controls/NameScope.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Controls
 {
@@ -29,6 +30,32 @@ namespace Avalonia.Controls
         /// </summary>
         public event EventHandler<NameScopeEventArgs> Unregistered;
 
+        /// <summary>
+        /// Finds the containing name scope for a visual.
+        /// </summary>
+        /// <param name="visual">The visual.</param>
+        /// <returns>The containing name scope.</returns>
+        public static INameScope FindNameScope(Visual visual)
+        {
+            Contract.Requires<ArgumentNullException>(visual != null);
+
+            INameScope result;
+
+            while (visual != null)
+            {
+                result = visual as INameScope ?? GetNameScope(visual);
+
+                if (result != null)
+                {
+                    return result;
+                }
+
+                visual = (visual as ILogical).LogicalParent as Visual;
+            }
+
+            return null;
+        }
+
         /// <summary>
         /// Gets the value of the attached <see cref="NameScopeProperty"/> on a visual.
         /// </summary>
@@ -36,6 +63,8 @@ namespace Avalonia.Controls
         /// <returns>The value of the NameScope attached property.</returns>
         public static INameScope GetNameScope(Visual visual)
         {
+            Contract.Requires<ArgumentNullException>(visual != null);
+
             return visual.GetValue(NameScopeProperty);
         }
 
@@ -46,6 +75,8 @@ namespace Avalonia.Controls
         /// <param name="value">The value to set.</param>
         public static void SetNameScope(Visual visual, INameScope value)
         {
+            Contract.Requires<ArgumentNullException>(visual != null);
+
             visual.SetValue(NameScopeProperty, value);
         }
 

+ 18 - 0
tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs

@@ -70,5 +70,23 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.Null(NameScope.GetNameScope((Control)root.Presenter).Find("foo"));
         }
+
+        [Fact]
+        public void Control_That_Is_NameScope_Should_Register_With_Parent_NameScope()
+        {
+            UserControl userControl;
+            var root = new TestTemplatedRoot
+            {
+                Content = userControl = new UserControl
+                {
+                    Name = "foo",
+                }
+            };
+
+            root.ApplyTemplate();
+
+            Assert.Same(userControl, root.FindControl<UserControl>("foo"));
+            Assert.Same(userControl, userControl.FindControl<UserControl>("foo"));
+        }
     }
 }