XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reflection;

namespace XFEEDToolBox.Core.Presentation;

/// <summary>A stable UI identity over immutable snapshots; only changed exposed properties notify.</summary>
public abstract class LiveRow<T>(T value) : INotifyPropertyChanged where T : class
{
    public T Record { get; private set; } = value;
    public event PropertyChangedEventHandler? PropertyChanged;
    private PropertyInfo[]? displayProperties;
    public bool Update(T next)
    {
        if (EqualityComparer<T>.Default.Equals(Record, next)) return false;
        displayProperties ??= GetType().GetProperties().Where(p => p.Name != nameof(Record) && p.CanRead && p.GetIndexParameters().Length == 0).ToArray();
        var before = displayProperties.Select(p => p.GetValue(this)).ToArray();
        Record = next;
        var changed=false;
        for (var i = 0; i < displayProperties.Length; i++)
            if (!Equals(before[i], displayProperties[i].GetValue(this)))
            {
                changed=true;
                PropertyChanged?.Invoke(this, new(displayProperties[i].Name));
            }
        return changed;
    }
}

public sealed record ListDelta(int Added, int Removed, int Updated, int Moved)
{
    public bool Changed => Added + Removed + Updated + Moved > 0;
}

/// <summary>Never resets the collection or replaces surviving rows. Reorders with Move only when requested.</summary>
public sealed class LiveCollection<TRow, TValue, TKey>(Func<TValue,TKey> key, Func<TValue,TRow> create)
    : ObservableCollection<TRow> where TRow : LiveRow<TValue> where TValue : class where TKey : notnull
{
    public ListDelta Apply(IEnumerable<TValue> values, bool reorder = true)
    {
        var next = values.DistinctBy(key).ToArray();
        var wanted = next.Select(key).ToHashSet();
        var byKey = this.ToDictionary(row => key(row.Record));
        var removed = 0; var added = 0; var updated = 0; var moved = 0;
        for (var i = Count - 1; i >= 0; i--)
            if (!wanted.Contains(key(this[i].Record))) { RemoveAt(i); removed++; }
        var position = 0;
        foreach (var value in next)
        {
            if (byKey.TryGetValue(key(value), out var row))
            {
                if (row.Update(value)) updated++;
                if (reorder && (position >= Count || !ReferenceEquals(this[position], row)))
                {
                    var current = IndexOf(row);
                    if (current != position) { Move(current, position); moved++; }
                }
            }
            else
            {
                row = create(value);
                if (reorder) Insert(position, row); else Add(row);
                added++;
            }
            position++;
        }
        return new(added, removed, updated, moved);
    }
}