442 lines
12 KiB
C#
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
|
|
}
|
|
}
|
|
}
|
|
} |