// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Controls.Utils;
namespace Avalonia.Controls
{
///
/// Represents a standardized view of the supported interactions between an items collection
/// and an items control.
///
public class ItemsSourceView : IReadOnlyList,
INotifyCollectionChanged,
ICollectionChangedListener
{
///
/// Gets an empty
///
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
private readonly IList _inner;
private NotifyCollectionChangedEventHandler? _collectionChanged;
private NotifyCollectionChangedEventHandler? _postCollectionChanged;
private bool _listening;
///
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
///
/// The data source.
private protected ItemsSourceView(IEnumerable source)
{
_inner = source switch
{
ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)),
IList list => list,
INotifyCollectionChanged => throw new ArgumentException(
"Collection implements INotifyCollectionChanged but not IList.",
nameof(source)),
IEnumerable iObj => new List(iObj),
null => throw new ArgumentNullException(nameof(source)),
_ => new List(source.Cast())
};
}
///
/// Gets the number of items in the collection.
///
public int Count => Inner.Count;
///
/// Gets the inner collection.
///
public IList Inner => _inner;
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
public object? this[int index] => GetAt(index);
///
/// Gets a value that indicates whether the items source can provide a unique key for each item.
///
///
/// TODO: Not yet implemented in Avalonia.
///
internal bool HasKeyIndexMapping => false;
///
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
///
public event NotifyCollectionChangedEventHandler? CollectionChanged
{
add
{
AddListenerIfNecessary();
_collectionChanged += value;
}
remove
{
_collectionChanged -= value;
RemoveListenerIfNecessary();
}
}
///
/// Occurs when a collection has finished changing and all
/// event handlers have been notified.
///
internal event NotifyCollectionChangedEventHandler? PostCollectionChanged
{
add
{
AddListenerIfNecessary();
_postCollectionChanged += value;
}
remove
{
_postCollectionChanged -= value;
RemoveListenerIfNecessary();
}
}
private void AddListenerIfNecessary()
{
if (!_listening)
{
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.AddListener(incc, this);
_listening = true;
}
}
private void RemoveListenerIfNecessary()
{
if (_listening && _collectionChanged is null && _postCollectionChanged is null)
{
if (_inner is INotifyCollectionChanged incc)
CollectionChangedEventManager.Instance.RemoveListener(incc, this);
_listening = false;
}
}
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
public object? GetAt(int index) => Inner[index];
///
/// Determines the index of a specific item in the collection.
///
/// The object to locate in the collection.
/// The index of value if found in the list; otherwise, -1.
public int IndexOf(object? item) => Inner.IndexOf(item);
///
/// Gets or creates an for the specified enumerable.
///
/// The enumerable.
///
/// This method handles the following three cases:
/// - If is null, returns
/// - If is an returns the existing
///
/// - Otherwise creates a new
///
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
return items switch
{
ItemsSourceView isv => isv,
null => Empty,
_ => new ItemsSourceView(items)
};
}
///
/// Gets or creates an for the specified enumerable.
///
/// The enumerable.
///
/// This method handles the following three cases:
/// - If is null, returns
/// - If is an returns the existing
///
/// - Otherwise creates a new
///
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
return items switch
{
ItemsSourceView isv => isv,
null => ItemsSourceView.Empty,
_ => new ItemsSourceView(items)
};
}
///
/// Gets or creates an for the specified enumerable.
///
/// The enumerable.
///
/// This method handles the following three cases:
/// - If is null, returns
/// - If is an returns the existing
///
/// - Otherwise creates a new
///
public static ItemsSourceView GetOrCreate(IEnumerable? items)
{
return items switch
{
ItemsSourceView isv => isv,
null => ItemsSourceView.Empty,
_ => new ItemsSourceView(items)
};
}
public IEnumerator GetEnumerator()
{
static IEnumerator EnumerateItems(IList list)
{
foreach (var o in list)
yield return o;
}
var inner = Inner;
return inner switch
{
IEnumerable e => e.GetEnumerator(),
_ => EnumerateItems(inner),
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
}
void ICollectionChangedListener.Changed(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
_collectionChanged?.Invoke(this, e);
}
void ICollectionChangedListener.PostChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
{
_postCollectionChanged?.Invoke(this, e);
}
///
/// Retrieves the index of the item that has the specified unique identifier (key).
///
/// The index.
/// The key
///
/// TODO: Not yet implemented in Avalonia.
///
internal string KeyFromIndex(int index) => throw new NotImplementedException();
}
public sealed class ItemsSourceView : ItemsSourceView, IReadOnlyList
{
///
/// Gets an empty
///
public new static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty());
///
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
///
/// The data source.
internal ItemsSourceView(IEnumerable source)
: base(source)
{
}
internal ItemsSourceView(IEnumerable source)
: base(source)
{
}
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
public new T this[int index] => GetAt(index);
///
/// Retrieves the item at the specified index.
///
/// The index.
/// The item.
public new T GetAt(int index) => (T)Inner[index]!;
public new IEnumerator GetEnumerator()
{
static IEnumerator EnumerateItems(IList list)
{
foreach (var o in list)
yield return (T)o;
}
var inner = Inner;
return inner switch
{
IEnumerable e => e.GetEnumerator(),
_ => EnumerateItems(inner),
};
}
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
}
}