From 90f4cfbea3977963e61b35b7907ac7cf8c1cfade Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 8 Jan 2017 01:44:43 +0100 Subject: [PATCH] Register namescoped controls with parent namescope. 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". Fixes #829. --- src/Avalonia.Controls/Control.cs | 17 ++++++++++ src/Avalonia.Styling/Controls/NameScope.cs | 31 +++++++++++++++++++ .../ControlTests_NameScope.cs | 18 +++++++++++ 3 files changed, 66 insertions(+) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 5cd2ddfc35..ef253a28e2 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/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); + } } } diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index ddfb3ea173..4c5875479e 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/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 /// public event EventHandler Unregistered; + /// + /// Finds the containing name scope for a visual. + /// + /// The visual. + /// The containing name scope. + public static INameScope FindNameScope(Visual visual) + { + Contract.Requires(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; + } + /// /// Gets the value of the attached on a visual. /// @@ -36,6 +63,8 @@ namespace Avalonia.Controls /// The value of the NameScope attached property. public static INameScope GetNameScope(Visual visual) { + Contract.Requires(visual != null); + return visual.GetValue(NameScopeProperty); } @@ -46,6 +75,8 @@ namespace Avalonia.Controls /// The value to set. public static void SetNameScope(Visual visual, INameScope value) { + Contract.Requires(visual != null); + visual.SetValue(NameScopeProperty, value); } diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs index ec75c2390b..9f39f7a47a 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs +++ b/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("foo")); + Assert.Same(userControl, userControl.FindControl("foo")); + } } }