Skip to content
5 changes: 5 additions & 0 deletions .ncrunch/Avalonia.Generators.Tests.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
5 changes: 5 additions & 0 deletions .ncrunch/Generators.Sandbox.v3.ncrunchproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
2 changes: 1 addition & 1 deletion samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Get<ComboBox>("fontComboBox");
fontComboBox.Items = FontManager.Current.SystemFonts;
fontComboBox.ItemsSource = FontManager.Current.SystemFonts;
fontComboBox.SelectedIndex = 0;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Avalonia.Metadata;
/// A typical usage example is a ListBox control, where <see cref="InheritDataTypeFromItemsAttribute"/> is defined on the ItemTemplate property,
/// allowing the template to inherit the data type from the Items collection binding.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public sealed class InheritDataTypeFromItemsAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ public class ItemsRepeater : Panel, IChildIndexProvider
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsRepeater, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsRepeater>(o => o.Items, (o, v) => o.Items = v);
AvaloniaProperty.RegisterDirect<ItemsRepeater, IEnumerable?>(
nameof(Items),
o => o.Items,
(o, v) => o.Items = v);

/// <summary>
/// Defines the <see cref="Layout"/> property.
Expand Down
4 changes: 3 additions & 1 deletion src/Avalonia.Controls/Flyouts/MenuFlyout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ public MenuFlyout()
/// Defines the <see cref="Items"/> property
/// </summary>
public static readonly DirectProperty<MenuFlyout, IEnumerable?> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<MenuFlyout>(x => x.Items,
AvaloniaProperty.RegisterDirect<MenuFlyout, IEnumerable?>(
nameof(Items),
x => x.Items,
(x, v) => x.Items = v);

/// <summary>
Expand Down
165 changes: 165 additions & 0 deletions src/Avalonia.Controls/ItemCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Collections;

namespace Avalonia.Controls
{
/// <summary>
/// Holds the list of items that constitute the content of an <see cref="ItemsControl"/>.
/// </summary>
public class ItemCollection : ItemsSourceView, IList
{
// Suppress "Avoid zero-length array allocations": This is a sentinel value and must be unique.
#pragma warning disable CA1825
private static readonly object?[] s_uninitialized = new object?[0];
#pragma warning restore CA1825

private Mode _mode;

internal ItemCollection()
: base(s_uninitialized)
{
}

public new object? this[int index]
{
get => base[index];
set => WritableSource[index] = value;
}

public bool IsReadOnly => _mode == Mode.ItemsSource;

internal event EventHandler? SourceChanged;

/// <summary>
/// Adds an item to the <see cref="ItemsControl"/>.
/// </summary>
/// <param name="value">The item to add to the collection.</param>
/// <returns>
/// The position into which the new element was inserted, or -1 to indicate that
/// the item was not inserted into the collection.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public int Add(object? value) => WritableSource.Add(value);

/// <summary>
/// Clears the collection and releases the references on all items currently in the
/// collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void Clear() => WritableSource.Clear();

/// <summary>
/// Inserts an element into the collection at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which to insert the item.</param>
/// <param name="value">The item to insert.</param>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void Insert(int index, object? value) => WritableSource.Insert(index, value);

/// <summary>
/// Removes the item at the specified index of the collection or view.
/// </summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public void RemoveAt(int index) => WritableSource.RemoveAt(index);

/// <summary>
/// Removes the specified item reference from the collection or view.
/// </summary>
/// <param name="value">The object to remove.</param>
/// <returns>True if the item was removed; otherwise false.</returns>
/// <exception cref="InvalidOperationException">
/// The collection is in ItemsSource mode.
/// </exception>
public bool Remove(object? value)
{
var c = Count;
WritableSource.Remove(value);
return Count < c;
}

int IList.Add(object? value) => Add(value);
void IList.Clear() => Clear();
void IList.Insert(int index, object? value) => Insert(index, value);
void IList.RemoveAt(int index) => RemoveAt(index);

private IList WritableSource
{
get
{
if (IsReadOnly)
ThrowIsItemsSource();
if (Source == s_uninitialized)
SetSource(CreateDefaultCollection());
return Source;
}
}

internal IList? GetItemsPropertyValue()
{
if (_mode == Mode.ObsoleteItemsSetter)
return Source == s_uninitialized ? null : Source;
return this;
}

internal void SetItems(IList? items)
{
_mode = Mode.ObsoleteItemsSetter;
SetSource(items ?? s_uninitialized);
}

internal void SetItemsSource(IEnumerable? value)
{
if (_mode != Mode.ItemsSource && Count > 0)
throw new InvalidOperationException(
"Items collection must be empty before using ItemsSource.");

_mode = value is not null ? Mode.ItemsSource : Mode.Items;
SetSource(value ?? CreateDefaultCollection());
}

private new void SetSource(IEnumerable source)
{
var oldSource = Source;

base.SetSource(source);

if (oldSource.Count > 0)
RaiseCollectionChanged(new(NotifyCollectionChangedAction.Remove, oldSource, 0));
if (Source.Count > 0)
RaiseCollectionChanged(new(NotifyCollectionChangedAction.Add, Source, 0));
SourceChanged?.Invoke(this, EventArgs.Empty);
}

private static AvaloniaList<object?> CreateDefaultCollection()
{
return new() { ResetBehavior = ResetBehavior.Remove };
}

[DoesNotReturn]
private static void ThrowIsItemsSource()
{
throw new InvalidOperationException(
"Operation is not valid while ItemsSource is in use." +
"Access and modify elements with ItemsControl.ItemsSource instead.");
}

private enum Mode
{
Items,
ItemsSource,
ObsoleteItemsSetter,
}
}
}
Loading