AyCode.Core/AyCode.Core/Helpers/AcObservableCollection.cs

442 lines
12 KiB
C#

using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using AyCode.Core.Extensions;
using System.Threading;
namespace AyCode.Core.Helpers
{
public interface IAcObservableCollection
{
public void AddRange(IEnumerable other);
public void Replace(IEnumerable other);
public void RemoveRange(IEnumerable other);
public void Synchronize(NotifyCollectionChangedEventArgs args);
/// <summary>
/// Populates/merges data from object source while suppressing per-item change events.
/// Fires a single Reset event at the end.
/// </summary>
void PopulateFrom(object source);
/// <summary>
/// Populates/merges data from json while suppressing per-item change events.
/// Fires a single Reset event at the end.
/// </summary>
void PopulateFromJson(string json, bool clearAll = false);
/// <summary>
/// Begins a batch update operation. All notifications are suppressed until EndUpdate is called.
/// Supports nested calls - only the outermost EndUpdate triggers the notification.
/// </summary>
public void BeginUpdate();
/// <summary>
/// Ends a batch update operation. Triggers a single Reset notification if this is the outermost call.
/// </summary>
public void EndUpdate();
/// <summary>
/// Forces a Reset notification to refresh bound UI controls.
/// </summary>
public void NotifyReset();
/// <summary>
/// Returns true if currently in a batch update operation.
/// </summary>
public bool IsUpdating { get; }
}
public interface IAcObservableCollection<T> : IAcObservableCollection
{
public void Replace(IEnumerable<T> other);
public void Sort(IComparer<T> comparer);
public void SortAndReplace(IEnumerable<T> other, IComparer<T> comparer);
}
/// <summary>
/// Thread-safe ObservableCollection with batch update support.
/// All public methods are synchronized using a lock.
/// </summary>
public class AcObservableCollection<T> : ObservableCollection<T>, IAcObservableCollection<T>
{
private readonly object _syncRoot = new();
private int _updateCount;
private SynchronizationContext? _synchronizationContext;
/// <summary>
/// Returns true if currently in a batch update operation.
/// </summary>
public bool IsUpdating
{
get
{
lock (_syncRoot)
{
return _updateCount > 0;
}
}
}
/// <summary>
/// Gets the synchronization object for external locking scenarios.
/// </summary>
public object SyncRoot => _syncRoot;
public AcObservableCollection() : base()
{
CaptureSynchronizationContext();
}
public AcObservableCollection(List<T> list) : base(list)
{
CaptureSynchronizationContext();
}
public AcObservableCollection(IEnumerable<T> collection) : base(collection)
{
CaptureSynchronizationContext();
}
/// <summary>
/// Captures the current SynchronizationContext for UI thread marshalling.
/// Should be called from the UI thread during construction.
/// </summary>
private void CaptureSynchronizationContext()
{
_synchronizationContext = SynchronizationContext.Current;
}
/// <summary>
/// Allows setting a custom SynchronizationContext (e.g., from a Blazor component).
/// </summary>
public void SetSynchronizationContext(SynchronizationContext? context)
{
_synchronizationContext = context;
}
public void BeginUpdate()
{
lock (_syncRoot)
{
_updateCount++;
}
}
public void EndUpdate()
{
bool shouldNotify;
lock (_syncRoot)
{
if (_updateCount <= 0) return;
_updateCount--;
shouldNotify = _updateCount == 0;
}
if (shouldNotify) NotifyReset();
}
public void NotifyReset()
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
var propertyArgs = new PropertyChangedEventArgs(nameof(Count));
// Ha van SynchronizationContext és nem azon a szálon vagyunk, marshal-oljuk a hívást
if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
{
_synchronizationContext.Post(_ =>
{
OnCollectionChanged(args);
OnPropertyChanged(propertyArgs);
}, null);
}
else
{
OnCollectionChanged(args);
OnPropertyChanged(propertyArgs);
}
}
public new void Add(T item)
{
lock (_syncRoot)
{
base.Add(item);
}
}
public new bool Remove(T item)
{
lock (_syncRoot)
{
return base.Remove(item);
}
}
public new void Insert(int index, T item)
{
lock (_syncRoot)
{
base.Insert(index, item);
}
}
public new void RemoveAt(int index)
{
lock (_syncRoot)
{
base.RemoveAt(index);
}
}
public new void Clear()
{
lock (_syncRoot)
{
base.Clear();
}
}
public new void Move(int oldIndex, int newIndex)
{
lock (_syncRoot)
{
base.Move(oldIndex, newIndex);
}
}
public new T this[int index]
{
get
{
lock (_syncRoot)
{
return base[index];
}
}
set
{
lock (_syncRoot)
{
base[index] = value;
}
}
}
public new int Count
{
get
{
lock (_syncRoot)
{
return base.Count;
}
}
}
public new bool Contains(T item)
{
lock (_syncRoot)
{
return base.Contains(item);
}
}
public new int IndexOf(T item)
{
lock (_syncRoot)
{
return base.IndexOf(item);
}
}
public new void CopyTo(T[] array, int arrayIndex)
{
lock (_syncRoot)
{
base.CopyTo(array, arrayIndex);
}
}
/// <summary>
/// Returns a snapshot copy of the collection for safe enumeration.
/// </summary>
public List<T> ToList()
{
lock (_syncRoot)
{
return [.. this.Items];
}
}
public void Replace(IEnumerable<T> other)
{
BeginUpdate();
try
{
lock (_syncRoot)
{
base.Clear();
foreach (var item in other) base.Add(item);
}
}
finally
{
EndUpdate();
}
}
public void Replace(IEnumerable other)
{
BeginUpdate();
try
{
lock (_syncRoot)
{
base.Clear();
foreach (T item in other) base.Add(item);
}
}
finally
{
EndUpdate();
}
}
public void AddRange(IEnumerable other)
{
BeginUpdate();
try
{
lock (_syncRoot)
{
foreach (var item in other)
{
if (item is T tItem) base.Add(tItem);
}
}
}
finally
{
EndUpdate();
}
}
public void RemoveRange(IEnumerable other)
{
BeginUpdate();
try
{
lock (_syncRoot)
{
foreach (var item in other)
{
if (item is T tItem) base.Remove(tItem);
}
}
}
finally
{
EndUpdate();
}
}
public void PopulateFrom(object source)
{
switch (source)
{
case IEnumerable<T> typedSource:
Replace(typedSource);
break;
case IEnumerable enumerable:
Replace(enumerable);
break;
}
}
public void PopulateFromJson(string json, bool clearAll = false)
{
BeginUpdate();
try
{
lock (_syncRoot)
{
if (clearAll) base.Clear();
json.JsonTo(this.Items);
}
}
finally
{
EndUpdate();
}
}
public void SortAndReplace(IEnumerable<T> other, IComparer<T> comparer)
{
var values = new List<T>(other);
values.Sort(comparer);
Replace(values);
}
public void Sort(IComparer<T> comparer)
{
List<T> values;
lock (_syncRoot)
{
values = new List<T>(this.Items);
}
values.Sort(comparer);
Replace(values);
}
public void Synchronize(NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add when args.NewItems != null:
AddRange(args.NewItems);
break;
case NotifyCollectionChangedAction.Remove when args.OldItems != null:
RemoveRange(args.OldItems);
break;
case NotifyCollectionChangedAction.Reset:
Clear();
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (IsUpdating) return;
try
{
base.OnPropertyChanged(e);
}
catch (ObjectDisposedException)
{
// A feliratkozott komponens már Disposed - biztonságosan figyelmen kívül hagyjuk
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (IsUpdating) return;
try
{
base.OnCollectionChanged(e);
}
catch (ObjectDisposedException)
{
// A feliratkozott komponens már Disposed - biztonságosan figyelmen kívül hagyjuk
}
}
}
}