From 95024e4107dc9b5c9e82fcf01d08f767a4e2dd25 Mon Sep 17 00:00:00 2001 From: qhy040404 Date: Mon, 5 Feb 2024 21:47:52 +0800 Subject: [PATCH 1/3] refine #1334 --- .../AdvancedCollectionView.Defer.cs | 54 ++ .../AdvancedCollectionView.Events.cs | 61 ++ .../AdvancedCollectionView.cs | 816 ++++++++++++++++++ .../VectorChangedEventArgs.cs | 40 + .../Achievement/AchievementViewModel.cs | 4 +- .../ViewModel/Game/LaunchGameViewModel.cs | 28 +- .../ViewModel/Game/LaunchGameViewModelSlim.cs | 2 +- .../ViewModel/Wiki/WikiAvatarViewModel.cs | 2 +- .../ViewModel/Wiki/WikiMonsterViewModel.cs | 2 +- .../ViewModel/Wiki/WikiWeaponViewModel.cs | 2 +- 10 files changed, 991 insertions(+), 20 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs new file mode 100644 index 00000000..3e84fd0e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs @@ -0,0 +1,54 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Control.Collection.AdvancedCollectionView; + +/// +/// A collection view implementation that supports filtering, grouping, sorting and incremental loading +/// +internal partial class AdvancedCollectionView +{ + /// + /// Stops refreshing until it is disposed + /// + /// An disposable object + public IDisposable DeferRefresh() + { + return new NotificationDeferrer(this); + } + + /// + /// Notification deferrer helper class + /// +#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; + + /// + /// Initializes a new instance of the class. + /// + /// Source ACVS + public NotificationDeferrer(AdvancedCollectionView acvs) + { + _acvs = acvs; + _currentItem = _acvs.CurrentItem; + _acvs._deferCounter++; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// 2 +#pragma warning disable CA1063 // Implement IDisposable Correctly + public void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly + { + _acvs.MoveCurrentTo(_currentItem); + _acvs._deferCounter--; + _acvs.Refresh(); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs new file mode 100644 index 00000000..56d8233e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs @@ -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; + +/// +/// A collection view implementation that supports filtering, grouping, sorting and incremental loading +/// +internal partial class AdvancedCollectionView +{ + /// + /// Currently selected item changing event + /// + /// event args + private void OnCurrentChanging(CurrentChangingEventArgs e) + { + if (_deferCounter > 0) + { + return; + } + + CurrentChanging?.Invoke(this, e); + } + + /// + /// Currently selected item changed event + /// + /// event args + private void OnCurrentChanged(object e) + { + if (_deferCounter > 0) + { + return; + } + + CurrentChanged?.Invoke(this, e); + + // ReSharper disable once ExplicitCallerInfoArgument + OnPropertyChanged(nameof(CurrentItem)); + } + + /// + /// Vector changed event + /// + /// event args + private void OnVectorChanged(IVectorChangedEventArgs e) + { + if (_deferCounter > 0) + { + return; + } + + VectorChanged?.Invoke(this, e); + + // ReSharper disable once ExplicitCallerInfoArgument + OnPropertyChanged(nameof(Count)); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs new file mode 100644 index 00000000..58557fd3 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs @@ -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; + +/// +/// A collection view implementation that supports filtering, sorting and incremental loading +/// https://github.com/CommunityToolkit/Windows/pull/309 +/// +internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer +{ + private readonly List _view; + + private readonly ObservableCollection _sortDescriptions; + + private readonly Dictionary _sortProperties; + + private readonly bool _liveShapingEnabled; + + private readonly HashSet _observedFilterProperties = new HashSet(); + + private IList _source; + + private Predicate _filter; + private int _deferCounter; + + private WeakEventListener _sourceWeakEventListener; + + /// + /// Initializes a new instance of the class. + /// + public AdvancedCollectionView() + : this(new List(0)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// source IEnumerable + /// Denotes whether or not this ACV should re-filter/re-sort if a PropertyChanged is raised for an observed property. +#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(); + _sortDescriptions = new ObservableCollection(); + _sortDescriptions.CollectionChanged += SortDescriptions_CollectionChanged; + _sortProperties = new Dictionary(); + Source = source; + } + + /// + /// Gets or sets the source + /// + 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(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(); + } + } + + /// + /// Manually refresh the view + /// + public void Refresh() + { + HandleSourceChanged(); + } + + /// + public void RefreshFilter() + { + HandleFilterChanged(); + } + + /// + public void RefreshSorting() + { + HandleSortChanged(); + } + + /// + public IEnumerator GetEnumerator() => _view.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => _view.GetEnumerator(); + + /// + public void Add(object item) + { + if (IsReadOnly) + { + throw new NotSupportedException("Collection is read-only."); + } + + _source.Add(item); + } + + /// + public void Clear() + { + if (IsReadOnly) + { + throw new NotSupportedException("Collection is read-only."); + } + + _source.Clear(); + } + + /// + public bool Contains(object item) => _view.Contains(item); + + /// + public void CopyTo(object[] array, int arrayIndex) => _view.CopyTo(array, arrayIndex); + + /// + public bool Remove(object item) + { + if (IsReadOnly) + { + throw new NotSupportedException("Collection is read-only."); + } + + _source.Remove(item); + return true; + } + + /// + public int Count => _view.Count; + + /// + public bool IsReadOnly => _source == null || _source.IsReadOnly; + + /// + public int IndexOf(object item) => _view.IndexOf(item); + + /// + public void Insert(int index, object item) + { + if (IsReadOnly) + { + throw new NotSupportedException("Collection is read-only."); + } + + _source.Insert(index, item); + } + + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. is not a valid index in the .The is read-only. + public void RemoveAt(int index) => Remove(_view[index]); + + /// + /// Gets or sets the element at the specified index. + /// + /// + /// The element at the specified index. + /// + /// The zero-based index of the element to get or set. is not a valid index in the .The property is set and the is read-only. + public object this[int index] + { + get { return _view[index]; } + set { _view[index] = value; } + } + + /// + /// Occurs when the vector changes. + /// + public event Windows.Foundation.Collections.VectorChangedEventHandler VectorChanged; + + /// + /// Move current index to item + /// + /// item + /// success of operation + public bool MoveCurrentTo(object item) => item == CurrentItem || MoveCurrentToIndex(IndexOf(item)); + + /// + /// Moves selected item to position + /// + /// index + /// success of operation + public bool MoveCurrentToPosition(int index) => MoveCurrentToIndex(index); + + /// + /// Move current item to first item + /// + /// success of operation + public bool MoveCurrentToFirst() => MoveCurrentToIndex(0); + + /// + /// Move current item to last item + /// + /// success of operation + public bool MoveCurrentToLast() => MoveCurrentToIndex(_view.Count - 1); + + /// + /// Move current item to next item + /// + /// success of operation + public bool MoveCurrentToNext() => MoveCurrentToIndex(CurrentPosition + 1); + + /// + /// Move current item to previous item + /// + /// success of operation + public bool MoveCurrentToPrevious() => MoveCurrentToIndex(CurrentPosition - 1); + + /// + /// Load more items from the source + /// + /// number of items to load + /// Async operation of LoadMoreItemsResult + /// Not implemented yet... + public IAsyncOperation LoadMoreItemsAsync(uint count) + { + var sil = _source as ISupportIncrementalLoading; + return sil?.LoadMoreItemsAsync(count); + } + + /// + /// Gets the groups in collection + /// + public IObservableVector CollectionGroups => null; + + /// + /// Gets or sets the current item + /// + public object CurrentItem + { + + get { return CurrentPosition > -1 && CurrentPosition < _view.Count ? _view[CurrentPosition] : null; } +#pragma warning restore CS8603 // Possible null reference return. + set { MoveCurrentTo(value); } + } + + /// + /// Gets the position of current item + /// + public int CurrentPosition { get; private set; } + + /// + /// Gets a value indicating whether the source has more items + /// + public bool HasMoreItems => (_source as ISupportIncrementalLoading)?.HasMoreItems ?? false; + + /// + /// Gets a value indicating whether the current item is after the last visible item + /// + public bool IsCurrentAfterLast => CurrentPosition >= _view.Count; + + /// + /// Gets a value indicating whether the current item is before the first visible item + /// + public bool IsCurrentBeforeFirst => CurrentPosition < 0; + + /// + /// Current item changed event handler + /// + public event EventHandler CurrentChanged; + + /// + /// Current item changing event handler + /// + public event CurrentChangingEventHandler CurrentChanging; + + /// + /// Gets a value indicating whether this CollectionView can filter its items + /// + public bool CanFilter => true; + + /// + /// Gets or sets the predicate used to filter the visible items + /// + public Predicate Filter + { + get + { + return _filter; + } + + set + { + if (_filter == value) + { + return; + } + + _filter = value; + HandleFilterChanged(); + } + } + + /// + /// Gets a value indicating whether this CollectionView can sort its items + /// + public bool CanSort => true; + + /// + /// Gets SortDescriptions to sort the visible items + /// + public IList SortDescriptions => _sortDescriptions; + + /* + /// + /// Gets a value indicating whether this CollectionView can group its items + /// + public bool CanGroup => false; + + /// + /// Gets GroupDescriptions to group the visible items + /// + public IList GroupDescriptions => null; + */ + + /// + /// Gets the source collection + /// + public IEnumerable SourceCollection => _source; + + /// + /// IComparer implementation + /// + /// Object A + /// Object B + /// Comparison value +#pragma warning disable CA1033 // Interface methods should be callable by child types + int IComparer.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; + } + + /// + /// Occurs when a property value changes. + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + /// Property changed event invoker + /// + /// name of the property that changed + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + public void ObserveFilterProperty(string propertyName) + { + _observedFilterProperties.Add(propertyName); + } + + /// + 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()) + { + item.PropertyChanged += ItemOnPropertyChanged; + } + } + + private void DetachPropertyChangedHandler(IEnumerable items) + { + if (!_liveShapingEnabled || items == null) + { + return; + } + + foreach (var item in items.OfType()) + { + 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(_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. diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs new file mode 100644 index 00000000..168ca7d9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs @@ -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; + +/// +/// Vector changed EventArgs +/// +internal class VectorChangedEventArgs : IVectorChangedEventArgs +{ + /// + /// Initializes a new instance of the class. + /// + /// collection change type + /// index of item changed + /// item changed + public VectorChangedEventArgs(CollectionChange cc, int index = -1, object item = null!) + { + CollectionChange = cc; + Index = (uint)index; + } + + /// + /// Gets the type of change that occurred in the vector. + /// + /// + /// The type of change in the vector. + /// + public CollectionChange CollectionChange { get; } + + /// + /// Gets the position where the change occurred in the vector. + /// + /// + /// The zero-based position where the change occurred in the vector, if applicable. + /// + public uint Index { get; } +} diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs index 8c3b69ac..7bffcb88 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs @@ -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; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index db52d5dc..db6c0190 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -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 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 accounts = gameService.GameAccountCollection; - - await taskContext.SwitchToMainThreadAsync(); - GameAccountsView = new(accounts, true) - { - Filter = gameAccountFilter.Filter, - }; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs index a7d29315..9fbb76c9 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs @@ -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; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index e0b4bad6..e50fba5c 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -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; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs index cd42acc1..ace8bf7f 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs @@ -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; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index d64196c8..85608aec 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -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; From 5f180846f6522dcd6501512f878bcf9e492a948f Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 5 Feb 2024 23:08:00 +0800 Subject: [PATCH 2/3] refactor --- .../AdvancedCollectionView.Defer.cs | 54 -- .../AdvancedCollectionView.Events.cs | 61 -- .../AdvancedCollectionView.cs | 758 ++++++++---------- .../VectorChangedEventArgs.cs | 23 +- .../Core/ExceptionService/ThrowHelper.cs | 9 + 5 files changed, 358 insertions(+), 547 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs delete mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs deleted file mode 100644 index 3e84fd0e..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Defer.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Control.Collection.AdvancedCollectionView; - -/// -/// A collection view implementation that supports filtering, grouping, sorting and incremental loading -/// -internal partial class AdvancedCollectionView -{ - /// - /// Stops refreshing until it is disposed - /// - /// An disposable object - public IDisposable DeferRefresh() - { - return new NotificationDeferrer(this); - } - - /// - /// Notification deferrer helper class - /// -#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; - - /// - /// Initializes a new instance of the class. - /// - /// Source ACVS - public NotificationDeferrer(AdvancedCollectionView acvs) - { - _acvs = acvs; - _currentItem = _acvs.CurrentItem; - _acvs._deferCounter++; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 -#pragma warning disable CA1063 // Implement IDisposable Correctly - public void Dispose() -#pragma warning restore CA1063 // Implement IDisposable Correctly - { - _acvs.MoveCurrentTo(_currentItem); - _acvs._deferCounter--; - _acvs.Refresh(); - } - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs deleted file mode 100644 index 56d8233e..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.Events.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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; - -/// -/// A collection view implementation that supports filtering, grouping, sorting and incremental loading -/// -internal partial class AdvancedCollectionView -{ - /// - /// Currently selected item changing event - /// - /// event args - private void OnCurrentChanging(CurrentChangingEventArgs e) - { - if (_deferCounter > 0) - { - return; - } - - CurrentChanging?.Invoke(this, e); - } - - /// - /// Currently selected item changed event - /// - /// event args - private void OnCurrentChanged(object e) - { - if (_deferCounter > 0) - { - return; - } - - CurrentChanged?.Invoke(this, e); - - // ReSharper disable once ExplicitCallerInfoArgument - OnPropertyChanged(nameof(CurrentItem)); - } - - /// - /// Vector changed event - /// - /// event args - private void OnVectorChanged(IVectorChangedEventArgs e) - { - if (_deferCounter > 0) - { - return; - } - - VectorChanged?.Invoke(this, e); - - // ReSharper disable once ExplicitCallerInfoArgument - OnPropertyChanged(nameof(Count)); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs index 58557fd3..386b4340 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs @@ -1,114 +1,95 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.WinUI.Collections; +using CommunityToolkit.WinUI.Helpers; +using Microsoft.UI.Xaml.Data; +using Snap.Hutao.Core.ExceptionService; 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; +using NotifyCollectionChangedAction = System.Collections.Specialized.NotifyCollectionChangedAction; namespace Snap.Hutao.Control.Collection.AdvancedCollectionView; -/// -/// A collection view implementation that supports filtering, sorting and incremental loading -/// https://github.com/CommunityToolkit/Windows/pull/309 -/// -internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer +internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer { - private readonly List _view; + private readonly List view; + private readonly ObservableCollection sortDescriptions; + private readonly Dictionary sortProperties; + private readonly bool liveShapingEnabled; + private readonly HashSet observedFilterProperties = []; - private readonly ObservableCollection _sortDescriptions; + private IList source; + private Predicate? filter; + private int deferCounter; + private WeakEventListener? sourceWeakEventListener; - private readonly Dictionary _sortProperties; - - private readonly bool _liveShapingEnabled; - - private readonly HashSet _observedFilterProperties = new HashSet(); - - private IList _source; - - private Predicate _filter; - private int _deferCounter; - - private WeakEventListener _sourceWeakEventListener; - - /// - /// Initializes a new instance of the class. - /// public AdvancedCollectionView() : this(new List(0)) { } - /// - /// Initializes a new instance of the class. - /// - /// source IEnumerable - /// Denotes whether or not this ACV should re-filter/re-sort if a PropertyChanged is raised for an observed property. -#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(); - _sortDescriptions = new ObservableCollection(); - _sortDescriptions.CollectionChanged += SortDescriptions_CollectionChanged; - _sortProperties = new Dictionary(); + liveShapingEnabled = isLiveShaping; + view = []; + sortDescriptions = []; + sortDescriptions.CollectionChanged += SortDescriptionsCollectionChanged; + sortProperties = []; Source = source; } - /// - /// Gets or sets the source - /// + public event VectorChangedEventHandler? VectorChanged; + + public event EventHandler? CurrentChanged; + + public event CurrentChangingEventHandler? CurrentChanging; + + public event PropertyChangedEventHandler? PropertyChanged; + public IList Source { - get - { - return _source; - } + get => source; + [MemberNotNull(nameof(source))] set { - // ReSharper disable once PossibleUnintendedReferenceComparison - if (_source == value) + if (source == value) { return; } - if (_source != null) + if (source is not null) { - DetachPropertyChangedHandler(_source); + DetachPropertyChangedHandler(source); } - _source = value; - AttachPropertyChangedHandler(_source); + source = value; + AttachPropertyChangedHandler(source); - _sourceWeakEventListener?.Detach(); + sourceWeakEventListener?.Detach(); - if (_source is INotifyCollectionChanged sourceNcc) + if (source is INotifyCollectionChanged sourceNcc) { - _sourceWeakEventListener = - new WeakEventListener(this) + sourceWeakEventListener = + new WeakEventListener(this) { // Call the actual collection changed event - OnEventAction = (source, changed, arg3) => SourceNcc_CollectionChanged(source, arg3), + OnEventAction = (source, changed, arg3) => SourceNotifyCollectionChangedCollectionChanged(source, arg3), // The source doesn't exist anymore - OnDetachAction = (listener) => sourceNcc.CollectionChanged -= _sourceWeakEventListener!.OnEvent + OnDetachAction = (listener) => + { + ArgumentNullException.ThrowIfNull(sourceWeakEventListener); + sourceNcc.CollectionChanged -= sourceWeakEventListener.OnEvent; + }, }; - sourceNcc.CollectionChanged += _sourceWeakEventListener.OnEvent; + sourceNcc.CollectionChanged += sourceWeakEventListener.OnEvent; } HandleSourceChanged(); @@ -116,300 +97,231 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify } } - /// - /// Manually refresh the view - /// + public int Count + { + get => view.Count; + } + + public bool IsReadOnly + { + get => source is null || source.IsReadOnly; + } + + public IObservableVector CollectionGroups + { + get => default!; + } + + public object? CurrentItem + { + get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : null; + set => MoveCurrentTo(value); + } + + public int CurrentPosition { get; private set; } + + public bool HasMoreItems + { + get => (source as ISupportIncrementalLoading)?.HasMoreItems ?? false; + } + + public bool IsCurrentAfterLast + { + get => CurrentPosition >= view.Count; + } + + public bool IsCurrentBeforeFirst + { + get => CurrentPosition < 0; + } + + public bool CanFilter + { + get => true; + } + + public Predicate Filter + { + get => filter; + set + { + if (filter == value) + { + return; + } + + filter = value; + HandleFilterChanged(); + } + } + + public bool CanSort + { + get => true; + } + + public IList SortDescriptions + { + get => sortDescriptions; + } + + public IEnumerable SourceCollection + { + get => source; + } + + public object this[int index] + { + get => view[index]; + set => view[index] = value; + } + public void Refresh() { HandleSourceChanged(); } - /// public void RefreshFilter() { HandleFilterChanged(); } - /// public void RefreshSorting() { HandleSortChanged(); } - /// - public IEnumerator GetEnumerator() => _view.GetEnumerator(); + public IEnumerator GetEnumerator() + { + return view.GetEnumerator(); + } - /// - IEnumerator IEnumerable.GetEnumerator() => _view.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + { + return view.GetEnumerator(); + } - /// public void Add(object item) { - if (IsReadOnly) - { - throw new NotSupportedException("Collection is read-only."); - } - - _source.Add(item); + ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); + source.Add(item); } - /// public void Clear() { - if (IsReadOnly) - { - throw new NotSupportedException("Collection is read-only."); - } - - _source.Clear(); + ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); + source.Clear(); } - /// - public bool Contains(object item) => _view.Contains(item); + public bool Contains(object item) + { + return view.Contains(item); + } - /// - public void CopyTo(object[] array, int arrayIndex) => _view.CopyTo(array, arrayIndex); + public void CopyTo(object[] array, int arrayIndex) + { + view.CopyTo(array, arrayIndex); + } - /// public bool Remove(object item) { - if (IsReadOnly) - { - throw new NotSupportedException("Collection is read-only."); - } - - _source.Remove(item); + ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); + source.Remove(item); return true; } - /// - public int Count => _view.Count; + public int IndexOf(object item) + { + return view.IndexOf(item); + } - /// - public bool IsReadOnly => _source == null || _source.IsReadOnly; - - /// - public int IndexOf(object item) => _view.IndexOf(item); - - /// public void Insert(int index, object item) { - if (IsReadOnly) + ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); + source.Insert(index, item); + } + + public void RemoveAt(int index) + { + Remove(view[index]); + } + + public bool MoveCurrentTo(object? item) + { + return item == CurrentItem || MoveCurrentToIndex(IndexOf(item)); + } + + public bool MoveCurrentToPosition(int index) + { + return MoveCurrentToIndex(index); + } + + public bool MoveCurrentToFirst() + { + return MoveCurrentToIndex(0); + } + + public bool MoveCurrentToLast() + { + return MoveCurrentToIndex(view.Count - 1); + } + + public bool MoveCurrentToNext() + { + return MoveCurrentToIndex(CurrentPosition + 1); + } + + public bool MoveCurrentToPrevious() + { + return MoveCurrentToIndex(CurrentPosition - 1); + } + + public IAsyncOperation? LoadMoreItemsAsync(uint count) + { + return (source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count); + } + + public void ObserveFilterProperty(string propertyName) + { + observedFilterProperties.Add(propertyName); + } + + public void ClearObservedFilterProperties() + { + observedFilterProperties.Clear(); + } + + public IDisposable DeferRefresh() + { + return new NotificationDeferrer(this); + } + + int IComparer.Compare(object? x, object? y) + { + if (sortProperties.Count <= 0) { - throw new NotSupportedException("Collection is read-only."); - } + Type? listType = source?.GetType(); + Type? type; - _source.Insert(index, item); - } - - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. is not a valid index in the .The is read-only. - public void RemoveAt(int index) => Remove(_view[index]); - - /// - /// Gets or sets the element at the specified index. - /// - /// - /// The element at the specified index. - /// - /// The zero-based index of the element to get or set. is not a valid index in the .The property is set and the is read-only. - public object this[int index] - { - get { return _view[index]; } - set { _view[index] = value; } - } - - /// - /// Occurs when the vector changes. - /// - public event Windows.Foundation.Collections.VectorChangedEventHandler VectorChanged; - - /// - /// Move current index to item - /// - /// item - /// success of operation - public bool MoveCurrentTo(object item) => item == CurrentItem || MoveCurrentToIndex(IndexOf(item)); - - /// - /// Moves selected item to position - /// - /// index - /// success of operation - public bool MoveCurrentToPosition(int index) => MoveCurrentToIndex(index); - - /// - /// Move current item to first item - /// - /// success of operation - public bool MoveCurrentToFirst() => MoveCurrentToIndex(0); - - /// - /// Move current item to last item - /// - /// success of operation - public bool MoveCurrentToLast() => MoveCurrentToIndex(_view.Count - 1); - - /// - /// Move current item to next item - /// - /// success of operation - public bool MoveCurrentToNext() => MoveCurrentToIndex(CurrentPosition + 1); - - /// - /// Move current item to previous item - /// - /// success of operation - public bool MoveCurrentToPrevious() => MoveCurrentToIndex(CurrentPosition - 1); - - /// - /// Load more items from the source - /// - /// number of items to load - /// Async operation of LoadMoreItemsResult - /// Not implemented yet... - public IAsyncOperation LoadMoreItemsAsync(uint count) - { - var sil = _source as ISupportIncrementalLoading; - return sil?.LoadMoreItemsAsync(count); - } - - /// - /// Gets the groups in collection - /// - public IObservableVector CollectionGroups => null; - - /// - /// Gets or sets the current item - /// - public object CurrentItem - { - - get { return CurrentPosition > -1 && CurrentPosition < _view.Count ? _view[CurrentPosition] : null; } -#pragma warning restore CS8603 // Possible null reference return. - set { MoveCurrentTo(value); } - } - - /// - /// Gets the position of current item - /// - public int CurrentPosition { get; private set; } - - /// - /// Gets a value indicating whether the source has more items - /// - public bool HasMoreItems => (_source as ISupportIncrementalLoading)?.HasMoreItems ?? false; - - /// - /// Gets a value indicating whether the current item is after the last visible item - /// - public bool IsCurrentAfterLast => CurrentPosition >= _view.Count; - - /// - /// Gets a value indicating whether the current item is before the first visible item - /// - public bool IsCurrentBeforeFirst => CurrentPosition < 0; - - /// - /// Current item changed event handler - /// - public event EventHandler CurrentChanged; - - /// - /// Current item changing event handler - /// - public event CurrentChangingEventHandler CurrentChanging; - - /// - /// Gets a value indicating whether this CollectionView can filter its items - /// - public bool CanFilter => true; - - /// - /// Gets or sets the predicate used to filter the visible items - /// - public Predicate Filter - { - get - { - return _filter; - } - - set - { - if (_filter == value) - { - return; - } - - _filter = value; - HandleFilterChanged(); - } - } - - /// - /// Gets a value indicating whether this CollectionView can sort its items - /// - public bool CanSort => true; - - /// - /// Gets SortDescriptions to sort the visible items - /// - public IList SortDescriptions => _sortDescriptions; - - /* - /// - /// Gets a value indicating whether this CollectionView can group its items - /// - public bool CanGroup => false; - - /// - /// Gets GroupDescriptions to group the visible items - /// - public IList GroupDescriptions => null; - */ - - /// - /// Gets the source collection - /// - public IEnumerable SourceCollection => _source; - - /// - /// IComparer implementation - /// - /// Object A - /// Object B - /// Comparison value -#pragma warning disable CA1033 // Interface methods should be callable by child types - int IComparer.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) + if (listType is { IsGenericType: true }) { type = listType.GetGenericArguments()[0]; } else { - type = x.GetType(); + type = x?.GetType(); } - foreach (var sd in _sortDescriptions) + foreach (SortDescription sd in sortDescriptions) { if (!string.IsNullOrEmpty(sd.PropertyName)) { - _sortProperties[sd.PropertyName] = type.GetProperty(sd.PropertyName); + sortProperties[sd.PropertyName] = type?.GetProperty(sd.PropertyName); } } } - foreach (var sd in _sortDescriptions) + foreach (SortDescription sd in sortDescriptions) { object cx, cy; @@ -420,13 +332,13 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify } else { - var pi = _sortProperties[sd.PropertyName]; + PropertyInfo pi = sortProperties[sd.PropertyName]; cx = pi.GetValue(x!); cy = pi.GetValue(y!); } - var cmp = sd.Comparer.Compare(cx, cy); + int cmp = sd.Comparer.Compare(cx, cy); if (cmp != 0) { @@ -437,58 +349,37 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify return 0; } - /// - /// Occurs when a property value changes. - /// - public event PropertyChangedEventHandler? PropertyChanged; - - /// - /// Property changed event invoker - /// - /// name of the property that changed - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) + internal void OnPropertyChanged([CallerMemberName] string propertyName = default!) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - /// - public void ObserveFilterProperty(string propertyName) + private void ItemOnPropertyChanged(object? item, PropertyChangedEventArgs e) { - _observedFilterProperties.Add(propertyName); - } - - /// - public void ClearObservedFilterProperties() - { - _observedFilterProperties.Clear(); - } - - private void ItemOnPropertyChanged(object item, PropertyChangedEventArgs e) - { - if (!_liveShapingEnabled) + if (!liveShapingEnabled) { return; } - var filterResult = _filter?.Invoke(item); + bool? filterResult = filter?.Invoke(item); - if (filterResult.HasValue && _observedFilterProperties.Contains(e.PropertyName)) + if (filterResult.HasValue && observedFilterProperties.Contains(e.PropertyName)) { - var viewIndex = _view.IndexOf(item); + int viewIndex = view.IndexOf(item); if (viewIndex != -1 && !filterResult.Value) { RemoveFromView(viewIndex, item); } else if (viewIndex == -1 && filterResult.Value) { - var index = _source.IndexOf(item); + int index = source.IndexOf(item); HandleItemAdded(index, item); } } if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName)) { - var oldIndex = _view.IndexOf(item); + int oldIndex = view.IndexOf(item); // Check if item is in view: if (oldIndex < 0) @@ -496,8 +387,8 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify return; } - _view.RemoveAt(oldIndex); - var targetIndex = _view.BinarySearch(item, this); + view.RemoveAt(oldIndex); + int targetIndex = view.BinarySearch(item, this); if (targetIndex < 0) { targetIndex = ~targetIndex; @@ -508,13 +399,13 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify { OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, item)); - _view.Insert(targetIndex, item); + view.Insert(targetIndex, item); OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, item)); } else { - _view.Insert(targetIndex, item); + view.Insert(targetIndex, item); } } else if (string.IsNullOrEmpty(e.PropertyName)) @@ -525,12 +416,12 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private void AttachPropertyChangedHandler(IEnumerable items) { - if (!_liveShapingEnabled || items == null) + if (!liveShapingEnabled || items is null) { return; } - foreach (var item in items.OfType()) + foreach (INotifyPropertyChanged item in items.OfType()) { item.PropertyChanged += ItemOnPropertyChanged; } @@ -538,12 +429,12 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private void DetachPropertyChangedHandler(IEnumerable items) { - if (!_liveShapingEnabled || items == null) + if (!liveShapingEnabled || items is null) { return; } - foreach (var item in items.OfType()) + foreach (INotifyPropertyChanged item in items.OfType()) { item.PropertyChanged -= ItemOnPropertyChanged; } @@ -551,20 +442,20 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private void HandleSortChanged() { - _sortProperties.Clear(); - _view.Sort(this); - _sortProperties.Clear(); + sortProperties.Clear(); + view.Sort(this); + sortProperties.Clear(); OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset)); } private void HandleFilterChanged() { - if (_filter != null) + if (filter is not null) { - for (var index = 0; index < _view.Count; index++) + for (int index = 0; index < view.Count; index++) { - var item = _view.ElementAt(index); - if (_filter(item)) + object item = view.ElementAt(index); + if (filter(item)) { continue; } @@ -574,11 +465,12 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify } } - var viewHash = new HashSet(_view); - var viewIndex = 0; - for (var index = 0; index < _source.Count; index++) + HashSet viewHash = new(view); + int viewIndex = 0; + for (int index = 0; index < source.Count; index++) { - var item = _source[index]; + object? item = source[index]; + ArgumentNullException.ThrowIfNull(item); if (viewHash.Contains(item)) { viewIndex++; @@ -594,49 +486,51 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private void HandleSourceChanged() { - _sortProperties.Clear(); - var currentItem = CurrentItem; - _view.Clear(); - foreach (var item in Source) + sortProperties.Clear(); + object? currentItem = CurrentItem; + view.Clear(); + foreach (object? item in Source) { - if (_filter != null && !_filter(item)) + if (filter is not null && !filter(item)) { continue; } - if (_sortDescriptions.Any()) + if (sortDescriptions.Any()) { - var targetIndex = _view.BinarySearch(item, this); + int targetIndex = view.BinarySearch(item, this); if (targetIndex < 0) { targetIndex = ~targetIndex; } - _view.Insert(targetIndex, item); + view.Insert(targetIndex, item); } else { - _view.Add(item); + view.Add(item); } } - _sortProperties.Clear(); + sortProperties.Clear(); OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset)); MoveCurrentTo(currentItem); } - private void SourceNcc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void SourceNotifyCollectionChangedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - // ReSharper disable once SwitchStatementMissingSomeCases switch (e.Action) { case NotifyCollectionChangedAction.Add: + ArgumentNullException.ThrowIfNull(e.NewItems); AttachPropertyChangedHandler(e.NewItems); - if (_deferCounter <= 0) + if (deferCounter <= 0) { if (e.NewItems?.Count == 1) { - HandleItemAdded(e.NewStartingIndex, e.NewItems[0]); + object? newItem = e.NewItems[0]; + ArgumentNullException.ThrowIfNull(newItem); + HandleItemAdded(e.NewStartingIndex, newItem); } else { @@ -646,12 +540,15 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify break; case NotifyCollectionChangedAction.Remove: + ArgumentNullException.ThrowIfNull(e.OldItems); DetachPropertyChangedHandler(e.OldItems); - if (_deferCounter <= 0) + if (deferCounter <= 0) { if (e.OldItems?.Count == 1) { - HandleItemRemoved(e.OldStartingIndex, e.OldItems[0]); + object? oldItem = e.OldItems[0]; + ArgumentNullException.ThrowIfNull(oldItem); + HandleItemRemoved(e.OldStartingIndex, oldItem); } else { @@ -663,7 +560,7 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Reset: - if (_deferCounter <= 0) + if (deferCounter <= 0) { HandleSourceChanged(); } @@ -674,37 +571,37 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private bool HandleItemAdded(int newStartingIndex, object newItem, int? viewIndex = null) { - if (_filter != null && !_filter(newItem)) + if (filter is not null && !filter(newItem)) { return false; } - var newViewIndex = _view.Count; + int newViewIndex = view.Count; - if (_sortDescriptions.Any()) + if (sortDescriptions.Any()) { - _sortProperties.Clear(); - newViewIndex = _view.BinarySearch(newItem, this); + sortProperties.Clear(); + newViewIndex = view.BinarySearch(newItem, this); if (newViewIndex < 0) { newViewIndex = ~newViewIndex; } } - else if (_filter != null) + else if (filter is not null) { - if (_source == null) + if (source is null) { HandleSourceChanged(); return false; } - if (newStartingIndex == 0 || _view.Count == 0) + if (newStartingIndex == 0 || view.Count == 0) { newViewIndex = 0; } - else if (newStartingIndex == _source.Count - 1) + else if (newStartingIndex == source.Count - 1) { - newViewIndex = _view.Count; + newViewIndex = view.Count; } else if (viewIndex.HasValue) { @@ -712,7 +609,7 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify } else { - for (int i = 0, j = 0; i < _source.Count; i++) + for (int i = 0, j = 0; i < source.Count; i++) { if (i == newStartingIndex) { @@ -720,7 +617,7 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify break; } - if (_view[j] == _source[i]) + if (view[j] == source[i]) { j++; } @@ -728,27 +625,26 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify } } - _view.Insert(newViewIndex, newItem); + view.Insert(newViewIndex, newItem); if (newViewIndex <= CurrentPosition) { CurrentPosition++; } - var e = new VectorChangedEventArgs(CollectionChange.ItemInserted, newViewIndex, newItem); - OnVectorChanged(e); + OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, newViewIndex, newItem)); return true; } private void HandleItemRemoved(int oldStartingIndex, object oldItem) { - if (_filter != null && !_filter(oldItem)) + if (filter is not null && !filter(oldItem)) { return; } - if (oldStartingIndex < 0 || oldStartingIndex >= _view.Count || !Equals(_view[oldStartingIndex], oldItem)) + if (oldStartingIndex < 0 || oldStartingIndex >= view.Count || !Equals(view[oldStartingIndex], oldItem)) { - oldStartingIndex = _view.IndexOf(oldItem); + oldStartingIndex = view.IndexOf(oldItem); } if (oldStartingIndex < 0) @@ -761,19 +657,18 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private void RemoveFromView(int itemIndex, object item) { - _view.RemoveAt(itemIndex); + view.RemoveAt(itemIndex); if (itemIndex <= CurrentPosition) { CurrentPosition--; } - var e = new VectorChangedEventArgs(CollectionChange.ItemRemoved, itemIndex, item); - OnVectorChanged(e); + OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, itemIndex, item)); } - private void SortDescriptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void SortDescriptionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (_deferCounter > 0) + if (deferCounter > 0) { return; } @@ -783,7 +678,7 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify private bool MoveCurrentToIndex(int i) { - if (i < -1 || i >= _view.Count) + if (i < -1 || i >= view.Count) { return false; } @@ -793,7 +688,7 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify return false; } - var e = new CurrentChangingEventArgs(); + CurrentChangingEventArgs e = new(); OnCurrentChanging(e); if (e.Cancel) { @@ -801,16 +696,59 @@ internal partial class AdvancedCollectionView : IAdvancedCollectionView, INotify } CurrentPosition = i; - OnCurrentChanged(null!); + OnCurrentChanged(default!); 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. + private void OnCurrentChanging(CurrentChangingEventArgs e) + { + if (deferCounter > 0) + { + return; + } + + CurrentChanging?.Invoke(this, e); + } + + private void OnCurrentChanged(object e) + { + if (deferCounter > 0) + { + return; + } + + CurrentChanged?.Invoke(this, e); + OnPropertyChanged(nameof(CurrentItem)); + } + + private void OnVectorChanged(IVectorChangedEventArgs e) + { + if (deferCounter > 0) + { + return; + } + + VectorChanged?.Invoke(this, e); + OnPropertyChanged(nameof(Count)); + } + + internal sealed class NotificationDeferrer : IDisposable + { + private readonly AdvancedCollectionView advancedCollectionView; + private readonly object? currentItem; + + public NotificationDeferrer(AdvancedCollectionView acvs) + { + advancedCollectionView = acvs; + currentItem = advancedCollectionView.CurrentItem; + advancedCollectionView.deferCounter++; + } + + public void Dispose() + { + advancedCollectionView.MoveCurrentTo(currentItem); + advancedCollectionView.deferCounter--; + advancedCollectionView.Refresh(); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs index 168ca7d9..d0e1a439 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/VectorChangedEventArgs.cs @@ -5,36 +5,15 @@ using Windows.Foundation.Collections; namespace Snap.Hutao.Control.Collection.AdvancedCollectionView; -/// -/// Vector changed EventArgs -/// -internal class VectorChangedEventArgs : IVectorChangedEventArgs +internal sealed class VectorChangedEventArgs : IVectorChangedEventArgs { - /// - /// Initializes a new instance of the class. - /// - /// collection change type - /// index of item changed - /// item changed public VectorChangedEventArgs(CollectionChange cc, int index = -1, object item = null!) { CollectionChange = cc; Index = (uint)index; } - /// - /// Gets the type of change that occurred in the vector. - /// - /// - /// The type of change in the vector. - /// public CollectionChange CollectionChange { get; } - /// - /// Gets the position where the change occurred in the vector. - /// - /// - /// The zero-based position where the change occurred in the vector, if applicable. - /// public uint Index { get; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs index 5689cc8b..263a6378 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ThrowHelper.cs @@ -73,6 +73,15 @@ internal static class ThrowHelper throw new NotSupportedException(message); } + [MethodImpl(MethodImplOptions.NoInlining)] + public static void NotSupportedIf(bool condition, string message) + { + if (condition) + { + throw new NotSupportedException(message); + } + } + [DoesNotReturn] [MethodImpl(MethodImplOptions.NoInlining)] public static OperationCanceledException OperationCanceled(string message, Exception? inner = default) From 26748e5885344966a040c401b0fd1a37c36daef2 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Tue, 6 Feb 2024 11:16:59 +0800 Subject: [PATCH 3/3] generic AdvancedCollectionView --- .../AdvancedCollectionView.cs | 182 ++++++++++-------- .../IAdvancedCollectionView.cs | 105 ++++++++++ .../Achievement/AchievementViewModel.cs | 35 ++-- .../ViewModel/Game/GameAccountFilter.cs | 4 +- .../ViewModel/Game/LaunchGameViewModel.cs | 8 +- .../ViewModel/Game/LaunchGameViewModelSlim.cs | 4 +- .../Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs | 4 +- .../Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs | 4 +- .../ViewModel/Wiki/WikiAvatarViewModel.cs | 8 +- .../ViewModel/Wiki/WikiMonsterViewModel.cs | 8 +- .../ViewModel/Wiki/WikiWeaponViewModel.cs | 8 +- 11 files changed, 245 insertions(+), 125 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/IAdvancedCollectionView.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs index 386b4340..d85c64e5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/AdvancedCollectionView.cs @@ -16,25 +16,26 @@ using NotifyCollectionChangedAction = System.Collections.Specialized.NotifyColle namespace Snap.Hutao.Control.Collection.AdvancedCollectionView; -internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer +internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer + where T : class { - private readonly List view; + private readonly List view; private readonly ObservableCollection sortDescriptions; - private readonly Dictionary sortProperties; + private readonly Dictionary sortProperties; private readonly bool liveShapingEnabled; - private readonly HashSet observedFilterProperties = []; + private readonly HashSet observedFilterProperties = []; - private IList source; - private Predicate? filter; + private IList source; + private Predicate? filter; private int deferCounter; - private WeakEventListener? sourceWeakEventListener; + private WeakEventListener, object?, NotifyCollectionChangedEventArgs>? sourceWeakEventListener; public AdvancedCollectionView() - : this(new List(0)) + : this(new List(0)) { } - public AdvancedCollectionView(IList source, bool isLiveShaping = false) + public AdvancedCollectionView(IList source, bool isLiveShaping = false) { liveShapingEnabled = isLiveShaping; view = []; @@ -44,22 +45,22 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP Source = source; } - public event VectorChangedEventHandler? VectorChanged; - public event EventHandler? CurrentChanged; public event CurrentChangingEventHandler? CurrentChanging; public event PropertyChangedEventHandler? PropertyChanged; - public IList Source + public event VectorChangedEventHandler? VectorChanged; + + public IList Source { get => source; [MemberNotNull(nameof(source))] set { - if (source == value) + if (ReferenceEquals(source, value)) { return; } @@ -74,22 +75,21 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP sourceWeakEventListener?.Detach(); - if (source is INotifyCollectionChanged sourceNcc) + if (source is INotifyCollectionChanged sourceNotifyCollectionChanged) { - sourceWeakEventListener = - new WeakEventListener(this) - { - // Call the actual collection changed event - OnEventAction = (source, changed, arg3) => SourceNotifyCollectionChangedCollectionChanged(source, arg3), + sourceWeakEventListener = new WeakEventListener, object?, NotifyCollectionChangedEventArgs>(this) + { + // Call the actual collection changed event + OnEventAction = (source, changed, arg3) => SourceNotifyCollectionChangedCollectionChanged(source, arg3), - // The source doesn't exist anymore - OnDetachAction = (listener) => - { - ArgumentNullException.ThrowIfNull(sourceWeakEventListener); - sourceNcc.CollectionChanged -= sourceWeakEventListener.OnEvent; - }, - }; - sourceNcc.CollectionChanged += sourceWeakEventListener.OnEvent; + // The source doesn't exist anymore + OnDetachAction = (listener) => + { + ArgumentNullException.ThrowIfNull(sourceWeakEventListener); + sourceNotifyCollectionChanged.CollectionChanged -= sourceWeakEventListener.OnEvent; + }, + }; + sourceNotifyCollectionChanged.CollectionChanged += sourceWeakEventListener.OnEvent; } HandleSourceChanged(); @@ -112,9 +112,9 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP get => default!; } - public object? CurrentItem + public T? CurrentItem { - get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : null; + get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : default; set => MoveCurrentTo(value); } @@ -122,7 +122,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP public bool HasMoreItems { - get => (source as ISupportIncrementalLoading)?.HasMoreItems ?? false; + get => source is ISupportIncrementalLoading { HasMoreItems: true }; } public bool IsCurrentAfterLast @@ -140,7 +140,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP get => true; } - public Predicate Filter + public Predicate? Filter { get => filter; set @@ -165,12 +165,17 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP get => sortDescriptions; } - public IEnumerable SourceCollection + public IEnumerable SourceCollection { get => source; } - public object this[int index] + public IReadOnlyList View + { + get => view; + } + + public T this[int index] { get => view[index]; set => view[index] = value; @@ -191,7 +196,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP HandleSortChanged(); } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return view.GetEnumerator(); } @@ -201,7 +206,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP return view.GetEnumerator(); } - public void Add(object item) + public void Add(T item) { ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); source.Add(item); @@ -213,29 +218,30 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP source.Clear(); } - public bool Contains(object item) + public bool Contains(T item) { return view.Contains(item); } - public void CopyTo(object[] array, int arrayIndex) + public void CopyTo(T[] array, int arrayIndex) { view.CopyTo(array, arrayIndex); } - public bool Remove(object item) + public bool Remove(T item) { ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); source.Remove(item); return true; } - public int IndexOf(object item) + [SuppressMessage("", "SH007")] + public int IndexOf(T? item) { - return view.IndexOf(item); + return view.IndexOf(item!); } - public void Insert(int index, object item) + public void Insert(int index, T item) { ThrowHelper.NotSupportedIf(IsReadOnly, "Collection is read-only."); source.Insert(index, item); @@ -246,9 +252,9 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP Remove(view[index]); } - public bool MoveCurrentTo(object? item) + public bool MoveCurrentTo(T? item) { - return item == CurrentItem || MoveCurrentToIndex(IndexOf(item)); + return (item is not null && item.Equals(CurrentItem)) || MoveCurrentToIndex(IndexOf(item)); } public bool MoveCurrentToPosition(int index) @@ -296,14 +302,14 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP return new NotificationDeferrer(this); } - int IComparer.Compare(object? x, object? y) + int IComparer.Compare(T? x, T? y) { if (sortProperties.Count <= 0) { - Type? listType = source?.GetType(); + Type listType = source.GetType(); Type? type; - if (listType is { IsGenericType: true }) + if (listType.IsGenericType) { type = listType.GetGenericArguments()[0]; } @@ -323,7 +329,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP foreach (SortDescription sd in sortDescriptions) { - object cx, cy; + T? cx, cy; if (string.IsNullOrEmpty(sd.PropertyName)) { @@ -332,17 +338,17 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP } else { - PropertyInfo pi = sortProperties[sd.PropertyName]; + PropertyInfo? pi = sortProperties[sd.PropertyName]; - cx = pi.GetValue(x!); - cy = pi.GetValue(y!); + cx = (T?)pi?.GetValue(x); + cy = (T?)pi?.GetValue(y); } int cmp = sd.Comparer.Compare(cx, cy); - if (cmp != 0) + if (cmp is not 0) { - return sd.Direction == SortDirection.Ascending ? +cmp : -cmp; + return sd.Direction is SortDirection.Ascending ? +cmp : -cmp; } } @@ -361,25 +367,28 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP return; } - bool? filterResult = filter?.Invoke(item); + ArgumentNullException.ThrowIfNull(item); + T typedItem = (T)item; + + bool? filterResult = filter?.Invoke(typedItem); if (filterResult.HasValue && observedFilterProperties.Contains(e.PropertyName)) { - int viewIndex = view.IndexOf(item); + int viewIndex = view.IndexOf(typedItem); if (viewIndex != -1 && !filterResult.Value) { - RemoveFromView(viewIndex, item); + RemoveFromView(viewIndex, typedItem); } else if (viewIndex == -1 && filterResult.Value) { - int index = source.IndexOf(item); - HandleItemAdded(index, item); + int index = source.IndexOf(typedItem); + HandleItemAdded(index, typedItem); } } if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName)) { - int oldIndex = view.IndexOf(item); + int oldIndex = view.IndexOf(typedItem); // Check if item is in view: if (oldIndex < 0) @@ -388,7 +397,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP } view.RemoveAt(oldIndex); - int targetIndex = view.BinarySearch(item, this); + int targetIndex = view.BinarySearch(typedItem, this); if (targetIndex < 0) { targetIndex = ~targetIndex; @@ -397,15 +406,15 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP // Only trigger expensive UI updates if the index really changed: if (targetIndex != oldIndex) { - OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, item)); + OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemRemoved, oldIndex, typedItem)); - view.Insert(targetIndex, item); + view.Insert(targetIndex, typedItem); - OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, item)); + OnVectorChanged(new VectorChangedEventArgs(CollectionChange.ItemInserted, targetIndex, typedItem)); } else { - view.Insert(targetIndex, item); + view.Insert(targetIndex, typedItem); } } else if (string.IsNullOrEmpty(e.PropertyName)) @@ -421,9 +430,12 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP return; } - foreach (INotifyPropertyChanged item in items.OfType()) + foreach (object item in items) { - item.PropertyChanged += ItemOnPropertyChanged; + if (item is INotifyPropertyChanged notifyPropertyChanged) + { + notifyPropertyChanged.PropertyChanged += ItemOnPropertyChanged; + } } } @@ -434,9 +446,12 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP return; } - foreach (INotifyPropertyChanged item in items.OfType()) + foreach (object item in items) { - item.PropertyChanged -= ItemOnPropertyChanged; + if (item is INotifyPropertyChanged notifyPropertyChanged) + { + notifyPropertyChanged.PropertyChanged -= ItemOnPropertyChanged; + } } } @@ -454,7 +469,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP { for (int index = 0; index < view.Count; index++) { - object item = view.ElementAt(index); + T item = view[index]; if (filter(item)) { continue; @@ -465,12 +480,11 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP } } - HashSet viewHash = new(view); + HashSet viewHash = new(view); int viewIndex = 0; for (int index = 0; index < source.Count; index++) { - object? item = source[index]; - ArgumentNullException.ThrowIfNull(item); + T item = source[index]; if (viewHash.Contains(item)) { viewIndex++; @@ -487,16 +501,16 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP private void HandleSourceChanged() { sortProperties.Clear(); - object? currentItem = CurrentItem; + T? currentItem = CurrentItem; view.Clear(); - foreach (object? item in Source) + foreach (T item in Source) { if (filter is not null && !filter(item)) { continue; } - if (sortDescriptions.Any()) + if (sortDescriptions.Count > 0) { int targetIndex = view.BinarySearch(item, this); if (targetIndex < 0) @@ -530,7 +544,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP { object? newItem = e.NewItems[0]; ArgumentNullException.ThrowIfNull(newItem); - HandleItemAdded(e.NewStartingIndex, newItem); + HandleItemAdded(e.NewStartingIndex, (T)newItem); } else { @@ -548,7 +562,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP { object? oldItem = e.OldItems[0]; ArgumentNullException.ThrowIfNull(oldItem); - HandleItemRemoved(e.OldStartingIndex, oldItem); + HandleItemRemoved(e.OldStartingIndex, (T)oldItem); } else { @@ -569,7 +583,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP } } - private bool HandleItemAdded(int newStartingIndex, object newItem, int? viewIndex = null) + private bool HandleItemAdded(int newStartingIndex, T newItem, int? viewIndex = null) { if (filter is not null && !filter(newItem)) { @@ -578,7 +592,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP int newViewIndex = view.Count; - if (sortDescriptions.Any()) + if (sortDescriptions.Count > 0) { sortProperties.Clear(); newViewIndex = view.BinarySearch(newItem, this); @@ -617,7 +631,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP break; } - if (view[j] == source[i]) + if (Equals(view[j], source[i])) { j++; } @@ -635,7 +649,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP return true; } - private void HandleItemRemoved(int oldStartingIndex, object oldItem) + private void HandleItemRemoved(int oldStartingIndex, T oldItem) { if (filter is not null && !filter(oldItem)) { @@ -655,7 +669,7 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP RemoveFromView(oldStartingIndex, oldItem); } - private void RemoveFromView(int itemIndex, object item) + private void RemoveFromView(int itemIndex, T item) { view.RemoveAt(itemIndex); if (itemIndex <= CurrentPosition) @@ -734,10 +748,10 @@ internal sealed class AdvancedCollectionView : IAdvancedCollectionView, INotifyP internal sealed class NotificationDeferrer : IDisposable { - private readonly AdvancedCollectionView advancedCollectionView; - private readonly object? currentItem; + private readonly AdvancedCollectionView advancedCollectionView; + private readonly T? currentItem; - public NotificationDeferrer(AdvancedCollectionView acvs) + public NotificationDeferrer(AdvancedCollectionView acvs) { advancedCollectionView = acvs; currentItem = advancedCollectionView.CurrentItem; diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/IAdvancedCollectionView.cs b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/IAdvancedCollectionView.cs new file mode 100644 index 00000000..e8fd0810 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Collection/AdvancedCollectionView/IAdvancedCollectionView.cs @@ -0,0 +1,105 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using CommunityToolkit.WinUI.Collections; +using Microsoft.UI.Xaml.Data; +using System.Collections; + +namespace Snap.Hutao.Control.Collection.AdvancedCollectionView; + +internal interface IAdvancedCollectionView : ICollectionView, IEnumerable + where T : class +{ + bool CanFilter { get; } + + bool CanSort { get; } + + object? ICollectionView.CurrentItem + { + get => CurrentItem; + } + + new T? CurrentItem { get; } + + Predicate? Filter { get; set; } + + IList SortDescriptions { get; } + + IEnumerable SourceCollection { get; } + + object IList.this[int index] + { + get => this[index]; + set => this[index] = (T)value; + } + + new T this[int index] { get; set; } + + void ICollection.Add(object item) + { + Add((T)item); + } + + void Add(T item); + + void ClearObservedFilterProperties(); + + bool ICollection.Contains(object item) + { + return Contains((T)item); + } + + bool Contains(T item); + + void ICollection.CopyTo(object[] array, int arrayIndex) + { + CopyTo((T[])array, arrayIndex); + } + + void CopyTo(T[] array, int arrayIndex); + + IDisposable DeferRefresh(); + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + new IEnumerator GetEnumerator(); + + int IList.IndexOf(object item) + { + return IndexOf((T)item); + } + + int IndexOf(T item); + + void IList.Insert(int index, object item) + { + Insert(index, (T)item); + } + + void Insert(int index, T item); + + bool ICollectionView.MoveCurrentTo(object item) + { + return MoveCurrentTo((T)item); + } + + bool MoveCurrentTo(T item); + + void ObserveFilterProperty(string propertyName); + + void Refresh(); + + void RefreshFilter(); + + void RefreshSorting(); + + bool ICollection.Remove(object item) + { + return Remove((T)item); + } + + bool Remove(T item); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs index 7bffcb88..4c1891e4 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Achievement/AchievementViewModel.cs @@ -43,7 +43,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav private readonly JsonSerializerOptions options; private readonly ITaskContext taskContext; - private AdvancedCollectionView? achievements; + private AdvancedCollectionView? achievements; private List? achievementGoals; private AchievementGoalView? selectedAchievementGoal; private ObservableCollection? archives; @@ -85,7 +85,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav /// /// 成就视图 /// - public AdvancedCollectionView? Achievements + public AdvancedCollectionView? Achievements { get => achievements; set => SetProperty(ref achievements, value); @@ -342,17 +342,19 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav [Command("SortUncompletedSwitchCommand")] private void UpdateAchievementsSort() { - if (Achievements is not null) + if (Achievements is null) { - if (IsUncompletedItemsFirst) - { - Achievements.SortDescriptions.Add(uncompletedItemsFirstSortDescription); - Achievements.SortDescriptions.Add(completionTimeSortDescription); - } - else - { - Achievements.SortDescriptions.Clear(); - } + return; + } + + if (IsUncompletedItemsFirst) + { + Achievements.SortDescriptions.Add(uncompletedItemsFirstSortDescription); + Achievements.SortDescriptions.Add(completionTimeSortDescription); + } + else + { + Achievements.SortDescriptions.Clear(); } } @@ -367,7 +369,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav else { Model.Primitive.AchievementGoalId goalId = goal.Id; - Achievements.Filter = (object o) => o is AchievementView view && view.Inner.Goal == goalId; + Achievements.Filter = (AchievementView view) => view.Inner.Goal == goalId; } } } @@ -383,19 +385,18 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav { if (uint.TryParse(search, out uint achievementId)) { - Achievements.Filter = obj => ((AchievementView)obj).Inner.Id == achievementId; + Achievements.Filter = view => view.Inner.Id == achievementId; return; } if (VersionRegex().IsMatch(search)) { - Achievements.Filter = obj => ((AchievementView)obj).Inner.Version == search; + Achievements.Filter = view => view.Inner.Version == search; return; } - Achievements.Filter = obj => + Achievements.Filter = view => { - AchievementView view = (AchievementView)obj; return view.Inner.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase) || view.Inner.Description.Contains(search, StringComparison.CurrentCultureIgnoreCase); }; diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GameAccountFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GameAccountFilter.cs index d3a52c75..07269a06 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GameAccountFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/GameAccountFilter.cs @@ -15,13 +15,13 @@ internal sealed class GameAccountFilter this.type = type; } - public bool Filter(object? item) + public bool Filter(GameAccount? item) { if (type is null) { return true; } - return item is GameAccount account && account.Type == type; + return item is not null && item.Type == type; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs index db6c0190..6aeb6596 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModel.cs @@ -50,7 +50,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView private readonly AppOptions appOptions; private LaunchScheme? selectedScheme; - private AdvancedCollectionView? gameAccountsView; + private AdvancedCollectionView? gameAccountsView; private GameAccount? selectedGameAccount; private GameResource? gameResource; private bool gamePathSelectedAndValid; @@ -75,7 +75,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView set => SetSelectedSchemeAsync(value).SafeForget(); } - public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); } + public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); } public GameAccount? SelectedGameAccount { get => selectedGameAccount; set => SetProperty(ref selectedGameAccount, value); } @@ -130,9 +130,9 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView ArgumentNullException.ThrowIfNull(GameAccountsView); // Exists in the source collection - if (GameAccountsView.SourceCollection.Cast().FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount) + if (GameAccountsView.SourceCollection.FirstOrDefault(g => g.AttachUid == uid) is { } sourceAccount) { - SelectedGameAccount = GameAccountsView.Cast().FirstOrDefault(g => g.AttachUid == uid); + SelectedGameAccount = GameAccountsView.View.FirstOrDefault(g => g.AttachUid == uid); // But not exists in the view for current scheme if (SelectedGameAccount is null) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs index 9fbb76c9..300a77c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Game/LaunchGameViewModelSlim.cs @@ -27,13 +27,13 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli private readonly IGameServiceFacade gameService; private readonly ITaskContext taskContext; - private AdvancedCollectionView? gameAccountsView; + private AdvancedCollectionView? gameAccountsView; private GameAccount? selectedGameAccount; private GameAccountFilter? gameAccountFilter; public LaunchStatusOptions LaunchStatusOptions { get => launchStatusOptions; } - public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); } + public AdvancedCollectionView? GameAccountsView { get => gameAccountsView; set => SetProperty(ref gameAccountsView, value); } /// /// 选中的账号 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs index 5c6ac72b..2d765c25 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs @@ -17,9 +17,9 @@ internal static class AvatarFilter /// /// 输入 /// 筛选操作 - public static Predicate Compile(string input) + public static Predicate Compile(string input) { - return (object o) => o is Avatar avatar && DoFilter(input, avatar); + return (Avatar avatar) => DoFilter(input, avatar); } private static bool DoFilter(string input, Avatar avatar) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs index 0e1540f9..06731581 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WeaponFilter.cs @@ -17,9 +17,9 @@ internal static class WeaponFilter /// /// 输入 /// 筛选操作 - public static Predicate Compile(string input) + public static Predicate Compile(string input) { - return (object o) => o is Weapon weapon && DoFilter(input, weapon); + return (Weapon weapon) => DoFilter(input, weapon); } private static bool DoFilter(string input, Weapon weapon) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index e50fba5c..250197cb 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -43,7 +43,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private readonly CalculateClient calculateClient; private readonly IUserService userService; - private AdvancedCollectionView? avatars; + private AdvancedCollectionView? avatars; private Avatar? selected; private string? filterText; private BaseValueInfo? baseValueInfo; @@ -53,7 +53,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel /// /// 角色列表 /// - public AdvancedCollectionView? Avatars { get => avatars; set => SetProperty(ref avatars, value); } + public AdvancedCollectionView? Avatars { get => avatars; set => SetProperty(ref avatars, value); } /// /// 选中的角色 @@ -99,8 +99,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel await CombineComplexDataAsync(list, idMaterialMap).ConfigureAwait(false); await taskContext.SwitchToMainThreadAsync(); - Avatars = new AdvancedCollectionView(list, true); - Selected = Avatars.Cast().FirstOrDefault(); + Avatars = new(list, true); + Selected = Avatars.View.ElementAtOrDefault(0); return true; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs index ace8bf7f..69ee0bbd 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs @@ -20,7 +20,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel private readonly IMetadataService metadataService; private readonly ITaskContext taskContext; - private AdvancedCollectionView? monsters; + private AdvancedCollectionView? monsters; private Monster? selected; private BaseValueInfo? baseValueInfo; private Dictionary>? levelMonsterCurveMap; @@ -28,7 +28,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel /// /// 角色列表 /// - public AdvancedCollectionView? Monsters { get => monsters; set => SetProperty(ref monsters, value); } + public AdvancedCollectionView? Monsters { get => monsters; set => SetProperty(ref monsters, value); } /// /// 选中的角色 @@ -65,8 +65,8 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel List ordered = monsters.SortBy(m => m.RelationshipId.Value); await taskContext.SwitchToMainThreadAsync(); - Monsters = new AdvancedCollectionView(ordered, true); - Selected = Monsters.Cast().FirstOrDefault(); + Monsters = new(ordered, true); + Selected = Monsters.View.ElementAtOrDefault(0); return true; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 85608aec..37615619 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -40,7 +40,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel private readonly IInfoBarService infoBarService; private readonly IUserService userService; - private AdvancedCollectionView? weapons; + private AdvancedCollectionView? weapons; private Weapon? selected; private string? filterText; private BaseValueInfo? baseValueInfo; @@ -50,7 +50,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel /// /// 角色列表 /// - public AdvancedCollectionView? Weapons { get => weapons; set => SetProperty(ref weapons, value); } + public AdvancedCollectionView? Weapons { get => weapons; set => SetProperty(ref weapons, value); } /// /// 选中的角色 @@ -96,8 +96,8 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel await taskContext.SwitchToMainThreadAsync(); - Weapons = new AdvancedCollectionView(list, true); - Selected = Weapons.Cast().FirstOrDefault(); + Weapons = new(list, true); + Selected = Weapons.View.ElementAtOrDefault(0); } }