csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
8.5 KiB
264 lines
8.5 KiB
// 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.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using Avalonia.Controls.Utils;
|
|
|
|
#nullable enable
|
|
|
|
namespace Avalonia.Controls
|
|
{
|
|
/// <summary>
|
|
/// Represents a standardized view of the supported interactions between a given ItemsSource
|
|
/// object and an <see cref="ItemsRepeater"/> control.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Components written to work with ItemsRepeater should consume the
|
|
/// <see cref="ItemsRepeater.Items"/> via ItemsSourceView since this provides a normalized
|
|
/// view of the Items. That way, each component does not need to know if the source is an
|
|
/// IEnumerable, an IList, or something else.
|
|
/// </remarks>
|
|
public class ItemsSourceView : INotifyCollectionChanged, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Gets an empty <see cref="ItemsSourceView"/>
|
|
/// </summary>
|
|
public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty<object>());
|
|
|
|
private IList? _inner;
|
|
private NotifyCollectionChangedEventHandler? _collectionChanged;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
|
|
/// </summary>
|
|
/// <param name="source">The data source.</param>
|
|
public ItemsSourceView(IEnumerable source)
|
|
{
|
|
source = source ?? throw new ArgumentNullException(nameof(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 by not IList.",
|
|
nameof(source)),
|
|
IEnumerable<object> iObj => new List<object>(iObj),
|
|
_ => new List<object>(source.Cast<object>())
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of items in the collection.
|
|
/// </summary>
|
|
public int Count => Inner.Count;
|
|
|
|
/// <summary>
|
|
/// Gets a value that indicates whether the items source can provide a unique key for each item.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// TODO: Not yet implemented in Avalonia.
|
|
/// </remarks>
|
|
public bool HasKeyIndexMapping => false;
|
|
|
|
/// <summary>
|
|
/// Gets the inner collection.
|
|
/// </summary>
|
|
public IList Inner
|
|
{
|
|
get
|
|
{
|
|
if (_inner is null)
|
|
ThrowDisposed();
|
|
return _inner!;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the item at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index.</param>
|
|
/// <returns>The item.</returns>
|
|
public object? this[int index] => GetAt(index);
|
|
|
|
/// <summary>
|
|
/// Occurs when the collection has changed to indicate the reason for the change and which items changed.
|
|
/// </summary>
|
|
public event NotifyCollectionChangedEventHandler? CollectionChanged
|
|
{
|
|
add
|
|
{
|
|
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
|
|
{
|
|
incc.CollectionChanged += OnCollectionChanged;
|
|
}
|
|
|
|
_collectionChanged += value;
|
|
}
|
|
|
|
remove
|
|
{
|
|
_collectionChanged -= value;
|
|
|
|
if (_collectionChanged is null && Inner is INotifyCollectionChanged incc)
|
|
{
|
|
incc.CollectionChanged -= OnCollectionChanged;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public void Dispose()
|
|
{
|
|
if (_inner is INotifyCollectionChanged incc)
|
|
{
|
|
incc.CollectionChanged -= OnCollectionChanged;
|
|
}
|
|
|
|
_inner = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the item at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index.</param>
|
|
/// <returns>The item.</returns>
|
|
public object? GetAt(int index) => Inner[index];
|
|
|
|
public int IndexOf(object? item) => Inner.IndexOf(item);
|
|
|
|
public static ItemsSourceView GetOrCreate(IEnumerable? items)
|
|
{
|
|
if (items is ItemsSourceView isv)
|
|
{
|
|
return isv;
|
|
}
|
|
else if (items is null)
|
|
{
|
|
return Empty;
|
|
}
|
|
else
|
|
{
|
|
return new ItemsSourceView(items);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the index of the item that has the specified unique identifier (key).
|
|
/// </summary>
|
|
/// <param name="index">The index.</param>
|
|
/// <returns>The key</returns>
|
|
/// <remarks>
|
|
/// TODO: Not yet implemented in Avalonia.
|
|
/// </remarks>
|
|
public string KeyFromIndex(int index)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the unique identifier (key) for the item at the specified index.
|
|
/// </summary>
|
|
/// <param name="key">The key.</param>
|
|
/// <returns>The index.</returns>
|
|
/// <remarks>
|
|
/// TODO: Not yet implemented in Avalonia.
|
|
/// </remarks>
|
|
public int IndexFromKey(string key)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
internal void AddListener(ICollectionChangedListener listener)
|
|
{
|
|
if (Inner is INotifyCollectionChanged incc)
|
|
{
|
|
CollectionChangedEventManager.Instance.AddListener(incc, listener);
|
|
}
|
|
}
|
|
|
|
internal void RemoveListener(ICollectionChangedListener listener)
|
|
{
|
|
if (Inner is INotifyCollectionChanged incc)
|
|
{
|
|
CollectionChangedEventManager.Instance.RemoveListener(incc, listener);
|
|
}
|
|
}
|
|
|
|
protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args)
|
|
{
|
|
_collectionChanged?.Invoke(this, args);
|
|
}
|
|
|
|
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
OnItemsSourceChanged(e);
|
|
}
|
|
|
|
private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView));
|
|
}
|
|
|
|
public class ItemsSourceView<T> : ItemsSourceView, IReadOnlyList<T>
|
|
{
|
|
/// <summary>
|
|
/// Gets an empty <see cref="ItemsSourceView"/>
|
|
/// </summary>
|
|
public new static ItemsSourceView<T> Empty { get; } = new ItemsSourceView<T>(Array.Empty<T>());
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ItemsSourceView class for the specified data source.
|
|
/// </summary>
|
|
/// <param name="source">The data source.</param>
|
|
public ItemsSourceView(IEnumerable<T> source)
|
|
: base(source)
|
|
{
|
|
}
|
|
|
|
private ItemsSourceView(IEnumerable source)
|
|
: base(source)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the item at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index.</param>
|
|
/// <returns>The item.</returns>
|
|
#pragma warning disable CS8603
|
|
public new T this[int index] => GetAt(index);
|
|
#pragma warning restore CS8603
|
|
|
|
/// <summary>
|
|
/// Retrieves the item at the specified index.
|
|
/// </summary>
|
|
/// <param name="index">The index.</param>
|
|
/// <returns>The item.</returns>
|
|
[return: MaybeNull]
|
|
public new T GetAt(int index) => (T)Inner[index];
|
|
|
|
public IEnumerator<T> GetEnumerator() => Inner.Cast<T>().GetEnumerator();
|
|
IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator();
|
|
|
|
public static new ItemsSourceView<T> GetOrCreate(IEnumerable? items)
|
|
{
|
|
if (items is ItemsSourceView<T> isv)
|
|
{
|
|
return isv;
|
|
}
|
|
else if (items is null)
|
|
{
|
|
return Empty;
|
|
}
|
|
else
|
|
{
|
|
return new ItemsSourceView<T>(items);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|