Browse Source

Merge branch 'fixes/TextLineCaretNavigation' of https://github.com/Gillibald/Avalonia into fixes/TextLineCaretNavigation

pull/4339/head
Benedikt Schroeder 6 years ago
parent
commit
6aedb7cce8
  1. 13
      native/Avalonia.Native/src/OSX/window.mm
  2. 8
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  3. 16
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  4. 31
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  5. 1
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  6. 21
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  7. 20
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  8. 2
      src/Avalonia.Controls/Repeater/ElementFactory.cs
  9. 1
      src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
  10. 29
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  11. 6
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  12. 12
      src/Avalonia.Controls/Templates/IDataTemplate.cs
  13. 25
      src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs
  14. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs
  15. 11
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  16. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  17. 2
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  18. 40
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  19. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

13
native/Avalonia.Native/src/OSX/window.mm

@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->UpdateCursor(); _parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size]; auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height; if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
[self updateRenderTarget]; {
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); _lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
} }
- (void)updateLayer - (void)updateLayer

8
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -10,7 +10,13 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8"> <StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox> <ListBox Items="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
SelectedItems="{Binding SelectedItems}"
AutoScrollToSelectedItem="True"
SelectionMode="{Binding SelectionMode}"
Width="250"
Height="350"/>
<Button Command="{Binding AddItemCommand}">Add</Button> <Button Command="{Binding AddItemCommand}">Add</Button>

16
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -88,7 +88,7 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate),
x => x.SelectedDate, (x, v) => x.SelectedDate = v); x => x.SelectedDate, (x, v) => x.SelectedDate = v);
//Template Items // Template Items
private Button _flyoutButton; private Button _flyoutButton;
private TextBlock _dayText; private TextBlock _dayText;
private TextBlock _monthText; private TextBlock _monthText;
@ -359,10 +359,14 @@ namespace Avalonia.Controls
} }
} }
Grid.SetColumn(_spacer1, 1); var isSpacer1Visible = columnIndex > 1;
Grid.SetColumn(_spacer2, 3); var isSpacer2Visible = columnIndex > 2;
_spacer1.IsVisible = columnIndex > 1; // ternary conditional operator is used to make sure grid cells will be validated
_spacer2.IsVisible = columnIndex > 2; Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
_spacer1.IsVisible = isSpacer1Visible;
_spacer2.IsVisible = isSpacer2Visible;
} }
private void SetSelectedDateText() private void SetSelectedDateText()
@ -398,7 +402,7 @@ namespace Avalonia.Controls
var deltaY = _presenter.GetOffsetForPopup(); var deltaY = _presenter.GetOffsetForPopup();
//The extra 5 px I think is related to default popup placement behavior // The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);

31
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -77,7 +77,7 @@ namespace Avalonia.Controls
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x =>
x.YearVisible, (x, v) => x.YearVisible = v); x.YearVisible, (x, v) => x.YearVisible = v);
//Template Items // Template Items
private Grid _pickerContainer; private Grid _pickerContainer;
private Button _acceptButton; private Button _acceptButton;
private Button _dismissButton; private Button _dismissButton;
@ -107,7 +107,7 @@ namespace Avalonia.Controls
private bool _yearVisible = true; private bool _yearVisible = true;
private DateTimeOffset _syncDate; private DateTimeOffset _syncDate;
private GregorianCalendar _calendar; private readonly GregorianCalendar _calendar;
private bool _suppressUpdateSelection; private bool _suppressUpdateSelection;
public DatePickerPresenter() public DatePickerPresenter()
@ -234,7 +234,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
//These are requirements, so throw if not found // These are requirements, so throw if not found
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer"); _pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
_monthHost = e.NameScope.Get<Panel>("MonthHost"); _monthHost = e.NameScope.Get<Panel>("MonthHost");
_dayHost = e.NameScope.Get<Panel>("DayHost"); _dayHost = e.NameScope.Get<Panel>("DayHost");
@ -326,7 +326,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
private void InitPicker() private void InitPicker()
{ {
//OnApplyTemplate must've been called before we can init here... // OnApplyTemplate must've been called before we can init here...
if (_pickerContainer == null) if (_pickerContainer == null)
return; return;
@ -344,12 +344,11 @@ namespace Avalonia.Controls
SetGrid(); SetGrid();
//Date should've been set when we reach this point // Date should've been set when we reach this point
var dt = Date; var dt = Date;
if (DayVisible) if (DayVisible)
{ {
GregorianCalendar gc = new GregorianCalendar(); var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month);
var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
_daySelector.MaximumValue = maxDays; _daySelector.MaximumValue = maxDays;
_daySelector.MinimumValue = 1; _daySelector.MinimumValue = 1;
_daySelector.SelectedValue = dt.Day; _daySelector.SelectedValue = dt.Day;
@ -407,10 +406,14 @@ namespace Avalonia.Controls
} }
} }
Grid.SetColumn(_spacer1, 1); var isSpacer1Visible = columnIndex > 1;
Grid.SetColumn(_spacer2, 3); var isSpacer2Visible = columnIndex > 2;
_spacer1.IsVisible = columnIndex > 1; // ternary conditional operator is used to make sure grid cells will be validated
_spacer2.IsVisible = columnIndex > 2; Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
_spacer1.IsVisible = isSpacer1Visible;
_spacer2.IsVisible = isSpacer2Visible;
} }
private void SetInitialFocus() private void SetInitialFocus()
@ -433,12 +436,12 @@ namespace Avalonia.Controls
} }
} }
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
{ {
OnDismiss(); OnDismiss();
} }
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
{ {
Date = _syncDate; Date = _syncDate;
OnConfirmed(); OnConfirmed();
@ -471,7 +474,7 @@ namespace Avalonia.Controls
_syncDate = newDate; _syncDate = newDate;
//We don't need to update the days if not displaying day, not february // We don't need to update the days if not displaying day, not february
if (!DayVisible || _syncDate.Month != 2) if (!DayVisible || _syncDate.Month != 2)
return; return;

1
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators
private readonly IDataTemplate _inner; private readonly IDataTemplate _inner;
public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner; public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
public IControl Build(object param) => _inner.Build(param); public IControl Build(object param) => _inner.Build(param);
public bool SupportsRecycling => _inner.SupportsRecycling;
public bool Match(object data) => _inner.Match(data); public bool Match(object data) => _inner.Match(data);
public InstancedBinding ItemsSelector(object item) => null; public InstancedBinding ItemsSelector(object item) => null;
} }

21
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child; private IControl _child;
private bool _createdChild; private bool _createdChild;
private IDataTemplate _dataTemplate; private IRecyclingDataTemplate _recyclingDataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
/// <summary> /// <summary>
@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
_dataTemplate = null; _recyclingDataTemplate = null;
_createdChild = false; _createdChild = false;
InvalidateMeasure(); InvalidateMeasure();
} }
@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters
{ {
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default; var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
// We have content and it isn't a control, so if the new data template is the same if (dataTemplate is IRecyclingDataTemplate rdt)
// as the old data template, try to recycle the existing child control to display
// the new data.
if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
{ {
newChild = oldChild; var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
newChild = rdt.Build(content, toRecycle);
_recyclingDataTemplate = rdt;
} }
else else
{ {
_dataTemplate = dataTemplate; newChild = dataTemplate.Build(content);
newChild = _dataTemplate.Build(content); _recyclingDataTemplate = null;
} }
} }
else else
{ {
_dataTemplate = null; _recyclingDataTemplate = null;
} }
return newChild; return newChild;
@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters
LogicalChildren.Remove(Child); LogicalChildren.Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent); ((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null; Child = null;
_dataTemplate = null; _recyclingDataTemplate = null;
} }
InvalidateMeasure(); InvalidateMeasure();

20
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives
} }
} }
foreach (var i in e.SelectedIndices) if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0)
{ {
Mark(i.GetAt(0), true); foreach (var i in e.SelectedIndices)
} {
Mark(i.GetAt(0), true);
}
foreach (var i in e.DeselectedIndices) foreach (var i in e.DeselectedIndices)
{
Mark(i.GetAt(0), false);
}
}
else if (e.DeselectedItems.Count > 0)
{ {
Mark(i.GetAt(0), false); // (De)selected indices being empty means that a selected item was removed from
// the Items (it can't tell us the index of the item because the index is no longer
// valid). In this case, we just update the selection state of all containers.
UpdateContainerSelection();
} }
var newSelectedIndex = SelectedIndex; var newSelectedIndex = SelectedIndex;

2
src/Avalonia.Controls/Repeater/ElementFactory.cs

@ -4,8 +4,6 @@ namespace Avalonia.Controls
{ {
public abstract class ElementFactory : IElementFactory public abstract class ElementFactory : IElementFactory
{ {
bool IDataTemplate.SupportsRecycling => false;
public IControl Build(object data) public IControl Build(object data)
{ {
return GetElementCore(new ElementFactoryGetArgs { Data = data }); return GetElementCore(new ElementFactoryGetArgs { Data = data });

1
src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs

@ -13,7 +13,6 @@ namespace Avalonia.Controls
public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate;
public bool SupportsRecycling => false;
public IControl Build(object param) => GetElement(null, param); public IControl Build(object param) => GetElement(null, param);
public bool Match(object data) => _dataTemplate.Match(data); public bool Match(object data) => _dataTemplate.Match(data);

29
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates
/// <summary> /// <summary>
/// Builds a control for a piece of data. /// Builds a control for a piece of data.
/// </summary> /// </summary>
public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate public class FuncDataTemplate : FuncTemplate<object, IControl>, IRecyclingDataTemplate
{ {
/// <summary> /// <summary>
/// The default data template used in the case where no matching data template is found. /// The default data template used in the case where no matching data template is found.
@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates
}, },
true); true);
/// <summary>
/// The implementation of the <see cref="Match"/> method.
/// </summary>
private readonly Func<object, bool> _match; private readonly Func<object, bool> _match;
private readonly bool _supportsRecycling;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FuncDataTemplate"/> class. /// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates
Contract.Requires<ArgumentNullException>(match != null); Contract.Requires<ArgumentNullException>(match != null);
_match = match; _match = match;
SupportsRecycling = supportsRecycling; _supportsRecycling = supportsRecycling;
} }
/// <inheritdoc/>
public bool SupportsRecycling { get; }
/// <summary> /// <summary>
/// Checks to see if this data template matches the specified data. /// Checks to see if this data template matches the specified data.
/// </summary> /// </summary>
@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates
return _match(data); return _match(data);
} }
/// <summary>
/// Creates or recycles a control to display the specified data.
/// </summary>
/// <param name="data">The data to display.</param>
/// <param name="existing">An optional control to recycle.</param>
/// <returns>
/// The <paramref name="existing"/> control if supplied and applicable to
/// <paramref name="data"/>, otherwise a new control.
/// </returns>
/// <remarks>
/// The caller should ensure that any control passed to <paramref name="existing"/>
/// originated from the same data template.
/// </remarks>
public IControl Build(object data, IControl existing)
{
return _supportsRecycling && existing is object ? existing : Build(data);
}
/// <summary> /// <summary>
/// Determines of an object is of the specified type. /// Determines of an object is of the specified type.
/// </summary> /// </summary>

6
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

@ -1,5 +1,7 @@
using System; using System;
#nullable enable
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
/// <summary> /// <summary>
@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates
/// <param name="func">The function used to create the control.</param> /// <param name="func">The function used to create the control.</param>
public FuncTemplate(Func<TParam, INameScope, TControl> func) public FuncTemplate(Func<TParam, INameScope, TControl> func)
{ {
Contract.Requires<ArgumentNullException>(func != null); _func = func ?? throw new ArgumentNullException(nameof(func));
_func = func;
} }
/// <summary> /// <summary>

12
src/Avalonia.Controls/Templates/IDataTemplate.cs

@ -1,3 +1,7 @@
using System;
#nullable enable
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
/// <summary> /// <summary>
@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates
/// </summary> /// </summary>
public interface IDataTemplate : ITemplate<object, IControl> public interface IDataTemplate : ITemplate<object, IControl>
{ {
/// <summary>
/// Gets a value indicating whether the data template supports recycling of the generated
/// control.
/// </summary>
bool SupportsRecycling { get; }
/// <summary> /// <summary>
/// Checks to see if this data template matches the specified data. /// Checks to see if this data template matches the specified data.
/// </summary> /// </summary>
@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
bool Match(object data); bool Match(object data);
} }
} }

25
src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs

@ -0,0 +1,25 @@
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
/// An <see cref="IDataTemplate"/> that supports recycling existing elements.
/// </summary>
public interface IRecyclingDataTemplate : IDataTemplate
{
/// <summary>
/// Creates or recycles a control to display the specified data.
/// </summary>
/// <param name="data">The data to display.</param>
/// <param name="existing">An optional control to recycle.</param>
/// <returns>
/// The <paramref name="existing"/> control if supplied and applicable to
/// <paramref name="data"/>, otherwise a new control.
/// </returns>
/// <remarks>
/// The caller should ensure that any control passed to <paramref name="existing"/>
/// originated from the same data template.
/// </remarks>
IControl Build(object data, IControl? existing);
}
}

2
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics
{ {
internal class ViewLocator : IDataTemplate internal class ViewLocator : IDataTemplate
{ {
public bool SupportsRecycling => false;
public IControl Build(object data) public IControl Build(object data)
{ {
var name = data.GetType().FullName.Replace("ViewModel", "View"); var name = data.GetType().FullName.Replace("ViewModel", "View");

11
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@ -5,7 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
public class DataTemplate : IDataTemplate public class DataTemplate : IRecyclingDataTemplate
{ {
public Type DataType { get; set; } public Type DataType { get; set; }
@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent] [TemplateContent]
public object Content { get; set; } public object Content { get; set; }
public bool SupportsRecycling { get; set; } = true;
public bool Match(object data) public bool Match(object data)
{ {
if (DataType == null) if (DataType == null)
@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates
} }
} }
public IControl Build(object data) => TemplateContent.Load(Content).Control; public IControl Build(object data) => Build(data, null);
public IControl Build(object data, IControl existing)
{
return existing ?? TemplateContent.Load(Content).Control;
}
} }
} }

2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates
[AssignBinding] [AssignBinding]
public Binding ItemsSource { get; set; } public Binding ItemsSource { get; set; }
public bool SupportsRecycling { get; set; } = true;
public bool Match(object data) public bool Match(object data)
{ {
if (DataType == null) if (DataType == null)

2
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -402,6 +402,8 @@ namespace Avalonia.Win32
case WindowsMessage.WM_GETMINMAXINFO: case WindowsMessage.WM_GETMINMAXINFO:
{ {
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam); MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam);
_maxTrackSize = mmi.ptMaxTrackSize;
if (_minSize.Width > 0) if (_minSize.Width > 0)
{ {

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

@ -531,6 +531,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
}; };
target.ApplyTemplate(); target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = 1; target.SelectedIndex = 1;
Assert.Equal(items[1], target.SelectedItem); Assert.Equal(items[1], target.SelectedItem);
@ -549,6 +550,45 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.NotNull(receivedArgs); Assert.NotNull(receivedArgs);
Assert.Empty(receivedArgs.AddedItems); Assert.Empty(receivedArgs.AddedItems);
Assert.Equal(new[] { removed }, receivedArgs.RemovedItems); Assert.Equal(new[] { removed }, receivedArgs.RemovedItems);
Assert.False(items.Single().IsSelected);
}
[Fact]
public void Removing_Selected_Item_Should_Clear_Selection_With_BeginInit()
{
var items = new AvaloniaList<Item>
{
new Item(),
new Item(),
};
var target = new SelectingItemsControl();
target.BeginInit();
target.Items = items;
target.Template = Template();
target.EndInit();
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = 0;
Assert.Equal(items[0], target.SelectedItem);
Assert.Equal(0, target.SelectedIndex);
SelectionChangedEventArgs receivedArgs = null;
target.SelectionChanged += (_, args) => receivedArgs = args;
var removed = items[0];
items.RemoveAt(0);
Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
Assert.NotNull(receivedArgs);
Assert.Empty(receivedArgs.AddedItems);
Assert.Equal(new[] { removed }, receivedArgs.RemovedItems);
Assert.False(items.Single().IsSelected);
} }
[Fact] [Fact]

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

@ -1273,8 +1273,6 @@ namespace Avalonia.Controls.UnitTests
return new TextBlock { Text = node.Value }; return new TextBlock { Text = node.Value };
} }
public bool SupportsRecycling => false;
public InstancedBinding ItemsSelector(object item) public InstancedBinding ItemsSelector(object item)
{ {
var obs = ExpressionObserver.Create(item, o => (o as Node).Children); var obs = ExpressionObserver.Create(item, o => (o as Node).Children);

Loading…
Cancel
Save