Browse Source

Merge branch 'master' into feature/skia-api-lease

pull/8656/head
Max Katz 4 years ago
committed by GitHub
parent
commit
2fd6e542b7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 56
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  2. 5
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  3. 8
      src/Avalonia.Base/Controls/Templates/ITemplateResult.cs
  4. 3
      src/Avalonia.Base/Controls/Templates/TemplateResult.cs
  5. 2
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  6. 13
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  7. 3
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  8. 17
      src/Avalonia.Controls/Converters/StringFormatConverter.cs
  9. 3
      src/Avalonia.Controls/ListBox.cs
  10. 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  11. 2
      src/Avalonia.Controls/TreeView.cs
  12. 30
      src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
  13. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  14. 90
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs
  15. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  16. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  17. 5
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  18. 49
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  19. 133
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  20. 89
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  21. 542
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  22. 38
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  23. 43
      tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs
  24. 207
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

56
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Templates;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -29,7 +30,11 @@ namespace Avalonia.Controls
public object? this[object key] public object? this[object key]
{ {
get => _inner?[key]; get
{
TryGetValue(key, out var value);
return value;
}
set set
{ {
Inner[key] = value; Inner[key] = value;
@ -119,6 +124,12 @@ namespace Avalonia.Controls
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
} }
public void AddDeferred(object key, Func<IServiceProvider?, object?> factory)
{
Inner.Add(key, new DeferredItem(factory));
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
public void Clear() public void Clear()
{ {
if (_inner?.Count > 0) if (_inner?.Count > 0)
@ -143,10 +154,8 @@ namespace Avalonia.Controls
public bool TryGetResource(object key, out object? value) public bool TryGetResource(object key, out object? value)
{ {
if (_inner is not null && _inner.TryGetValue(key, out value)) if (TryGetValue(key, out value))
{
return true; return true;
}
if (_mergedDictionaries != null) if (_mergedDictionaries != null)
{ {
@ -165,12 +174,28 @@ namespace Avalonia.Controls
public bool TryGetValue(object key, out object? value) public bool TryGetValue(object key, out object? value)
{ {
if (_inner is not null) if (_inner is not null && _inner.TryGetValue(key, out value))
return _inner.TryGetValue(key, out value); {
if (value is DeferredItem deffered)
{
_inner[key] = value = deffered.Factory(null) switch
{
ITemplateResult t => t.Result,
object v => v,
_ => null,
};
}
return true;
}
value = null; value = null;
return false; return false;
} }
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator();
}
void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item) void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item)
{ {
@ -198,12 +223,17 @@ namespace Avalonia.Controls
return false; return false;
} }
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal bool ContainsDeferredKey(object key)
{ {
return _inner?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<object, object?>>().GetEnumerator(); if (_inner is not null && _inner.TryGetValue(key, out var result))
} {
return result is DeferredItem;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return false;
}
void IResourceProvider.AddOwner(IResourceHost owner) void IResourceProvider.AddOwner(IResourceHost owner)
{ {
@ -258,5 +288,11 @@ namespace Avalonia.Controls
} }
} }
} }
private class DeferredItem
{
public DeferredItem(Func<IServiceProvider?, object?> factory) => Factory = factory;
public Func<IServiceProvider?, object?> Factory { get; }
}
} }
} }

5
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -132,6 +132,11 @@ namespace Avalonia.Controls
{ {
_target.OwnerChanged += OwnerChanged; _target.OwnerChanged += OwnerChanged;
_owner = _target.Owner; _owner = _target.Owner;
if (_owner is object)
{
_owner.ResourcesChanged += ResourcesChanged;
}
} }
protected override void Deinitialize() protected override void Deinitialize()

8
src/Avalonia.Base/Controls/Templates/ITemplateResult.cs

@ -0,0 +1,8 @@
namespace Avalonia.Controls.Templates
{
public interface ITemplateResult
{
public object? Result { get; }
public INameScope NameScope { get; }
}
}

3
src/Avalonia.Controls/Templates/TemplateResult.cs → src/Avalonia.Base/Controls/Templates/TemplateResult.cs

@ -1,9 +1,10 @@
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
public class TemplateResult<T> public class TemplateResult<T> : ITemplateResult
{ {
public T Result { get; } public T Result { get; }
public INameScope NameScope { get; } public INameScope NameScope { get; }
object? ITemplateResult.Result => Result;
public TemplateResult(T result, INameScope nameScope) public TemplateResult(T result, INameScope nameScope)
{ {

2
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -71,7 +71,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate) if(_queuedUpdate)
return; return;
_queuedUpdate = true; _queuedUpdate = true;
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); _compositor.InvokeWhenReadyForNextCommit(_update);
} }
/// <inheritdoc/> /// <inheritdoc/>

