mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refine #1334
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
/// <summary>
|
||||
/// A collection view implementation that supports filtering, grouping, sorting and incremental loading
|
||||
/// </summary>
|
||||
internal partial class AdvancedCollectionView
|
||||
{
|
||||
/// <summary>
|
||||
/// Stops refreshing until it is disposed
|
||||
/// </summary>
|
||||
/// <returns>An disposable object</returns>
|
||||
public IDisposable DeferRefresh()
|
||||
{
|
||||
return new NotificationDeferrer(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notification deferrer helper class
|
||||
/// </summary>
|
||||
#pragma warning disable CA1063 // Implement IDisposable Correctly
|
||||
public class NotificationDeferrer : IDisposable
|
||||
#pragma warning restore CA1063 // Implement IDisposable Correctly
|
||||
{
|
||||
private readonly AdvancedCollectionView _acvs;
|
||||
private readonly object _currentItem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationDeferrer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="acvs">Source ACVS</param>
|
||||
public NotificationDeferrer(AdvancedCollectionView acvs)
|
||||
{
|
||||
_acvs = acvs;
|
||||
_currentItem = _acvs.CurrentItem;
|
||||
_acvs._deferCounter++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
#pragma warning disable CA1063 // Implement IDisposable Correctly
|
||||
public void Dispose()
|
||||
#pragma warning restore CA1063 // Implement IDisposable Correctly
|
||||
{
|
||||
_acvs.MoveCurrentTo(_currentItem);
|
||||
_acvs._deferCounter--;
|
||||
_acvs.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
/// <summary>
|
||||
/// A collection view implementation that supports filtering, grouping, sorting and incremental loading
|
||||
/// </summary>
|
||||
internal partial class AdvancedCollectionView
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently selected item changing event
|
||||
/// </summary>
|
||||
/// <param name="e">event args</param>
|
||||
private void OnCurrentChanging(CurrentChangingEventArgs e)
|
||||
{
|
||||
if (_deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentChanging?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected item changed event
|
||||
/// </summary>
|
||||
/// <param name="e">event args</param>
|
||||
private void OnCurrentChanged(object e)
|
||||
{
|
||||
if (_deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentChanged?.Invoke(this, e);
|
||||
|
||||
// ReSharper disable once ExplicitCallerInfoArgument
|
||||
OnPropertyChanged(nameof(CurrentItem));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vector changed event
|
||||
/// </summary>
|
||||
/// <param name="e">event args</param>
|
||||
private void OnVectorChanged(IVectorChangedEventArgs e)
|
||||
{
|
||||
if (_deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VectorChanged?.Invoke(this, e);
|
||||
|
||||
// ReSharper disable once ExplicitCallerInfoArgument
|
||||
OnPropertyChanged(nameof(Count));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using NotifyCollectionChangedAction = global::System.Collections.Specialized.NotifyCollectionChangedAction;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
/// <summary>
|
||||
/// A collection view implementation that supports filtering, sorting and incremental loading
|
||||
/// https://github.com/CommunityToolkit/Windows/pull/309
|
||||
/// </summary>
|
||||
internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<object>
|
||||
{
|
||||
private readonly List<object> _view;
|
||||
|
||||
private readonly ObservableCollection<SortDescription> _sortDescriptions;
|
||||
|
||||
private readonly Dictionary<string, PropertyInfo> _sortProperties;
|
||||
|
||||
private readonly bool _liveShapingEnabled;
|
||||
|
||||
private readonly HashSet<string> _observedFilterProperties = new HashSet<string>();
|
||||
|
||||
private IList _source;
|
||||
|
||||
private Predicate<object> _filter;
|
||||
private int _deferCounter;
|
||||
|
||||
private WeakEventListener<AdvancedCollectionView, object, NotifyCollectionChangedEventArgs> _sourceWeakEventListener;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdvancedCollectionView"/> class.
|
||||
/// </summary>
|
||||
public AdvancedCollectionView()
|
||||
: this(new List<object>(0))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AdvancedCollectionView"/> class.
|
||||
/// </summary>
|
||||
/// <param name="source">source IEnumerable</param>
|
||||
/// <param name="isLiveShaping">Denotes whether or not this ACV should re-filter/re-sort if a PropertyChanged is raised for an observed property.</param>
|
||||
#pragma warning disable CS8767
|
||||
#pragma warning disable CS8769
|
||||
#pragma warning disable CS8622
|
||||
#pragma warning disable CS8600
|
||||
#pragma warning disable CS8601
|
||||
#pragma warning disable CS8604
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
public AdvancedCollectionView(IList source, bool isLiveShaping = false)
|
||||
{
|
||||
_liveShapingEnabled = isLiveShaping;
|
||||
_view = new List<object>();
|
||||
_sortDescriptions = new ObservableCollection<SortDescription>();
|
||||
_sortDescriptions.CollectionChanged += SortDescriptions_CollectionChanged;
|
||||
_sortProperties = new Dictionary<string, PropertyInfo>();
|
||||
Source = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source
|
||||
/// </summary>
|
||||
public IList Source
|
||||
{
|
||||
get
|
||||
{
|
||||
return _source;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// ReSharper disable once PossibleUnintendedReferenceComparison
|
||||
if (_source == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_source != null)
|
||||
{
|
||||
DetachPropertyChangedHandler(_source);
|
||||
}
|
||||
|
||||
_source = value;
|
||||
AttachPropertyChangedHandler(_source);
|
||||
|
||||
_sourceWeakEventListener?.Detach();
|
||||
|
||||
if (_source is INotifyCollectionChanged sourceNcc)
|
||||
{
|
||||
_sourceWeakEventListener =
|
||||
new WeakEventListener<AdvancedCollectionView, object, NotifyCollectionChangedEventArgs>(this)
|
||||
{
|
||||
// Call the actual collection changed event
|
||||
OnEventAction = (source, changed, arg3) => SourceNcc_CollectionChanged(source, arg3),
|
||||
|
||||
// The source doesn't exist anymore
|
||||
OnDetachAction = (listener) => sourceNcc.CollectionChanged -= _sourceWeakEventListener!.OnEvent
|
||||
};
|
||||
sourceNcc.CollectionChanged += _sourceWeakEventListener.OnEvent;
|
||||
}
|
||||
|
||||
HandleSourceChanged();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually refresh the view
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RefreshFilter()
|
||||
{
|
||||
HandleFilterChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RefreshSorting()
|
||||
{
|
||||
HandleSortChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<object> GetEnumerator() => _view.GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => _view.GetEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(object item)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
_source.Add(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
_source.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(object item) => _view.Contains(item);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(object[] array, int arrayIndex) => _view.CopyTo(array, arrayIndex);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(object item)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
_source.Remove(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _view.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => _source == null || _source.IsReadOnly;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(object item) => _view.IndexOf(item);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, object item)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
throw new NotSupportedException("Collection is read-only.");
|
||||
}
|
||||
|
||||
_source.Insert(index, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
|
||||
public void RemoveAt(int index) => Remove(_view[index]);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element at the specified index.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The element at the specified index.
|
||||
/// </returns>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception><exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
|
||||
public object this[int index]
|
||||
{
|
||||
get { return _view[index]; }
|
||||
set { _view[index] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the vector changes.
|
||||
/// </summary>
|
||||
public event Windows.Foundation.Collections.VectorChangedEventHandler<object> VectorChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Move current index to item
|
||||
/// </summary>
|
||||
/// <param name="item">item</param>
|
||||
/// <returns>success of operation</returns>
|
||||
public bool MoveCurrentTo(object item) => item == CurrentItem || MoveCurrentToIndex(IndexOf(item));
|
||||
|
||||
/// <summary>
|
||||
/// Moves selected item to position
|
||||
/// </summary>
|
||||
/// <param name="index">index</param>
|
||||
/// <returns>success of operation</returns>
|
||||
public bool MoveCurrentToPosition(int index) => MoveCurrentToIndex(index);
|
||||
|
||||
/// <summary>
|
||||
/// Move current item to first item
|
||||
/// </summary>
|
||||
/// <returns>success of operation</returns>
|
||||
public bool MoveCurrentToFirst() => MoveCurrentToIndex(0);
|
||||
|
||||
/// <summary>
|
||||
/// Move current item to last item
|
||||
/// </summary>
|
||||
/// <returns>success of operation</returns>
|
||||
public bool MoveCurrentToLast() => MoveCurrentToIndex(_view.Count - 1);
|
||||
|
||||
/// <summary>
|
||||
/// Move current item to next item
|
||||
/// </summary>
|
||||
/// <returns>success of operation</returns>
|
||||
public bool MoveCurrentToNext() => MoveCurrentToIndex(CurrentPosition + 1);
|
||||
|
||||
/// <summary>
|
||||
/// Move current item to previous item
|
||||
/// </summary>
|
||||
/// <returns>success of operation</returns>
|
||||
public bool MoveCurrentToPrevious() => MoveCurrentToIndex(CurrentPosition - 1);
|
||||
|
||||
/// <summary>
|
||||
/// Load more items from the source
|
||||
/// </summary>
|
||||
/// <param name="count">number of items to load</param>
|
||||
/// <returns>Async operation of LoadMoreItemsResult</returns>
|
||||
/// <exception cref="NotImplementedException">Not implemented yet...</exception>
|
||||
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
|
||||
{
|
||||
var sil = _source as ISupportIncrementalLoading;
|
||||
return sil?.LoadMoreItemsAsync(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the groups in collection
|
||||
/// </summary>
|
||||
public IObservableVector<object> CollectionGroups => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current item
|
||||
/// </summary>
|
||||
public object CurrentItem
|
||||
{
|
||||
|
||||
get { return CurrentPosition > -1 && CurrentPosition < _view.Count ? _view[CurrentPosition] : null; }
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
set { MoveCurrentTo(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of current item
|
||||
/// </summary>
|
||||
public int CurrentPosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source has more items
|
||||
/// </summary>
|
||||
public bool HasMoreItems => (_source as ISupportIncrementalLoading)?.HasMoreItems ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current item is after the last visible item
|
||||
/// </summary>
|
||||
public bool IsCurrentAfterLast => CurrentPosition >= _view.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current item is before the first visible item
|
||||
/// </summary>
|
||||
public bool IsCurrentBeforeFirst => CurrentPosition < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Current item changed event handler
|
||||
/// </summary>
|
||||
public event EventHandler<object> CurrentChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Current item changing event handler
|
||||
/// </summary>
|
||||
public event CurrentChangingEventHandler CurrentChanging;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this CollectionView can filter its items
|
||||
/// </summary>
|
||||
public bool CanFilter => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the predicate used to filter the visible items
|
||||
/// </summary>
|
||||
public Predicate<object> Filter
|
||||
{
|
||||
get
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_filter == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_filter = value;
|
||||
HandleFilterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this CollectionView can sort its items
|
||||
/// </summary>
|
||||
public bool CanSort => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets SortDescriptions to sort the visible items
|
||||
/// </summary>
|
||||
public IList<SortDescription> SortDescriptions => _sortDescriptions;
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this CollectionView can group its items
|
||||
/// </summary>
|
||||
public bool CanGroup => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets GroupDescriptions to group the visible items
|
||||
/// </summary>
|
||||
public IList<object> GroupDescriptions => null;
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source collection
|
||||
/// </summary>
|
||||
public IEnumerable SourceCollection => _source;
|
||||
|
||||
/// <summary>
|
||||
/// IComparer implementation
|
||||
/// </summary>
|
||||
/// <param name="x">Object A</param>
|
||||
/// <param name="y">Object B</param>
|
||||
/// <returns>Comparison value</returns>
|
||||
#pragma warning disable CA1033 // Interface methods should be callable by child types
|
||||
int IComparer<object>.Compare(object x, object y)
|
||||
#pragma warning restore CA1033 // Interface methods should be callable by child types
|
||||
{
|
||||
if (!_sortProperties.Any())
|
||||
{
|
||||
var listType = _source?.GetType();
|
||||
Type type;
|
||||
|
||||
if (listType != null && listType.IsGenericType)
|
||||
{
|
||||
type = listType.GetGenericArguments()[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
type = x.GetType();
|
||||
}
|
||||
|
||||
foreach (var sd in _sortDescriptions)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sd.PropertyName))
|
||||
{
|
||||
_sortProperties[sd.PropertyName] = type.GetProperty(sd.PropertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var sd in _sortDescriptions)
|
||||
{
|
||||
object cx, cy;
|
||||
|
||||
if (string.IsNullOrEmpty(sd.PropertyName))
|
||||
{
|
||||
cx = x;
|
||||
cy = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pi = _sortProperties[sd.PropertyName];
|
||||
|
||||
cx = pi.GetValue(x!);
|
||||
cy = pi.GetValue(y!);
|
||||
}
|
||||
|
||||
var cmp = sd.Comparer.Compare(cx, cy);
|
||||
|
||||
if (cmp != 0)
|
||||
{
|
||||
return sd.Direction == SortDirection.Ascending ? +cmp : -cmp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Property changed event invoker
|
||||
/// </summary>
|
||||
/// <param name="propertyName">name of the property that changed</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ObserveFilterProperty(string propertyName)
|
||||
{
|
||||
_observedFilterProperties.Add(propertyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearObservedFilterProperties()
|
||||
{
|
||||
_observedFilterProperties.Clear();
|
||||
}
|
||||
|
||||
private void ItemOnPropertyChanged(object item, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (!_liveShapingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var filterResult = _filter?.Invoke(item);
|
||||
|
||||
if (filterResult.HasValue && _observedFilterProperties.Contains(e.PropertyName))
|
||||
{
|
||||
var viewIndex = _view.IndexOf(item);
|
||||
if (viewIndex != -1 && !filterResult.Value)
|
||||
{
|
||||
RemoveFromView(viewIndex, item);
|
||||
}
|
||||
else if (viewIndex == -1 && filterResult.Value)
|
||||
{
|
||||
var index = _source.IndexOf(item);
|
||||
HandleItemAdded(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName))
|
||||
{
|
||||
var oldIndex = _view.IndexOf(item);
|
||||
|
||||
// Check if item is in view:
|
||||
if (oldIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_view.RemoveAt(oldIndex);
|
||||
var targetIndex = _view.BinarySearch(item, this);
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex = ~targetIndex;
|
||||
}
|
||||
|
||||
// Only trigger expensive UI updates if the index really changed:
|
||||
if (targetIndex != oldIndex)
|
||||
{
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, item));
|
||||
|
||||
_view.Insert(targetIndex, item);
|
||||
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, item));
|
||||
}
|
||||
else
|
||||
{
|
||||
_view.Insert(targetIndex, item);
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(e.PropertyName))
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachPropertyChangedHandler(IEnumerable items)
|
||||
{
|
||||
if (!_liveShapingEnabled || items == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in items.OfType<INotifyPropertyChanged>())
|
||||
{
|
||||
item.PropertyChanged += ItemOnPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void DetachPropertyChangedHandler(IEnumerable items)
|
||||
{
|
||||
if (!_liveShapingEnabled || items == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in items.OfType<INotifyPropertyChanged>())
|
||||
{
|
||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSortChanged()
|
||||
{
|
||||
_sortProperties.Clear();
|
||||
_view.Sort(this);
|
||||
_sortProperties.Clear();
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
|
||||
}
|
||||
|
||||
private void HandleFilterChanged()
|
||||
{
|
||||
if (_filter != null)
|
||||
{
|
||||
for (var index = 0; index < _view.Count; index++)
|
||||
{
|
||||
var item = _view.ElementAt(index);
|
||||
if (_filter(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveFromView(index, item);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
var viewHash = new HashSet<object>(_view);
|
||||
var viewIndex = 0;
|
||||
for (var index = 0; index < _source.Count; index++)
|
||||
{
|
||||
var item = _source[index];
|
||||
if (viewHash.Contains(item))
|
||||
{
|
||||
viewIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HandleItemAdded(index, item, viewIndex))
|
||||
{
|
||||
viewIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSourceChanged()
|
||||
{
|
||||
_sortProperties.Clear();
|
||||
var currentItem = CurrentItem;
|
||||
_view.Clear();
|
||||
foreach (var item in Source)
|
||||
{
|
||||
if (_filter != null && !_filter(item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_sortDescriptions.Any())
|
||||
{
|
||||
var targetIndex = _view.BinarySearch(item, this);
|
||||
if (targetIndex < 0)
|
||||
{
|
||||
targetIndex = ~targetIndex;
|
||||
}
|
||||
|
||||
_view.Insert(targetIndex, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
_view.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
_sortProperties.Clear();
|
||||
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
|
||||
MoveCurrentTo(currentItem);
|
||||
}
|
||||
|
||||
private void SourceNcc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// ReSharper disable once SwitchStatementMissingSomeCases
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
AttachPropertyChangedHandler(e.NewItems);
|
||||
if (_deferCounter <= 0)
|
||||
{
|
||||
if (e.NewItems?.Count == 1)
|
||||
{
|
||||
HandleItemAdded(e.NewStartingIndex, e.NewItems[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
DetachPropertyChangedHandler(e.OldItems);
|
||||
if (_deferCounter <= 0)
|
||||
{
|
||||
if (e.OldItems?.Count == 1)
|
||||
{
|
||||
HandleItemRemoved(e.OldStartingIndex, e.OldItems[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
if (_deferCounter <= 0)
|
||||
{
|
||||
HandleSourceChanged();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleItemAdded(int newStartingIndex, object newItem, int? viewIndex = null)
|
||||
{
|
||||
if (_filter != null && !_filter(newItem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newViewIndex = _view.Count;
|
||||
|
||||
if (_sortDescriptions.Any())
|
||||
{
|
||||
_sortProperties.Clear();
|
||||
newViewIndex = _view.BinarySearch(newItem, this);
|
||||
if (newViewIndex < 0)
|
||||
{
|
||||
newViewIndex = ~newViewIndex;
|
||||
}
|
||||
}
|
||||
else if (_filter != null)
|
||||
{
|
||||
if (_source == null)
|
||||
{
|
||||
HandleSourceChanged();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newStartingIndex == 0 || _view.Count == 0)
|
||||
{
|
||||
newViewIndex = 0;
|
||||
}
|
||||
else if (newStartingIndex == _source.Count - 1)
|
||||
{
|
||||
newViewIndex = _view.Count;
|
||||
}
|
||||
else if (viewIndex.HasValue)
|
||||
{
|
||||
newViewIndex = viewIndex.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0, j = 0; i < _source.Count; i++)
|
||||
{
|
||||
if (i == newStartingIndex)
|
||||
{
|
||||
newViewIndex = j;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_view[j] == _source[i])
|
||||
{
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_view.Insert(newViewIndex, newItem);
|
||||
if (newViewIndex <= CurrentPosition)
|
||||
{
|
||||
CurrentPosition++;
|
||||
}
|
||||
|
||||
var e = new VectorChangedEventArgs(CollectionChange.ItemInserted, newViewIndex, newItem);
|
||||
OnVectorChanged(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleItemRemoved(int oldStartingIndex, object oldItem)
|
||||
{
|
||||
if (_filter != null && !_filter(oldItem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldStartingIndex < 0 || oldStartingIndex >= _view.Count || !Equals(_view[oldStartingIndex], oldItem))
|
||||
{
|
||||
oldStartingIndex = _view.IndexOf(oldItem);
|
||||
}
|
||||
|
||||
if (oldStartingIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveFromView(oldStartingIndex, oldItem);
|
||||
}
|
||||
|
||||
private void RemoveFromView(int itemIndex, object item)
|
||||
{
|
||||
_view.RemoveAt(itemIndex);
|
||||
if (itemIndex <= CurrentPosition)
|
||||
{
|
||||
CurrentPosition--;
|
||||
}
|
||||
|
||||
var e = new VectorChangedEventArgs(CollectionChange.ItemRemoved, itemIndex, item);
|
||||
OnVectorChanged(e);
|
||||
}
|
||||
|
||||
private void SortDescriptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_deferCounter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleSortChanged();
|
||||
}
|
||||
|
||||
private bool MoveCurrentToIndex(int i)
|
||||
{
|
||||
if (i < -1 || i >= _view.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i == CurrentPosition)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var e = new CurrentChangingEventArgs();
|
||||
OnCurrentChanging(e);
|
||||
if (e.Cancel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentPosition = i;
|
||||
OnCurrentChanged(null!);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CS8767
|
||||
#pragma warning restore CS8769
|
||||
#pragma warning restore CS8622
|
||||
#pragma warning restore CS8601
|
||||
#pragma warning restore CS8600
|
||||
#pragma warning restore CS8604
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
|
||||
/// <summary>
|
||||
/// Vector changed EventArgs
|
||||
/// </summary>
|
||||
internal class VectorChangedEventArgs : IVectorChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VectorChangedEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cc">collection change type</param>
|
||||
/// <param name="index">index of item changed</param>
|
||||
/// <param name="item">item changed</param>
|
||||
public VectorChangedEventArgs(CollectionChange cc, int index = -1, object item = null!)
|
||||
{
|
||||
CollectionChange = cc;
|
||||
Index = (uint)index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of change that occurred in the vector.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The type of change in the vector.
|
||||
/// </returns>
|
||||
public CollectionChange CollectionChange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position where the change occurred in the vector.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The zero-based position where the change occurred in the vector, if applicable.
|
||||
/// </returns>
|
||||
public uint Index { get; }
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Core.IO;
|
||||
using Snap.Hutao.Core.LifeCycle;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
@@ -18,6 +18,8 @@ using System.Text.RegularExpressions;
|
||||
using EntityAchievementArchive = Snap.Hutao.Model.Entity.AchievementArchive;
|
||||
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
|
||||
using MetadataAchievementGoal = Snap.Hutao.Model.Metadata.Achievement.AchievementGoal;
|
||||
using SortDescription = CommunityToolkit.WinUI.Collections.SortDescription;
|
||||
using SortDirection = CommunityToolkit.WinUI.Collections.SortDirection;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Achievement;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Core;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics.CodeAnalysis;
|
||||
@@ -241,8 +241,6 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
SelectedGameAccount = account;
|
||||
|
||||
await UpdateGameAccountsViewAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (UserdataCorruptedException ex)
|
||||
@@ -328,6 +326,18 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
GameResource = response.Data;
|
||||
}
|
||||
}
|
||||
|
||||
async ValueTask UpdateGameAccountsViewAsync()
|
||||
{
|
||||
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
|
||||
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
GameAccountsView = new(accounts, true)
|
||||
{
|
||||
Filter = gameAccountFilter.Filter,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Command("IdentifyMonitorsCommand")]
|
||||
@@ -353,16 +363,4 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask UpdateGameAccountsViewAsync()
|
||||
{
|
||||
gameAccountFilter = new(SelectedScheme?.GetSchemeType());
|
||||
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
|
||||
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
GameAccountsView = new(accounts, true)
|
||||
{
|
||||
Filter = gameAccountFilter.Filter,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Service.Game;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Calculable;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Item;
|
||||
using Snap.Hutao.Model.Metadata.Monster;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Collections;
|
||||
using Snap.Hutao.Control.Collection.AdvancedCollectionView;
|
||||
using Snap.Hutao.Factory.ContentDialog;
|
||||
using Snap.Hutao.Model.Calculable;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
|
||||
Reference in New Issue
Block a user