Browse Source

ItemsControl+ItemVirtualizerSimple did not recreated item containers when Items or ItemTemplate were replaced

pull/5014/head
adospace 5 years ago
parent
commit
13cd835bc0
  1. 2
      global.json
  2. 6
      src/Avalonia.Controls/ItemsControl.cs
  3. 4
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  4. 4
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  5. 92
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  6. 112
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

2
global.json

@ -1,6 +1,6 @@
{
"sdk": {
"version": "3.1.401"
"version": "3.1.301"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",

6
src/Avalonia.Controls/ItemsControl.cs

@ -449,7 +449,11 @@ namespace Avalonia.Controls
if (_itemContainerGenerator != null)
{
_itemContainerGenerator.ItemTemplate = (IDataTemplate)e.NewValue;
// TODO: Rebuild the item containers.
if (e.OldValue != null && Presenter != null)
{
Presenter.ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}

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

@ -200,6 +200,10 @@ namespace Avalonia.Controls.Presenters
break;
case NotifyCollectionChangedAction.Reset:
Owner.ItemContainerGenerator.Clear();
VirtualizingPanel.Children.Clear();
FirstIndex = NextIndex = 0;
RecycleContainersOnRemove();
CreateAndRemoveContainers();
panel.ForceInvalidateMeasure();

4
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -57,6 +57,8 @@ namespace Avalonia.Controls.Presenters
set
{
var itemsReplaced = (_items != value);
_itemsSubscription?.Dispose();
_itemsSubscription = null;
@ -67,7 +69,7 @@ namespace Avalonia.Controls.Presenters
SetAndRaise(ItemsProperty, ref _items, value);
if (_createdPanel)
if (_createdPanel && itemsReplaced)
{
ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

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

@ -596,6 +596,98 @@ namespace Avalonia.Controls.UnitTests
root.Child = target;
}
[Fact]
public void Presenter_Items_Should_Be_In_Sync_When_Replacing_Items()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = new[]
{
new Item("Item1")
}
};
var root = new TestRoot { Child = target };
var otherPanel = new StackPanel();
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
int dematerializedEventCallCount = 0;
target.ItemContainerGenerator.Dematerialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
dematerializedEventCallCount++;
};
int materializedEventCallCount = 0;
target.ItemContainerGenerator.Materialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item2", ((Item)e.Containers[0].Item).Value);
materializedEventCallCount++;
};
target.Items = new[]
{
new Item("Item2")
};
//Ensure that events are called one time only
Assert.Equal(1, dematerializedEventCallCount);
Assert.Equal(1, materializedEventCallCount);
}
[Fact]
public void Presenter_Items_Should_Be_In_Sync_When_Replacing_ItemTemplate()
{
var target = new ItemsControl
{
Template = GetTemplate(),
Items = new[]
{
new Item("Item1")
},
ItemTemplate = new FuncDataTemplate<Item>((x, ns) => new TextBlock())
};
var root = new TestRoot { Child = target };
var otherPanel = new StackPanel();
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
int dematerializedEventCallCount = 0;
target.ItemContainerGenerator.Dematerialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
var contentPresenter = ((ContentPresenter)e.Containers[0].ContainerControl);
contentPresenter.UpdateChild();
Assert.IsType<TextBlock>(contentPresenter.Child);
dematerializedEventCallCount++;
};
int materializedEventCallCount = 0;
target.ItemContainerGenerator.Materialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
var contentPresenter = ((ContentPresenter)e.Containers[0].ContainerControl);
contentPresenter.UpdateChild();
Assert.IsType<Canvas>(contentPresenter.Child);
materializedEventCallCount++;
};
target.ItemTemplate =
new FuncDataTemplate<Item>((x, ns) => new Canvas());
Assert.Equal(1, dematerializedEventCallCount);
Assert.Equal(1, materializedEventCallCount);
}
private class Item
{
public Item(string value)

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

@ -454,6 +454,118 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void ListBox_Presenter_Items_Should_Be_In_Sync_When_Replacing_Items()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
var target = new ListBox()
{
VerticalAlignment = Layout.VerticalAlignment.Top,
AutoScrollToSelectedItem = true,
Width = 50,
VirtualizationMode = ItemVirtualizationMode.Simple,
Items = new[]
{
new Item("Item1")
},
};
wnd.Content = target;
var lm = wnd.LayoutManager;
lm.ExecuteInitialLayoutPass();
int dematerializedEventCallCount = 0;
target.ItemContainerGenerator.Dematerialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
dematerializedEventCallCount++;
};
int materializedEventCallCount = 0;
target.ItemContainerGenerator.Materialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item2", ((Item)e.Containers[0].Item).Value);
materializedEventCallCount++;
};
target.Items = new[]
{
new Item("Item2")
};
//assert that materialize/dematerialize events are called exactly one time
Assert.Equal(1, dematerializedEventCallCount);
Assert.Equal(1, materializedEventCallCount);
}
}
[Fact]
public void ListBox_Items_Should_Be_In_Sync_When_Replacing_ItemTemplate()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
var target = new ListBox()
{
VerticalAlignment = Layout.VerticalAlignment.Top,
AutoScrollToSelectedItem = true,
Width = 50,
VirtualizationMode = ItemVirtualizationMode.Simple,
Items = new[]
{
new Item("Item1")
},
ItemTemplate =
new FuncDataTemplate<Item>((x, ns) => new Canvas())
};
wnd.Content = target;
var lm = wnd.LayoutManager;
lm.ExecuteInitialLayoutPass();
int dematerializedEventCallCount = 0;
target.ItemContainerGenerator.Dematerialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
Assert.IsType<Canvas>(((ListBoxItem)e.Containers[0].ContainerControl).Presenter.Child);
dematerializedEventCallCount++;
};
int materializedEventCallCount = 0;
ListBoxItem materializedListBoxItem = null;
target.ItemContainerGenerator.Materialized += (s, e) =>
{
Assert.IsType<Item>(e.Containers[0].Item);
Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value);
materializedListBoxItem = ((ListBoxItem)e.Containers[0].ContainerControl);
materializedEventCallCount++;
};
target.ItemTemplate =
new FuncDataTemplate<Item>((x, ns) => new TextBlock());
//ensure events are called only one time
Assert.Equal(1, dematerializedEventCallCount);
Assert.Equal(1, materializedEventCallCount);
wnd.LayoutManager.ExecuteLayoutPass();
//ensure that new template has been applied
Assert.IsType<TextBlock>(materializedListBoxItem.Presenter.Child);
}
}
private FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate<ListBox>((parent, scope) =>

Loading…
Cancel
Save