13
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -33,6 +33,7 @@ namespace Avalonia.Rendering.Composition
internal IEasing DefaultEasing { get; } internal IEasing DefaultEasing { get; }
private List<Action>? _invokeOnNextCommit; private List<Action>? _invokeOnNextCommit;
private readonly Stack<List<Action>> _invokeListPool = new(); private readonly Stack<List<Action>> _invokeListPool = new();
private Task? _lastBatchCompleted;
/// <summary> /// <summary>
/// Creates a new compositor on a specified render loop that would use a particular GPU /// Creates a new compositor on a specified render loop that would use a particular GPU
@ -86,7 +87,7 @@ namespace Avalonia.Rendering.Composition
if (_invokeOnNextCommit != null) if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed); ScheduleCommitCallbacks(batch.Completed);
return batch.Completed; return _lastBatchCompleted = batch.Completed;
} }
async void ScheduleCommitCallbacks(Task task) async void ScheduleCommitCallbacks(Task task)
@ -139,5 +140,15 @@ namespace Avalonia.Rendering.Composition
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new(); _invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action); _invokeOnNextCommit.Add(action);
} }
public void InvokeWhenReadyForNextCommit(Action action)
{
if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted)
Dispatcher.UIThread.Post(action, DispatcherPriority.Composition);
else
_lastBatchCompleted.ContinueWith(
static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
action);
}
} }
} }

3
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -108,6 +108,7 @@ namespace Avalonia.Rendering.Composition.Server
private void RenderCore() private void RenderCore()
{ {
ApplyPendingBatches(); ApplyPendingBatches();
CompletePendingBatches();
foreach(var animation in _activeAnimations) foreach(var animation in _activeAnimations)
_animationsToUpdate.Add(animation); _animationsToUpdate.Add(animation);
@ -119,8 +120,6 @@ namespace Avalonia.Rendering.Composition.Server
foreach (var t in _activeTargets) foreach (var t in _activeTargets)
t.Render(); t.Render();
CompletePendingBatches();
} }
public void AddCompositionTarget(ServerCompositionTarget target) public void AddCompositionTarget(ServerCompositionTarget target)

17
src/Avalonia.Controls/Converters/StringFormatConverter.cs

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
namespace Avalonia.Controls.Converters; namespace Avalonia.Controls.Converters;
@ -15,13 +14,17 @@ public class StringFormatConverter : IMultiValueConverter
{ {
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture) public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{ {
try if (values[0] is string format)
{ {
return string.Format((string)values[0]!, values.Skip(1).ToArray()); try
} {
catch (Exception e) return string.Format(format, values.Skip(1).ToArray());
{ }
return new BindingNotification(e, BindingErrorType.Error); catch
{
return AvaloniaProperty.UnsetValue;
}
} }
return AvaloniaProperty.UnsetValue;
} }
} }

3
src/Avalonia.Controls/ListBox.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -157,7 +158,7 @@ namespace Avalonia.Controls
e.Source, e.Source,
true, true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasAllFlags(KeyModifiers.Control), e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>().CommandModifiers),
point.Properties.IsRightButtonPressed); point.Properties.IsRightButtonPressed);
} }
} }

2
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -187,7 +187,7 @@ namespace Avalonia.Controls.Presenters
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) || if (e.OldStartingIndex < NextIndex ||
panel.Children.Count > ItemCount) panel.Children.Count > ItemCount)
{ {
RecycleContainersOnRemove(); RecycleContainersOnRemove();

2
src/Avalonia.Controls/TreeView.cs

@ -529,7 +529,7 @@ namespace Avalonia.Controls
e.Source, e.Source,
true, true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasAllFlags(KeyModifiers.Control), e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>().CommandModifiers),
point.Properties.IsRightButtonPressed); point.Properties.IsRightButtonPressed);
} }
} }

30
src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml

