Browse Source

Merge branch 'master' into feature-devtool-search-regex

pull/4529/head
Luis v.d.Eltz 6 years ago
committed by GitHub
parent
commit
f0525e11f5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 100
      src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs
  2. 59
      src/Avalonia.Themes.Default/TextBox.xaml
  3. 89
      tests/Avalonia.Controls.UnitTests/Utils/CollectionChangedEventManagerTests.cs

100
src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

@ -17,12 +17,12 @@ namespace Avalonia.Controls.Utils
void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e);
}
internal class CollectionChangedEventManager : IWeakSubscriber<NotifyCollectionChangedEventArgs>
internal class CollectionChangedEventManager
{
public static CollectionChangedEventManager Instance { get; } = new CollectionChangedEventManager();
private ConditionalWeakTable<INotifyCollectionChanged, List<WeakReference<ICollectionChangedListener>>> _entries =
new ConditionalWeakTable<INotifyCollectionChanged, List<WeakReference<ICollectionChangedListener>>>();
private ConditionalWeakTable<INotifyCollectionChanged, Entry> _entries =
new ConditionalWeakTable<INotifyCollectionChanged, Entry>();
private CollectionChangedEventManager()
{
@ -34,17 +34,13 @@ namespace Avalonia.Controls.Utils
listener = listener ?? throw new ArgumentNullException(nameof(listener));
Dispatcher.UIThread.VerifyAccess();
if (!_entries.TryGetValue(collection, out var listeners))
if (!_entries.TryGetValue(collection, out var entry))
{
listeners = new List<WeakReference<ICollectionChangedListener>>();
_entries.Add(collection, listeners);
WeakSubscriptionManager.Subscribe(
collection,
nameof(INotifyCollectionChanged.CollectionChanged),
this);
entry = new Entry(collection);
_entries.Add(collection, entry);
}
foreach (var l in listeners)
foreach (var l in entry.Listeners)
{
if (l.TryGetTarget(out var target) && target == listener)
{
@ -53,7 +49,7 @@ namespace Avalonia.Controls.Utils
}
}
listeners.Add(new WeakReference<ICollectionChangedListener>(listener));
entry.Listeners.Add(new WeakReference<ICollectionChangedListener>(listener));
}
public void RemoveListener(INotifyCollectionChanged collection, ICollectionChangedListener listener)
@ -62,8 +58,10 @@ namespace Avalonia.Controls.Utils
listener = listener ?? throw new ArgumentNullException(nameof(listener));
Dispatcher.UIThread.VerifyAccess();
if (_entries.TryGetValue(collection, out var listeners))
if (_entries.TryGetValue(collection, out var entry))
{
var listeners = entry.Listeners;
for (var i = 0; i < listeners.Count; ++i)
{
if (listeners[i].TryGetTarget(out var target) && target == listener)
@ -72,10 +70,7 @@ namespace Avalonia.Controls.Utils
if (listeners.Count == 0)
{
WeakSubscriptionManager.Unsubscribe(
collection,
nameof(INotifyCollectionChanged.CollectionChanged),
this);
entry.Dispose();
_entries.Remove(collection);
}
@ -88,51 +83,72 @@ namespace Avalonia.Controls.Utils
"Collection listener not registered for this collection/listener combination.");
}
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e)
private class Entry : IWeakSubscriber<NotifyCollectionChangedEventArgs>, IDisposable
{
static void Notify(
INotifyCollectionChanged incc,
NotifyCollectionChangedEventArgs args,
List<WeakReference<ICollectionChangedListener>> listeners)
private INotifyCollectionChanged _collection;
public Entry(INotifyCollectionChanged collection)
{
foreach (var l in listeners)
_collection = collection;
Listeners = new List<WeakReference<ICollectionChangedListener>>();
WeakSubscriptionManager.Subscribe(
_collection,
nameof(INotifyCollectionChanged.CollectionChanged),
this);
}
public List<WeakReference<ICollectionChangedListener>> Listeners { get; }
public void Dispose()
{
WeakSubscriptionManager.Unsubscribe(
_collection,
nameof(INotifyCollectionChanged.CollectionChanged),
this);
}
void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object sender, NotifyCollectionChangedEventArgs e)
{
static void Notify(
INotifyCollectionChanged incc,
NotifyCollectionChangedEventArgs args,
List<WeakReference<ICollectionChangedListener>> listeners)
{
if (l.TryGetTarget(out var target))
foreach (var l in listeners)
{
target.PreChanged(incc, args);
if (l.TryGetTarget(out var target))
{
target.PreChanged(incc, args);
}
}
}
foreach (var l in listeners)
{
if (l.TryGetTarget(out var target))
foreach (var l in listeners)
{
target.Changed(incc, args);
if (l.TryGetTarget(out var target))
{
target.Changed(incc, args);
}
}
}
foreach (var l in listeners)
{
if (l.TryGetTarget(out var target))
foreach (var l in listeners)
{
target.PostChanged(incc, args);
if (l.TryGetTarget(out var target))
{
target.PostChanged(incc, args);
}
}
}
}
if (sender is INotifyCollectionChanged incc && _entries.TryGetValue(incc, out var listeners))
{
var l = listeners.ToList();
var l = Listeners.ToList();
if (Dispatcher.UIThread.CheckAccess())
{
Notify(incc, e, l);
Notify(_collection, e, l);
}
else
{
var inccCapture = incc;
var eCapture = e;
Dispatcher.UIThread.Post(() => Notify(inccCapture, eCapture, l));
Dispatcher.UIThread.Post(() => Notify(_collection, eCapture, l));
}
}
}

59
src/Avalonia.Themes.Default/TextBox.xaml

@ -45,34 +45,35 @@
</TextBlock>
<DataValidationErrors>
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
<TextBlock Name="watermark"
Opacity="0.5"
Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
Grid.Column="1" Grid.ColumnSpan="1"/>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}"
SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"
Grid.Column="1" Grid.ColumnSpan="1"/>
<ContentPresenter Grid.Column="2" Grid.ColumnSpan="1" Content="{TemplateBinding InnerRightContent}"/>
</Grid>
</ScrollViewer>
<Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
<ScrollViewer Grid.Column="1" Grid.ColumnSpan="1"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Panel>
<TextBlock Name="watermark"
Opacity="0.5"
Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}"
SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}" />
</Panel>
</ScrollViewer>
<ContentPresenter Grid.Column="2" Grid.ColumnSpan="1" Content="{TemplateBinding InnerRightContent}"/>
</Grid>
</DataValidationErrors>
</DockPanel>
</Border>
@ -129,7 +130,7 @@
<Style Selector="TextBox.revealPasswordButton[AcceptsReturn=False][IsReadOnly=False]:not(TextBox:empty)">
<Setter Property="InnerRightContent">
<Template>
<Panel Margin="0,0,4,0">
<Panel Margin="4,0">
<Panel.Styles>
<Style Selector="ToggleButton[IsChecked=True]">
<Setter Property="(ToolTip.Tip)" Value="Hide Password" />

89
tests/Avalonia.Controls.UnitTests/Utils/CollectionChangedEventManagerTests.cs

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using Avalonia.Collections;
using Avalonia.Controls.Utils;
using Xunit;
using CollectionChangedEventManager = Avalonia.Controls.Utils.CollectionChangedEventManager;
namespace Avalonia.Controls.UnitTests.Utils
{
public class CollectionChangedEventManagerTests
{
[Fact]
public void AddListener_Listens_To_Events()
{
var source = new AvaloniaList<string>();
var listener = new Listener();
CollectionChangedEventManager.Instance.AddListener(source, listener);
Assert.Empty(listener.Received);
source.Add("foo");
Assert.Equal(1, listener.Received.Count);
}
[Fact]
public void RemoveListener_Stops_Listening_To_Events()
{
var source = new AvaloniaList<string>();
var listener = new Listener();
CollectionChangedEventManager.Instance.AddListener(source, listener);
CollectionChangedEventManager.Instance.RemoveListener(source, listener);
source.Add("foo");
Assert.Empty(listener.Received);
}
[Fact]
public void Receives_Events_From_Wrapped_Collection()
{
var source = new WrappingCollection();
var listener = new Listener();
CollectionChangedEventManager.Instance.AddListener(source, listener);
Assert.Empty(listener.Received);
source.Add("foo");
Assert.Equal(1, listener.Received.Count);
}
private class Listener : ICollectionChangedListener
{
public List<NotifyCollectionChangedEventArgs> Received { get; } = new List<NotifyCollectionChangedEventArgs>();
public void Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
Received.Add(e);
}
public void PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
}
public void PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
}
}
private class WrappingCollection : INotifyCollectionChanged
{
private AvaloniaList<string> _inner = new AvaloniaList<string>();
public void Add(string s) => _inner.Add(s);
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _inner.CollectionChanged += value;
remove => _inner.CollectionChanged -= value;
}
}
}
}
Loading…
Cancel
Save