Browse Source

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.
pull/843/head
Steven Kirk 9 years ago
parent
commit
90f4cfbea3
  1. 17
      src/Avalonia.Controls/Control.cs
  2. 31
      src/Avalonia.Styling/Controls/NameScope.cs
  3. 18
      tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs

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

Loading…
Cancel
Save