@ -41,13 +41,31 @@
<StreamGeometry x:Key="ButtonSpinnerIncreaseButtonIcon">M0,9 L10,0 20,9 19,10 10,2 1,10 z</StreamGeometry> <StreamGeometry x:Key="ButtonSpinnerIncreaseButtonIcon">M0,9 L10,0 20,9 19,10 10,2 1,10 z</StreamGeometry>
<StreamGeometry x:Key="ButtonSpinnerDecreaseButtonIcon">M0,1 L10,10 20,1 19,0 10,8 1,0 z</StreamGeometry> <StreamGeometry x:Key="ButtonSpinnerDecreaseButtonIcon">M0,1 L10,10 20,1 19,0 10,8 1,0 z</StreamGeometry>
<ControlTheme x:Key="FluentButtonSpinnerRepeatButton" TargetType="RepeatButton" BasedOn="{StaticResource {x:Type RepeatButton}}"> <ControlTheme x:Key="FluentButtonSpinnerRepeatButton" TargetType="RepeatButton">
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="MinWidth" Value="34" /> <Setter Property="MinWidth" Value="34" />
<Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="Template">
<ControlTemplate>
<ContentPresenter x:Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundPointerOver}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource RepeatButtonBackgroundPressed}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter"> <Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}" /> <Setter Property="Foreground" Value="{DynamicResource RepeatButtonForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource RepeatButtonForegroundDisabled}"/>
</Style> </Style>
</ControlTheme> </ControlTheme>
@ -83,7 +101,6 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}"
CornerRadius="0"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
@ -99,7 +116,6 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}" BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}"
CornerRadius="0"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -60,6 +60,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertAfter<TypeReferenceResolver>( InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer()); new XDataTypeTransformer());
InsertBefore<DeferredContentTransformer>(
new AvaloniaXamlIlDeferredResourceTransformer()
);
// After everything else // After everything else
InsertBefore<NewObjectTransformer>( InsertBefore<NewObjectTransformer>(
new AddNameScopeRegistration(), new AddNameScopeRegistration(),

90
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs

@ -0,0 +1,90 @@
using System.Collections.Generic;
using System.Linq;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
internal class AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
return node;
if (!ShouldBeDeferred(pa.Values[1]))
return node;
var types = context.GetAvaloniaTypes();
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content")
{
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new XamlDirectCallPropertySetter(types.ResourceDictionaryDeferredAdd),
};
}
else if (pa.Property.Name == "Resources" && pa.Property.Getter.ReturnType.Equals(types.IResourceDictionary))
{
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
pa.PossibleSetters = new List<IXamlPropertySetter>
{
new AdderSetter(pa.Property.Getter, types.ResourceDictionaryDeferredAdd),
};
}
return node;
}
private static bool ShouldBeDeferred(IXamlAstValueNode node)
{
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
// At the moment it should be safe to not defer structs.
return !node.Type.GetClrType().IsValueType;
}
class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
private readonly IXamlMethod _getter;
private readonly IXamlMethod _adder;
public AdderSetter(IXamlMethod getter, IXamlMethod adder)
{
_getter = getter;
_adder = adder;
TargetType = getter.DeclaringType;
Parameters = adder.ParametersWithThis().Skip(1).ToList();
}
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters
{
AllowMultiple = true
};
public IReadOnlyList<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter emitter)
{
var locals = new Stack<XamlLocalsPool.PooledLocal>();
// Save all "setter" parameters
for (var c = Parameters.Count - 1; c >= 0; c--)
{
var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
locals.Push(loc);
emitter.Stloc(loc.Local);
}
emitter.EmitCall(_getter);
while (locals.Count > 0)
using (var loc = locals.Pop())
emitter.Ldloc(loc.Local);
emitter.EmitCall(_adder, true);
}
}
}
}

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -98,6 +98,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextDecorations { get; } public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; } public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; } public IXamlType ISetter { get; }
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -218,6 +221,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
IResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.IResourceDictionary");
ResourceDictionary = cfg.TypeSystem.GetType("Avalonia.Controls.ResourceDictionary");
ResourceDictionaryDeferredAdd = ResourceDictionary.FindMethod("AddDeferred", XamlIlTypes.Void, true, XamlIlTypes.Object,
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
} }
} }

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -39,6 +39,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
targetType = setter.Property.PropertyType; targetType = setter.Property.PropertyType;
} }
var previousWasControlTheme = false;
// Look upwards though the ambient context for IResourceNodes // Look upwards though the ambient context for IResourceNodes
// which might be able to give us the resource. // which might be able to give us the resource.
foreach (var parent in stack.Parents) foreach (var parent in stack.Parents)
@ -47,6 +49,21 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
return ColorToBrushConverter.Convert(value, targetType); return ColorToBrushConverter.Convert(value, targetType);
} }
// HACK: Temporary fix for #8678. Hard-coded to only work for the DevTools main
// window as we don't want 3rd parties to start relying on this hack.
//
// We need to implement compile-time merging of resource dictionaries and this
// hack can be removed.
if (previousWasControlTheme &&
parent is ResourceDictionary hack &&
hack.Owner?.GetType().FullName == "Avalonia.Diagnostics.Views.MainWindow" &&
hack.Owner.TryGetResource(ResourceKey, out value))
{
return ColorToBrushConverter.Convert(value, targetType);
}
previousWasControlTheme = parent is ControlTheme;
} }
if (provideTarget.TargetObject is IControl target && if (provideTarget.TargetObject is IControl target &&
@ -69,3 +86,4 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
} }
} }
} }

5
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -19,6 +19,7 @@ namespace Avalonia.Data
private bool _isSetterValue; private bool _isSetterValue;
private IStyledElement _target = default!; private IStyledElement _target = default!;
private Type? _targetType; private Type? _targetType;
private bool _hasProducedValue;
public TemplateBinding() public TemplateBinding()
{ {
@ -143,10 +144,12 @@ namespace Avalonia.Data
} }
PublishNext(value); PublishNext(value);
_hasProducedValue = true;
} }
else else if (_hasProducedValue)
{ {
PublishNext(AvaloniaProperty.UnsetValue); PublishNext(AvaloniaProperty.UnsetValue);
_hasProducedValue = false;
} }
} }

49
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -9,7 +9,6 @@ using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
@ -19,7 +18,7 @@ namespace Avalonia.Controls.UnitTests
public class ListBoxTests public class ListBoxTests
{ {
private MouseTestHelper _mouse = new MouseTestHelper(); private MouseTestHelper _mouse = new MouseTestHelper();
[Fact] [Fact]
public void Should_Use_ItemTemplate_To_Create_Item_Content() public void Should_Use_ItemTemplate_To_Create_Item_Content()
{ {
@ -433,6 +432,47 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void ListBox_Should_Be_Valid_After_Remove_Of_Item_In_NonVisibleArea()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = new AvaloniaList<string>(Enumerable.Range(1, 30).Select(v => v.ToString()));
var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
var target = new ListBox()
{
AutoScrollToSelectedItem = true,
Height = 100,
Width = 50,
VirtualizationMode = ItemVirtualizationMode.Simple,
ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
Items = items,
};
wnd.Content = target;
var lm = wnd.LayoutManager;
lm.ExecuteInitialLayoutPass();
//select last / scroll to last item
target.SelectedItem = items.Last();
lm.ExecuteLayoutPass();
//remove the first item (in non realized area of the listbox)
items.Remove("1");
lm.ExecuteLayoutPass();
Assert.Equal("30", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 1).DataContext);
Assert.Equal("29", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 2).DataContext);
Assert.Equal("28", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 3).DataContext);
Assert.Equal("27", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 4).DataContext);
Assert.Equal("26", target.ItemContainerGenerator.ContainerFromIndex(items.Count - 5).DataContext);
}
}
[Fact] [Fact]
public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control() public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control()
{ {
@ -656,7 +696,6 @@ namespace Avalonia.Controls.UnitTests
public string Value { get; } public string Value { get; }
} }
[Fact] [Fact]
public void SelectedItem_Validation() public void SelectedItem_Validation()
{ {
@ -670,11 +709,11 @@ namespace Avalonia.Controls.UnitTests
}; };
Prepare(target); Prepare(target);
var exception = new System.InvalidCastException("failed validation"); var exception = new System.InvalidCastException("failed validation");
var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError)); var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
target.Bind(ComboBox.SelectedItemProperty, textObservable); target.Bind(ComboBox.SelectedItemProperty, textObservable);
Assert.True(DataValidationErrors.GetHasErrors(target)); Assert.True(DataValidationErrors.GetHasErrors(target));
Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
} }

133
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -5,10 +5,12 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
@ -60,104 +62,123 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Clicking_Item_Should_Select_It() public void Clicking_Item_Should_Select_It()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = new FuncControlTemplate(CreateListBoxTemplate), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
}; Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ApplyTemplate(target); };
_mouse.Click(target.Presenter.Panel.Children[0]); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
Assert.Equal(0, target.SelectedIndex); _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
} }
[Fact] [Fact]
public void Clicking_Selected_Item_Should_Not_Deselect_It() public void Clicking_Selected_Item_Should_Not_Deselect_It()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = new FuncControlTemplate(CreateListBoxTemplate), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
}; Template = new FuncControlTemplate(CreateListBoxTemplate),
Items = new[] { "Foo", "Bar", "Baz " },
ApplyTemplate(target); };
target.SelectedIndex = 0; AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
target.SelectedIndex = 0;
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex); Assert.Equal(0, target.SelectedIndex);
}
} }
[Fact] [Fact]
public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = new FuncControlTemplate(CreateListBoxTemplate), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
SelectionMode = SelectionMode.Single | SelectionMode.Toggle, Template = new FuncControlTemplate(CreateListBoxTemplate),
}; Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
ApplyTemplate(target); };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex); Assert.Equal(0, target.SelectedIndex);
}
} }
[Fact] [Fact]
public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = new FuncControlTemplate(CreateListBoxTemplate), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
SelectionMode = SelectionMode.Toggle, Template = new FuncControlTemplate(CreateListBoxTemplate),
}; Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Toggle,
};
ApplyTemplate(target); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.SelectedIndex = 0; ApplyTemplate(target);
target.SelectedIndex = 0;
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
}
} }
[Fact] [Fact]
public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = new FuncControlTemplate(CreateListBoxTemplate), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, Template = new FuncControlTemplate(CreateListBoxTemplate),
}; Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected,
ApplyTemplate(target); };
target.SelectedIndex = 0; AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
target.SelectedIndex = 0;
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex); Assert.Equal(0, target.SelectedIndex);
}
} }
[Fact] [Fact]
public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = new FuncControlTemplate(CreateListBoxTemplate), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
SelectionMode = SelectionMode.Single | SelectionMode.Toggle, Template = new FuncControlTemplate(CreateListBoxTemplate),
}; Items = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
ApplyTemplate(target); };
target.SelectedIndex = 1; AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
ApplyTemplate(target);
target.SelectedIndex = 1;
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex); Assert.Equal(0, target.SelectedIndex);
}
} }
[Fact] [Fact]

89
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -14,6 +14,7 @@ using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Avalonia.Platform; using Avalonia.Platform;
@ -1115,42 +1116,48 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact] [Fact]
public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz " }, {
}; Template = Template(),
Items = new[] { "Foo", "Bar", "Baz " },
Prepare(target); };
_helper.Down((Interactive)target.Presenter.Panel.Children[1]); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var panel = target.Presenter.Panel; var panel = target.Presenter.Panel;
Assert.Equal( Assert.Equal(
KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel), KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel),
panel.Children[1]); panel.Children[1]);
}
} }
[Fact] [Fact]
public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement()
{ {
var items = new ObservableCollection<string>(new[] { "Foo", "Bar", "Baz " }); using (UnitTestApplication.Start())
var target = new ListBox
{ {
Template = Template(), var items = new ObservableCollection<string>(new[] { "Foo", "Bar", "Baz " });
Items = items,
};
Prepare(target); var target = new ListBox
{
Template = Template(),
Items = items,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
_helper.Down(target.Presenter.Panel.Children[1]); _helper.Down(target.Presenter.Panel.Children[1]);
items.RemoveAt(1); items.RemoveAt(1);
var panel = target.Presenter.Panel; var panel = target.Presenter.Panel;
Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel));
}
} }
[Fact] [Fact]
@ -1230,31 +1237,37 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact] [Fact]
public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, {
}; Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
Prepare(target); };
_helper.Down((Interactive)target.Presenter.Panel.Children[3]); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[3]);
Assert.Equal(3, target.SelectedIndex); Assert.Equal(3, target.SelectedIndex);
}
} }
[Fact] [Fact]
public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, {
}; Template = Template(),
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
Prepare(target); };
_helper.Down((Interactive)target.Presenter.Panel.Children[3]); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[3]);
Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes);
}
} }
[Fact] [Fact]

542
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -10,8 +10,10 @@ using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives namespace Avalonia.Controls.UnitTests.Primitives
@ -701,261 +703,290 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact] [Fact]
public void Shift_Selecting_From_No_Selection_Selects_From_Start() public void Shift_Selecting_From_No_Selection_Selects_From_Start()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var panel = target.Presenter.Panel; _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift);
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); var panel = target.Presenter.Panel;
Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target));
}
} }
[Fact] [Fact]
public void Ctrl_Selecting_Raises_SelectionChanged_Events() public void Ctrl_Selecting_Raises_SelectionChanged_Events()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Qux" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Qux" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
SelectionChangedEventArgs receivedArgs = null; SelectionChangedEventArgs receivedArgs = null;
target.SelectionChanged += (_, args) => receivedArgs = args; target.SelectionChanged += (_, args) => receivedArgs = args;
void VerifyAdded(string selection) void VerifyAdded(string selection)
{ {
Assert.NotNull(receivedArgs); Assert.NotNull(receivedArgs);
Assert.Equal(new[] { selection }, receivedArgs.AddedItems); Assert.Equal(new[] { selection }, receivedArgs.AddedItems);
Assert.Empty(receivedArgs.RemovedItems); Assert.Empty(receivedArgs.RemovedItems);
} }
void VerifyRemoved(string selection) void VerifyRemoved(string selection)
{ {
Assert.NotNull(receivedArgs); Assert.NotNull(receivedArgs);
Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); Assert.Equal(new[] { selection }, receivedArgs.RemovedItems);
Assert.Empty(receivedArgs.AddedItems); Assert.Empty(receivedArgs.AddedItems);
} }
_helper.Click((Interactive)target.Presenter.Panel.Children[1]); _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
VerifyAdded("Bar"); VerifyAdded("Bar");
receivedArgs = null; receivedArgs = null;
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
VerifyAdded("Baz"); VerifyAdded("Baz");
receivedArgs = null; receivedArgs = null;
_helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control);
VerifyAdded("Qux"); VerifyAdded("Qux");
receivedArgs = null; receivedArgs = null;
_helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control);
VerifyRemoved("Bar"); VerifyRemoved("Bar");
}
} }
[Fact] [Fact]
public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Qux" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Qux" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
_helper.Click((Interactive)target.Presenter.Panel.Children[1]); target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
Assert.Equal(1, target.SelectedIndex); _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control);
Assert.Equal("Bar", target.SelectedItem);
Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems); Assert.Equal(1, target.SelectedIndex);
Assert.Equal("Bar", target.SelectedItem);
_helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); Assert.Equal(new[] { "Bar", "Baz", "Qux" }, target.SelectedItems);
Assert.Equal(2, target.SelectedIndex); _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control);
Assert.Equal("Baz", target.SelectedItem);
Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems); Assert.Equal(2, target.SelectedIndex);
Assert.Equal("Baz", target.SelectedItem);
Assert.Equal(new[] { "Baz", "Qux" }, target.SelectedItems);
}
} }
[Fact] [Fact]
public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.ApplyTemplate(); target.ApplyTemplate();
target.Presenter.ApplyTemplate(); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[1]); _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
Assert.Equal(1, target.SelectedIndex); Assert.Equal(1, target.SelectedIndex);
Assert.Equal("Bar", target.SelectedItem); Assert.Equal("Bar", target.SelectedItem);
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
Assert.Equal(1, target.SelectedIndex); Assert.Equal(1, target.SelectedIndex);
Assert.Equal("Bar", target.SelectedItem); Assert.Equal("Bar", target.SelectedItem);
}
} }
[Fact] [Fact]
public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
_helper.Click((Interactive)target.Presenter.Panel.Children[3]); target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[3]);
var panel = target.Presenter.Panel; _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control);
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); var panel = target.Presenter.Panel;
Assert.Equal(new[] { 3, 4 }, SelectedContainers(target));
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
Assert.Equal(new[] { 3, 4 }, SelectedContainers(target));
}
} }
[Fact] [Fact]
public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
_helper.Click((Interactive)target.Presenter.Panel.Children[3]); target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[3]);
var panel = target.Presenter.Panel; _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift);
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); var panel = target.Presenter.Panel;
Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target));
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
Assert.Equal(new[] { 3, 4, 5 }, SelectedContainers(target));
}
} }
[Fact] [Fact]
public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
_helper.Click((Interactive)target.Presenter.Panel.Children[0]); target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]);
var panel = target.Presenter.Panel; _helper.Click((Interactive)target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift);
Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems); var panel = target.Presenter.Panel;
Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target));
Assert.Equal(new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, target.SelectedItems);
Assert.Equal(new[] { 0, 1, 2, 3, 4, 5 }, SelectedContainers(target));
}
} }
[Fact] [Fact]
public void Shift_Selecting_Raises_SelectionChanged_Events() public void Shift_Selecting_Raises_SelectionChanged_Events()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Qux" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Qux" },
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
SelectionChangedEventArgs receivedArgs = null; SelectionChangedEventArgs receivedArgs = null;
target.SelectionChanged += (_, args) => receivedArgs = args; target.SelectionChanged += (_, args) => receivedArgs = args;
void VerifyAdded(params string[] selection) void VerifyAdded(params string[] selection)
{ {
Assert.NotNull(receivedArgs); Assert.NotNull(receivedArgs);
Assert.Equal(selection, receivedArgs.AddedItems); Assert.Equal(selection, receivedArgs.AddedItems);
Assert.Empty(receivedArgs.RemovedItems); Assert.Empty(receivedArgs.RemovedItems);
} }
void VerifyRemoved(string selection) void VerifyRemoved(string selection)
{ {
Assert.NotNull(receivedArgs); Assert.NotNull(receivedArgs);
Assert.Equal(new[] { selection }, receivedArgs.RemovedItems); Assert.Equal(new[] { selection }, receivedArgs.RemovedItems);
Assert.Empty(receivedArgs.AddedItems); Assert.Empty(receivedArgs.AddedItems);
} }
_helper.Click((Interactive)target.Presenter.Panel.Children[1]); _helper.Click((Interactive)target.Presenter.Panel.Children[1]);
VerifyAdded("Bar"); VerifyAdded("Bar");
receivedArgs = null; receivedArgs = null;
_helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift); _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Shift);
VerifyAdded("Baz" ,"Qux"); VerifyAdded("Baz", "Qux");
receivedArgs = null; receivedArgs = null;
_helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); _helper.Click((Interactive)target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift);
VerifyRemoved("Qux"); VerifyRemoved("Qux");
}
} }
[Fact] [Fact]
public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, {
SelectionMode = SelectionMode.Multiple, Template = Template(),
}; Items = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
SelectionMode = SelectionMode.Multiple,
};
target.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.Presenter.ApplyTemplate(); target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]);
Assert.Equal(new[] { "Foo" }, target.SelectedItems); Assert.Equal(new[] { "Foo" }, target.SelectedItems);
_helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control);
Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
_helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control);
Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems); Assert.Equal(new[] { "Foo", "Bar", "Foo" }, target.SelectedItems);
_helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control); _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Control);
Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems); Assert.Equal(new[] { "Foo", "Bar", "Foo", "Bar" }, target.SelectedItems);
}
} }
[Fact] [Fact]
@ -1158,70 +1189,79 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact] [Fact]
public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }), Template = Template(),
SelectionMode = SelectionMode.Multiple, Items = new[] { "Foo", "Bar", "Baz" },
}; ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.SelectAll(); target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(3, target.SelectedItems.Count); target.SelectAll();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]); Assert.Equal(3, target.SelectedItems.Count);
Assert.Equal(1, target.SelectedItems.Count); _helper.Click((Interactive)target.Presenter.Panel.Children[0]);
Assert.Equal(new[] { "Foo", }, target.SelectedItems);
Assert.Equal(new[] { 0 }, SelectedContainers(target)); Assert.Equal(1, target.SelectedItems.Count);
Assert.Equal(new[] { "Foo", }, target.SelectedItems);
Assert.Equal(new[] { 0 }, SelectedContainers(target));
}
} }
[Fact] [Fact]
public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }), Template = Template(),
SelectionMode = SelectionMode.Multiple, Items = new[] { "Foo", "Bar", "Baz" },
}; ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.SelectAll(); target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectAll();
Assert.Equal(3, target.SelectedItems.Count); Assert.Equal(3, target.SelectedItems.Count);
_helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); _helper.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right);
Assert.Equal(3, target.SelectedItems.Count); Assert.Equal(3, target.SelectedItems.Count);
}
} }
[Fact] [Fact]
public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }), Template = Template(),
SelectionMode = SelectionMode.Multiple, Items = new[] { "Foo", "Bar", "Baz" },
}; ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
_helper.Click((Interactive)target.Presenter.Panel.Children[0]); target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]);
Assert.Equal(2, target.SelectedItems.Count); _helper.Click((Interactive)target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift);
_helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right); Assert.Equal(2, target.SelectedItems.Count);
Assert.Equal(1, target.SelectedItems.Count); _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right);
Assert.Equal(1, target.SelectedItems.Count);
}
} }
[Fact] [Fact]
@ -1253,41 +1293,47 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact] [Fact]
public void Shift_Right_Click_Should_Not_Select_Multiple() public void Shift_Right_Click_Should_Not_Select_Multiple()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }), Template = Template(),
SelectionMode = SelectionMode.Multiple, Items = new[] { "Foo", "Bar", "Baz" },
}; ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift);
_helper.Click((Interactive)target.Presenter.Panel.Children[0]);
Assert.Equal(1, target.SelectedItems.Count); _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift);
Assert.Equal(1, target.SelectedItems.Count);
}
} }
[Fact] [Fact]
public void Ctrl_Right_Click_Should_Not_Select_Multiple() public void Ctrl_Right_Click_Should_Not_Select_Multiple()
{ {
var target = new ListBox using (UnitTestApplication.Start())
{ {
Template = Template(), var target = new ListBox
Items = new[] { "Foo", "Bar", "Baz" }, {
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }), Template = Template(),
SelectionMode = SelectionMode.Multiple, Items = new[] { "Foo", "Bar", "Baz" },
}; ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectionMode = SelectionMode.Multiple,
target.ApplyTemplate(); };
target.Presenter.ApplyTemplate(); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
target.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[0]); target.Presenter.ApplyTemplate();
_helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control);
_helper.Click((Interactive)target.Presenter.Panel.Children[0]);
Assert.Equal(1, target.SelectedItems.Count); _helper.Click((Interactive)target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control);
Assert.Equal(1, target.SelectedItems.Count);
}
} }
[Fact] [Fact]

