Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ItemsControl.ItemsSource. #10590

Merged
merged 12 commits into from
Mar 16, 2023
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