11 changed files with 275 additions and 366 deletions
@ -1,5 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> |
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> |
||||
|
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,54 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
public class ChildNameScope : INameScope |
||||
|
{ |
||||
|
private readonly INameScope _parentScope; |
||||
|
private readonly NameScope _inner = new NameScope(); |
||||
|
|
||||
|
public ChildNameScope(INameScope parentScope) |
||||
|
{ |
||||
|
_parentScope = parentScope; |
||||
|
} |
||||
|
|
||||
|
public void Register(string name, object element) => _inner.Register(name, element); |
||||
|
|
||||
|
public ValueTask<object> FindAsync(string name) |
||||
|
{ |
||||
|
var found = Find(name); |
||||
|
if (found != null) |
||||
|
return new ValueTask<object>(found); |
||||
|
// Not found and both current and parent scope are in completed stage
|
||||
|
if(IsCompleted) |
||||
|
return new ValueTask<object>(null); |
||||
|
return DoFindAsync(name); |
||||
|
} |
||||
|
|
||||
|
async ValueTask<object> DoFindAsync(string name) |
||||
|
{ |
||||
|
if (!_inner.IsCompleted) |
||||
|
{ |
||||
|
var found = await _inner.FindAsync(name); |
||||
|
if (found != null) |
||||
|
return found; |
||||
|
} |
||||
|
|
||||
|
return await _parentScope.FindAsync(name); |
||||
|
} |
||||
|
|
||||
|
public object Find(string name) |
||||
|
{ |
||||
|
var found = _inner.Find(name); |
||||
|
if (found != null) |
||||
|
return found; |
||||
|
if (_inner.IsCompleted) |
||||
|
return _parentScope.Find(name); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public void Complete() => _inner.Complete(); |
||||
|
|
||||
|
public bool IsCompleted => _inner.IsCompleted && _parentScope.IsCompleted; |
||||
|
} |
||||
|
} |
||||
@ -1,221 +0,0 @@ |
|||||
// Copyright (c) The Avalonia 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; |
|
||||
using System.Reactive.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.LogicalTree; |
|
||||
using Avalonia.UnitTests; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Styling.UnitTests |
|
||||
{ |
|
||||
public class ControlLocatorTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public async Task Track_By_Name_Should_Find_Control_Added_Earlier() |
|
||||
{ |
|
||||
TextBlock target; |
|
||||
TextBlock relativeTo; |
|
||||
|
|
||||
var root = new TestRoot |
|
||||
{ |
|
||||
Child = new StackPanel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
(target = new TextBlock { Name = "target" }), |
|
||||
(relativeTo = new TextBlock { Name = "start" }), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
var scope = Register(root, relativeTo); |
|
||||
Register(root, target); |
|
||||
|
|
||||
var locator = NameScopeLocator.Track(scope, "target"); |
|
||||
var result = await locator.Take(1); |
|
||||
|
|
||||
Assert.Same(target, result); |
|
||||
Assert.Equal(0, scope.RegisteredSubscribers); |
|
||||
Assert.Equal(0, scope.UnregisteredSubscribers); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
|
|
||||
[Fact] |
|
||||
public void Track_By_Name_Should_Find_Control_Added_Later() |
|
||||
{ |
|
||||
StackPanel panel; |
|
||||
TextBlock relativeTo; |
|
||||
|
|
||||
var root = new TestRoot |
|
||||
{ |
|
||||
Child = (panel = new StackPanel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
(relativeTo = new TextBlock |
|
||||
{ |
|
||||
Name = "start" |
|
||||
}), |
|
||||
} |
|
||||
}) |
|
||||
}; |
|
||||
var scope = Register(root, relativeTo); |
|
||||
|
|
||||
var locator = NameScopeLocator.Track(scope, "target"); |
|
||||
var target = new TextBlock { Name = "target" }; |
|
||||
var result = new List<ILogical>(); |
|
||||
|
|
||||
using (locator.Subscribe(x => result.Add((ILogical)x))) |
|
||||
{ |
|
||||
panel.Children.Add(target); |
|
||||
Register(root, target); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { null, target }, result); |
|
||||
Assert.Equal(0, scope.RegisteredSubscribers); |
|
||||
Assert.Equal(0, scope.UnregisteredSubscribers); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Track_By_Name_Should_Track_Removal_And_Readd() |
|
||||
{ |
|
||||
StackPanel panel; |
|
||||
TextBlock target; |
|
||||
TextBlock relativeTo; |
|
||||
|
|
||||
var root = new TestRoot |
|
||||
{ |
|
||||
Child = panel = new StackPanel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
(target = new TextBlock { Name = "target" }), |
|
||||
(relativeTo = new TextBlock { Name = "start" }), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
var scope = Register(root, target); |
|
||||
Register(root, relativeTo); |
|
||||
|
|
||||
var locator = NameScopeLocator.Track(scope, "target"); |
|
||||
var result = new List<ILogical>(); |
|
||||
locator.Subscribe(x => result.Add((IControl)x)); |
|
||||
|
|
||||
var other = new TextBlock { Name = "target" }; |
|
||||
panel.Children.Remove(target); |
|
||||
scope.Unregister(target.Name); |
|
||||
panel.Children.Add(other); |
|
||||
Register(root, other); |
|
||||
|
|
||||
Assert.Equal(new[] { target, null, other }, result); |
|
||||
} |
|
||||
|
|
||||
[Fact(Skip = "I'm going to remove that logic anyway")] |
|
||||
public void Track_By_Name_Should_Find_Control_When_Tree_Changed() |
|
||||
{ |
|
||||
TextBlock target1; |
|
||||
TextBlock target2; |
|
||||
TextBlock relativeTo; |
|
||||
|
|
||||
var root1 = new TestRoot |
|
||||
{ |
|
||||
Child = new StackPanel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
(relativeTo = new TextBlock |
|
||||
{ |
|
||||
Name = "start" |
|
||||
}), |
|
||||
(target1 = new TextBlock { Name = "target" }), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
var scope1 = Register(root1, relativeTo); |
|
||||
Register(root1, relativeTo); |
|
||||
Register(root1, target1); |
|
||||
|
|
||||
var root2 = new TestRoot |
|
||||
{ |
|
||||
Child = new StackPanel |
|
||||
{ |
|
||||
Children = |
|
||||
{ |
|
||||
(target2 = new TextBlock { Name = "target" }), |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
var scope2 = Register(root2, target2); |
|
||||
|
|
||||
var locator = NameScopeLocator.Track(scope1, "target"); |
|
||||
var result = new List<ILogical>(); |
|
||||
|
|
||||
using (locator.Subscribe(x => result.Add((ILogical)x))) |
|
||||
{ |
|
||||
((StackPanel)root1.Child).Children.Remove(relativeTo); |
|
||||
scope1.Unregister(relativeTo.Name); |
|
||||
((StackPanel)root2.Child).Children.Add(relativeTo); |
|
||||
Register(root2, relativeTo); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { target1, null, target2 }, result); |
|
||||
Assert.Equal(0, scope1.RegisteredSubscribers); |
|
||||
Assert.Equal(0, scope1.UnregisteredSubscribers); |
|
||||
Assert.Equal(0, scope2.RegisteredSubscribers); |
|
||||
Assert.Equal(0, scope2.UnregisteredSubscribers); |
|
||||
} |
|
||||
|
|
||||
TrackingNameScope Register(StyledElement anchor, StyledElement element) |
|
||||
{ |
|
||||
var scope = (TrackingNameScope)NameScope.GetNameScope(anchor); |
|
||||
if (scope == null) |
|
||||
NameScope.SetNameScope(anchor, scope = new TrackingNameScope()); |
|
||||
scope.Register(element.Name, element); |
|
||||
return scope; |
|
||||
} |
|
||||
|
|
||||
class TrackingNameScope : INameScope |
|
||||
{ |
|
||||
public int RegisteredSubscribers { get; private set; } |
|
||||
public int UnregisteredSubscribers { get; private set; } |
|
||||
private NameScope _inner = new NameScope(); |
|
||||
public event EventHandler<NameScopeEventArgs> Registered |
|
||||
{ |
|
||||
add |
|
||||
{ |
|
||||
_inner.Registered += value; |
|
||||
RegisteredSubscribers++; |
|
||||
} |
|
||||
remove |
|
||||
{ |
|
||||
_inner.Registered -= value; |
|
||||
RegisteredSubscribers--; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public event EventHandler<NameScopeEventArgs> Unregistered |
|
||||
{ |
|
||||
add |
|
||||
{ |
|
||||
_inner.Unregistered += value; |
|
||||
UnregisteredSubscribers++; |
|
||||
} |
|
||||
remove |
|
||||
{ |
|
||||
_inner.Unregistered -= value; |
|
||||
UnregisteredSubscribers--; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Register(string name, object element) => _inner.Register(name, element); |
|
||||
|
|
||||
public object Find(string name) => _inner.Find(name); |
|
||||
|
|
||||
public void Unregister(string name) => _inner.Unregister(name); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue