diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs
index 41b57b6e70..027028480c 100644
--- a/src/Avalonia.Base/Utilities/MathUtilities.cs
+++ b/src/Avalonia.Base/Utilities/MathUtilities.cs
@@ -159,6 +159,11 @@ namespace Avalonia.Utilities
/// The clamped value.
public static int Clamp(int val, int min, int max)
{
+ if (min > max)
+ {
+ throw new ArgumentException($"{min} cannot be greater than {max}.");
+ }
+
if (val < min)
{
return min;
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index b027da6d0c..bf22f0a08a 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -359,6 +359,12 @@ namespace Avalonia.Controls
UpdateItemCount();
RemoveControlItemsFromLogicalChildren(oldValue);
AddControlItemsToLogicalChildren(newValue);
+
+ if (Presenter != null)
+ {
+ Presenter.Items = newValue;
+ }
+
SubscribeToItems(newValue);
}
@@ -370,6 +376,8 @@ namespace Avalonia.Controls
/// The event args.
protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
+ UpdateItemCount();
+
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@@ -381,7 +389,7 @@ namespace Avalonia.Controls
break;
}
- UpdateItemCount();
+ Presenter?.ItemsChanged(e);
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
diff --git a/src/Avalonia.Controls/Presenters/IItemsPresenter.cs b/src/Avalonia.Controls/Presenters/IItemsPresenter.cs
index 42311dc781..c4acf1ebef 100644
--- a/src/Avalonia.Controls/Presenters/IItemsPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/IItemsPresenter.cs
@@ -1,12 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System.Collections;
+using System.Collections.Specialized;
+
namespace Avalonia.Controls.Presenters
{
public interface IItemsPresenter : IPresenter
{
+ IEnumerable Items { get; set; }
+
IPanel Panel { get; }
+ void ItemsChanged(NotifyCollectionChangedEventArgs e);
+
void ScrollIntoView(object item);
}
}
diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
index 0f0cdc37cf..ef1f277162 100644
--- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
+++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
@@ -63,7 +63,7 @@ namespace Avalonia.Controls.Presenters
_itemsSubscription?.Dispose();
_itemsSubscription = null;
- if (_createdPanel && value is INotifyCollectionChanged incc)
+ if (!IsHosted && _createdPanel && value is INotifyCollectionChanged incc)
{
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
}
@@ -130,6 +130,8 @@ namespace Avalonia.Controls.Presenters
private set;
}
+ protected bool IsHosted => TemplatedParent is IItemsPresenterHost;
+
///
public override sealed void ApplyTemplate()
{
@@ -144,6 +146,15 @@ namespace Avalonia.Controls.Presenters
{
}
+ ///
+ void IItemsPresenter.ItemsChanged(NotifyCollectionChangedEventArgs e)
+ {
+ if (Panel != null)
+ {
+ ItemsChanged(e);
+ }
+ }
+
///
/// Creates the for the control.
///
@@ -215,7 +226,7 @@ namespace Avalonia.Controls.Presenters
_createdPanel = true;
- if (_itemsSubscription == null && Items is INotifyCollectionChanged incc)
+ if (!IsHosted && _itemsSubscription == null && Items is INotifyCollectionChanged incc)
{
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
}
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 4b33b18475..b6ae567123 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -302,13 +302,24 @@ namespace Avalonia.Controls.Primitives
///
protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
- base.ItemsCollectionChanged(sender, e);
-
if (_updateCount > 0)
{
+ base.ItemsCollectionChanged(sender, e);
return;
}
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ _selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ _selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
+ break;
+ }
+
+ base.ItemsCollectionChanged(sender, e);
+
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@@ -318,14 +329,12 @@ namespace Avalonia.Controls.Primitives
}
else
{
- _selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
UpdateSelectedItem(_selection.First(), false);
}
break;
case NotifyCollectionChangedAction.Remove:
- _selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
UpdateSelectedItem(_selection.First(), false);
ResetSelectedItems();
break;
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index b2839360ee..227d783874 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -12,6 +12,7 @@ using Xunit;
using System.Collections.ObjectModel;
using Avalonia.UnitTests;
using Avalonia.Input;
+using System.Collections.Generic;
namespace Avalonia.Controls.UnitTests
{
@@ -104,6 +105,28 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child }, target.GetLogicalChildren());
}
+ [Fact]
+ public void Added_Container_Should_Have_LogicalParent_Set_To_ItemsControl()
+ {
+ var item = new Border();
+ var items = new ObservableCollection();
+
+ var target = new ItemsControl
+ {
+ Template = GetTemplate(),
+ Items = items,
+ };
+
+ var root = new TestRoot(true, target);
+
+ root.Measure(new Size(100, 100));
+ root.Arrange(new Rect(0, 0, 100, 100));
+
+ items.Add(item);
+
+ Assert.Equal(target, item.Parent);
+ }
+
[Fact]
public void Control_Item_Should_Be_Removed_From_Logical_Children_Before_ApplyTemplate()
{
@@ -522,6 +545,36 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Presenter_Items_Should_Be_In_Sync()
+ {
+ var target = new ItemsControl
+ {
+ Template = GetTemplate(),
+ Items = new object[]
+ {
+ new Button(),
+ new Button(),
+ },
+ };
+
+ var root = new TestRoot { Child = target };
+ var otherPanel = new StackPanel();
+
+ target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
+
+ target.ItemContainerGenerator.Materialized += (s, e) =>
+ {
+ Assert.IsType