38
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -18,6 +18,7 @@ using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using JetBrains.Annotations; using JetBrains.Annotations;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
@ -885,28 +886,31 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection()
{ {
var tree = CreateTestTreeData(); using (UnitTestApplication.Start())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple, {
}; Template = CreateTreeViewTemplate(),
Items = tree,
var visualRoot = new TestRoot(); SelectionMode = SelectionMode.Multiple,
visualRoot.Child = target; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
target.SelectAll(); target.SelectAll();
AssertChildrenSelected(target, tree[0]); AssertChildrenSelected(target, tree[0]);
Assert.Equal(5, target.SelectedItems.Count); Assert.Equal(5, target.SelectedItems.Count);
_mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right); _mouse.Click((Interactive)target.Presenter.Panel.Children[0], MouseButton.Right);
Assert.Equal(5, target.SelectedItems.Count); Assert.Equal(5, target.SelectedItems.Count);
}
} }
[Fact] [Fact]

43
tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
@ -217,6 +218,37 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
[Fact]
public void Should_Not_Pass_UnsetValue_To_MultiBinding_During_ApplyTemplate()
{
var converter = new MultiConverter();
var source = new Button
{
Content = "foo",
Template = new FuncControlTemplate<Button>((parent, _) =>
new ContentPresenter
{
[~ContentPresenter.ContentProperty] = new MultiBinding
{
Converter = converter,
Bindings =
{
new TemplateBinding(ContentControl.ContentProperty),
}
}
}),
};
source.ApplyTemplate();
var target = (ContentPresenter)source.GetVisualChildren().Single();
// #8672 was caused by TemplateBinding passing "unset" to the MultiBinding during
// ApplyTemplate as the TemplatedParent property doesn't get setup until after the
// binding is initiated.
Assert.Equal(new[] { "foo" }, converter.Values);
}
private class PrefixConverter : IValueConverter private class PrefixConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
@ -247,5 +279,16 @@ namespace Avalonia.Markup.UnitTests.Data
return null; return null;
} }
} }
private class MultiConverter : IMultiValueConverter
{
public List<object> Values { get; } = new();
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
Values.AddRange(values);
return values.FirstOrDefault();
}
}
} }
} }

