6 changed files with 315 additions and 5 deletions
@ -0,0 +1,60 @@ |
|||
// 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 System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Perspex.Controls; |
|||
|
|||
namespace Perspex.Markup.Data |
|||
{ |
|||
/// <summary>
|
|||
/// Locates controls relative to other controls.
|
|||
/// </summary>
|
|||
public static class ControlLocator |
|||
{ |
|||
/// <summary>
|
|||
/// Tracks a named control relative to another control.
|
|||
/// </summary>
|
|||
/// <param name="relativeTo">
|
|||
/// The control relative from which the other control should be found.
|
|||
/// </param>
|
|||
/// <param name="name">The name of the control to find.</param>
|
|||
public static IObservable<IControl> Track(IControl relativeTo, string name) |
|||
{ |
|||
var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>( |
|||
x => relativeTo.AttachedToVisualTree += x, |
|||
x => relativeTo.DetachedFromVisualTree += x) |
|||
.Select(x => x.EventArgs.NameScope) |
|||
.StartWith(relativeTo.FindNameScope()); |
|||
|
|||
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>( |
|||
x => relativeTo.DetachedFromVisualTree += x, |
|||
x => relativeTo.DetachedFromVisualTree += x) |
|||
.Select(x => (INameScope)null); |
|||
|
|||
return attached.Merge(detached).Select(nameScope => |
|||
{ |
|||
if (nameScope != null) |
|||
{ |
|||
var registered = Observable.FromEventPattern<NameScopeEventArgs>( |
|||
x => nameScope.Registered += x, |
|||
x => nameScope.Registered -= x) |
|||
.Where(x => x.EventArgs.Name == name) |
|||
.Select(x => x.EventArgs.Element) |
|||
.OfType<IControl>(); |
|||
var unregistered = Observable.FromEventPattern<NameScopeEventArgs>( |
|||
x => nameScope.Unregistered += x, |
|||
x => nameScope.Unregistered -= x); |
|||
return registered |
|||
.StartWith(nameScope.Find<IControl>(name)) |
|||
.TakeUntil(unregistered); |
|||
} |
|||
else |
|||
{ |
|||
return Observable.Return<IControl>(null); |
|||
} |
|||
}).Switch(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
// 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 System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using Perspex.Controls; |
|||
using Perspex.Markup.Data; |
|||
using Xunit; |
|||
|
|||
namespace Perspex.Markup.UnitTests.Data |
|||
{ |
|||
public class ControlLocatorTests |
|||
{ |
|||
[Fact] |
|||
public async void Track_By_Name_Should_Find_Control_Added_Earlier() |
|||
{ |
|||
TextBlock target; |
|||
TextBlock relativeTo; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = new StackPanel |
|||
{ |
|||
Children = new Controls.Controls |
|||
{ |
|||
(target = new TextBlock { Name = "target" }), |
|||
(relativeTo = new TextBlock { Name = "start" }), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var locator = ControlLocator.Track(relativeTo, "target"); |
|||
var result = await locator.Take(1); |
|||
|
|||
Assert.Same(target, result); |
|||
Assert.Equal(0, root.NameScopeRegisteredSubscribers); |
|||
Assert.Equal(0, root.NameScopeUnregisteredSubscribers); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Track_By_Name_Should_Find_Control_Added_Later() |
|||
{ |
|||
StackPanel panel; |
|||
TextBlock relativeTo; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = (panel = new StackPanel |
|||
{ |
|||
Children = new Controls.Controls |
|||
{ |
|||
(relativeTo = new TextBlock |
|||
{ |
|||
Name = "start" |
|||
}), |
|||
} |
|||
}) |
|||
}; |
|||
|
|||
var locator = ControlLocator.Track(relativeTo, "target"); |
|||
var target = new TextBlock { Name = "target" }; |
|||
var result = new List<IControl>(); |
|||
|
|||
using (locator.Subscribe(x => result.Add(x))) |
|||
{ |
|||
panel.Children.Add(target); |
|||
} |
|||
|
|||
Assert.Equal(new[] { null, target }, result); |
|||
Assert.Equal(0, root.NameScopeRegisteredSubscribers); |
|||
Assert.Equal(0, root.NameScopeUnregisteredSubscribers); |
|||
} |
|||
|
|||
[Fact] |
|||
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 = new Controls.Controls |
|||
{ |
|||
(relativeTo = new TextBlock |
|||
{ |
|||
Name = "start" |
|||
}), |
|||
(target1 = new TextBlock { Name = "target" }), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var root2 = new TestRoot |
|||
{ |
|||
Child = new StackPanel |
|||
{ |
|||
Children = new Controls.Controls |
|||
{ |
|||
(target2 = new TextBlock { Name = "target" }), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var locator = ControlLocator.Track(relativeTo, "target"); |
|||
var target = new TextBlock { Name = "target" }; |
|||
var result = new List<IControl>(); |
|||
|
|||
using (locator.Subscribe(x => result.Add(x))) |
|||
{ |
|||
((StackPanel)root1.Child).Children.Remove(relativeTo); |
|||
((StackPanel)root2.Child).Children.Add(relativeTo); |
|||
} |
|||
|
|||
Assert.Equal(new[] { target1, null, target2 }, result); |
|||
Assert.Equal(0, root1.NameScopeRegisteredSubscribers); |
|||
Assert.Equal(0, root1.NameScopeUnregisteredSubscribers); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// 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 Perspex.Controls; |
|||
using Perspex.Platform; |
|||
using Perspex.Rendering; |
|||
|
|||
namespace Perspex.Markup.UnitTests |
|||
{ |
|||
public class TestRoot : Decorator, IRenderRoot, INameScope |
|||
{ |
|||
private NameScope _nameScope = new NameScope(); |
|||
|
|||
event EventHandler<NameScopeEventArgs> INameScope.Registered |
|||
{ |
|||
add { _nameScope.Registered += value; ++NameScopeRegisteredSubscribers; } |
|||
remove { _nameScope.Registered -= value; --NameScopeRegisteredSubscribers; } |
|||
} |
|||
|
|||
public event EventHandler<NameScopeEventArgs> Unregistered |
|||
{ |
|||
add { _nameScope.Unregistered += value; ++NameScopeUnregisteredSubscribers; } |
|||
remove { _nameScope.Unregistered -= value; --NameScopeUnregisteredSubscribers; } |
|||
} |
|||
|
|||
public int NameScopeRegisteredSubscribers { get; private set; } |
|||
|
|||
public int NameScopeUnregisteredSubscribers { get; private set; } |
|||
|
|||
public IRenderTarget RenderTarget |
|||
{ |
|||
get { throw new NotImplementedException(); } |
|||
} |
|||
|
|||
public IRenderQueueManager RenderQueueManager |
|||
{ |
|||
get { throw new NotImplementedException(); } |
|||
} |
|||
|
|||
public Point TranslatePointToScreen(Point p) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
public void Register(string name, object element) |
|||
{ |
|||
_nameScope.Register(name, element); |
|||
} |
|||
|
|||
public object Find(string name) |
|||
{ |
|||
return _nameScope.Find(name); |
|||
} |
|||
|
|||
public void Unregister(string name) |
|||
{ |
|||
_nameScope.Unregister(name); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue