Browse Source

Added container lifecycle events.

Seems we weren't calling `ClearItemContainer` for `ItemIsOwnContainer` items in `PanelContainerGenerator`. Fixed that too.
pull/10685/head
Steven Kirk 3 years ago
parent
commit
791ea8beef
  1. 20
      src/Avalonia.Controls/ContainerClearingEventArgs.cs
  2. 32
      src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs
  3. 26
      src/Avalonia.Controls/ContainerPreparedEventArgs.cs
  4. 31
      src/Avalonia.Controls/ItemsControl.cs
  5. 3
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  6. 130
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

20
src/Avalonia.Controls/ContainerClearingEventArgs.cs

@ -0,0 +1,20 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerClearing"/> event.
/// </summary>
public class ContainerClearingEventArgs : EventArgs
{
public ContainerClearingEventArgs(Control container)
{
Container = container;
}
/// <summary>
/// Gets the prepared container.
/// </summary>
public Control Container { get; }
}
}

32
src/Avalonia.Controls/ContainerIndexChangedEventArgs.cs

@ -0,0 +1,32 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerIndexChanged"/> event.
/// </summary>
public class ContainerIndexChangedEventArgs : EventArgs
{
public ContainerIndexChangedEventArgs(Control container, int oldIndex, int newIndex)
{
Container = container;
OldIndex = oldIndex;
NewIndex = newIndex;
}
/// <summary>
/// Get the container for which the index changed.
/// </summary>
public Control Container { get; }
/// <summary>
/// Gets the index of the container after the change.
/// </summary>
public int NewIndex { get; }
/// <summary>
/// Gets the index of the container before the change.
/// </summary>
public int OldIndex { get; }
}
}

26
src/Avalonia.Controls/ContainerPreparedEventArgs.cs

@ -0,0 +1,26 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data for the <see cref="ItemsControl.ContainerPrepared"/> event.
/// </summary>
public class ContainerPreparedEventArgs : EventArgs
{
public ContainerPreparedEventArgs(Control container, int index)
{
Container = container;
Index = index;
}
/// <summary>
/// Gets the prepared container.
/// </summary>
public Control Container { get; }
/// <summary>
/// Gets the index of the item the container was prepared for.
/// </summary>
public int Index { get; }
}
}

31
src/Avalonia.Controls/ItemsControl.cs

@ -274,6 +274,34 @@ namespace Avalonia.Controls
remove => _childIndexChanged -= value;
}
/// <summary>
/// Occurs each time a container is prepared for use.
/// </summary>
/// <remarks>
/// The prepared element might be newly created or an existing container that is being re-
/// used.
/// </remarks>
public event EventHandler<ContainerPreparedEventArgs>? ContainerPrepared;
/// <summary>
/// Occurs for each realized container when the index for the item it represents has changed.
/// </summary>
/// <remarks>
/// This event is raised for each realized container where the index for the item it
/// represents has changed. For example, when another item is added or removed in the data
/// source, the index for items that come after in the ordering will be impacted.
/// </remarks>
public event EventHandler<ContainerIndexChangedEventArgs>? ContainerIndexChanged;
/// <summary>
/// Occurs each time a container is cleared.
/// </summary>
/// <remarks>
/// This event is raised immediately each time an container is cleared, such as when it
/// falls outside the range of realized items or the corresponding item is removed.
/// </remarks>
public event EventHandler<ContainerClearingEventArgs>? ContainerClearing;
/// <inheritdoc />
public event EventHandler<RoutedEventArgs> HorizontalSnapPointsChanged
{
@ -649,18 +677,21 @@ namespace Avalonia.Controls
{
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, index));
_scrollViewer?.RegisterAnchorCandidate(container);
ContainerPrepared?.Invoke(this, new(container, index));
}
internal void ItemContainerIndexChanged(Control container, int oldIndex, int newIndex)
{
ContainerIndexChangedOverride(container, oldIndex, newIndex);
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(container, newIndex));
ContainerIndexChanged?.Invoke(this, new(container, oldIndex, newIndex));
}
internal void ClearItemContainer(Control container)
{
_scrollViewer?.UnregisterAnchorCandidate(container);
ClearContainerForItemOverride(container);
ContainerClearing?.Invoke(this, new(container));
}
private void AddControlItemsToLogicalChildren(IEnumerable? items)

3
src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs

@ -69,8 +69,7 @@ namespace Avalonia.Controls.Presenters
var c = children[index + i];
if (!c.IsSet(ItemIsOwnContainerProperty))
itemsControl.RemoveLogicalChild(children[i + index]);
else
generator.ClearItemContainer(c);
generator.ClearItemContainer(c);
}
children.RemoveRange(index, count);

130
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@ -831,6 +832,135 @@ namespace Avalonia.Controls.UnitTests
Assert.Throws<InvalidOperationException>(() => target.DisplayMemberBinding = new Binding("Length"));
}
[Fact]
public void ContainerPrepared_Is_Called_For_Each_Item_Container_On_Layout()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
var result = new List<Control>();
var index = 0;
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(index++, e.Index);
result.Add(e.Container);
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(3, result.Count);
Assert.Equal(target.GetRealizedContainers(), result);
}
[Fact]
public void ContainerPrepared_Is_Called_For_Each_ItemsSource_Container_On_Layout()
{
var target = new ItemsControl
{
Template = GetTemplate(),
ItemsSource = new[] { "Foo", "Bar", "Baz" },
};
var result = new List<Control>();
var index = 0;
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(index++, e.Index);
result.Add(e.Container);
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.Equal(3, result.Count);
Assert.Equal(target.GetRealizedContainers(), result);
}
[Fact]
public void ContainerPrepared_Is_Called_For_Added_Item()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var result = new List<Control>();
target.ContainerPrepared += (s, e) =>
{
Assert.Equal(3, e.Index);
result.Add(e.Container);
};
target.Items.Add("Qux");
Assert.Equal(1, result.Count);
}
[Fact]
public void ContainerIndexChanged_Is_Called_When_Item_Added()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var result = new List<Control>();
var index = 1;
target.ContainerIndexChanged += (s, e) =>
{
Assert.Equal(index++, e.OldIndex);
Assert.Equal(index, e.NewIndex);
result.Add(e.Container);
};
target.Items.Insert(1, "Qux");
Assert.Equal(2, result.Count);
Assert.Equal(target.GetRealizedContainers().Skip(2), result);
}
[Fact]
public void ContainerClearing_Is_Called_When_Item_Removed()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = { "Foo", "Bar", "Baz" },
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var expected = target.ContainerFromIndex(1);
var raised = 0;
target.ContainerClearing += (s, e) =>
{
Assert.Same(expected, e.Container);
++raised;
};
target.Items.RemoveAt(1);
Assert.Equal(1, raised);
}
private class Item
{
public Item(string value)

Loading…
Cancel
Save