207
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -66,6 +67,212 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
} }
} }
[Fact]
public void Item_Is_Added_To_ResourceDictionary_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='Red' Color='Red' />
</ResourceDictionary>";
var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml);
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Added_To_ResourceDictionary_Is_UnDeferred_On_Read()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='Red' Color='Red' />
</ResourceDictionary>";
var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml);
Assert.True(resources.ContainsDeferredKey("Red"));
Assert.IsType<SolidColorBrush>(resources["Red"]);
Assert.False(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Window_Resources_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<SolidColorBrush x:Key='Red' Color='Red' />
</Window.Resources>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)window.Resources;
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Window_MergedDictionaries_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<SolidColorBrush x:Key='Red' Color='Red' />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)window.Resources.MergedDictionaries[0];
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Style_Resources_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<SolidColorBrush x:Key='Red' Color='Red' />
</Style.Resources>
</Style>";
var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)style.Resources;
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Is_Added_To_Styles_Resources_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='Red' Color='Red' />
</Styles.Resources>
</Styles>";
var style = (Styles)AvaloniaRuntimeXamlLoader.Load(xaml);
var resources = (ResourceDictionary)style.Resources;
Assert.True(resources.ContainsDeferredKey("Red"));
}
}
[Fact]
public void Item_Can_Be_StaticReferenced_As_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<SolidColorBrush x:Key='Red' Color='Red' />
</Window.Resources>
<Button>
<Button.Resources>
<StaticResource x:Key='Red2' ResourceKey='Red' />
</Button.Resources>
</Button>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var windowResources = (ResourceDictionary)window.Resources;
var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources;
Assert.True(windowResources.ContainsDeferredKey("Red"));
Assert.True(buttonResources.ContainsDeferredKey("Red2"));
}
}
[Fact]
public void Item_StaticReferenced_Is_UnDeferred_On_Read()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<SolidColorBrush x:Key='Red' Color='Red' />
</Window.Resources>
<Button>
<Button.Resources>
<StaticResource x:Key='Red2' ResourceKey='Red' />
</Button.Resources>
</Button>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var windowResources = (ResourceDictionary)window.Resources;
var buttonResources = (ResourceDictionary)((Button)window.Content!).Resources;
Assert.IsType<SolidColorBrush>(buttonResources["Red2"]);
Assert.False(windowResources.ContainsDeferredKey("Red"));
Assert.False(buttonResources.ContainsDeferredKey("Red2"));
}
}
[Fact]
public void Value_Type_With_Parse_Converter_Should_Not_Be_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Color x:Key='Red'>Red</Color>
</ResourceDictionary>";
var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml);
Assert.False(resources.ContainsDeferredKey("Red"));
Assert.IsType<Color>(resources["Red"]);
}
}
[Fact]
public void Value_Type_With_Ctor_Converter_Should_Not_Be_Deferred()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Thickness x:Key='Margin'>1 1 1 1</Thickness>
</ResourceDictionary>";
var resources = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml);
Assert.False(resources.ContainsDeferredKey("Margin"));
Assert.IsType<Thickness>(resources["Margin"]);
}
}
private IDisposable StyledWindow(params (string, string)[] assets) private IDisposable StyledWindow(params (string, string)[] assets)
{ {
var services = TestServices.StyledWindow.With( var services = TestServices.StyledWindow.With(

Loading…
Cancel
Save