refine AdvancedCollectionView

This commit is contained in:
DismissedLight
2024-07-15 17:31:06 +08:00
parent 6489f66d13
commit bfdb4b0060
13 changed files with 116 additions and 156 deletions

View File

@@ -18,7 +18,7 @@ internal sealed class AdvancedDbCollectionView<TEntity> : AdvancedCollectionView
private bool savingToDatabase = true; private bool savingToDatabase = true;
public AdvancedDbCollectionView(IList<TEntity> source, IServiceProvider serviceProvider) public AdvancedDbCollectionView(IList<TEntity> source, IServiceProvider serviceProvider)
: base(source, true) : base(source)
{ {
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
} }
@@ -85,7 +85,7 @@ internal sealed class AdvancedDbCollectionView<TEntityAccess, TEntity> : Advance
private bool savingToDatabase = true; private bool savingToDatabase = true;
public AdvancedDbCollectionView(IList<TEntityAccess> source, IServiceProvider serviceProvider) public AdvancedDbCollectionView(IList<TEntityAccess> source, IServiceProvider serviceProvider)
: base(source, true) : base(source)
{ {
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
} }

View File

@@ -41,7 +41,7 @@ internal sealed partial class SummaryFactory : ISummaryFactory
return new() return new()
{ {
Avatars = new(views, true), Avatars = new(views),
}; };
} }
} }

View File

@@ -212,7 +212,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
return new() return new()
{ {
// history // history
HistoryWishes = taskContext.InvokeOnMainThread(() => new AdvancedCollectionView<HistoryWish>(historyWishes, true)), HistoryWishes = taskContext.InvokeOnMainThread(() => new AdvancedCollectionView<HistoryWish>(historyWishes)),
// avatars // avatars
OrangeAvatars = orangeAvatarCounter.ToStatisticsList(), OrangeAvatars = orangeAvatarCounter.ToStatisticsList(),

View File

@@ -214,7 +214,7 @@ internal sealed partial class UserInitializationService : IUserInitializationSer
if (userGameRolesResponse.IsOk()) if (userGameRolesResponse.IsOk())
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
user.UserGameRoles = new(userGameRolesResponse.Data.List, true); user.UserGameRoles = new(userGameRolesResponse.Data.List);
return user.UserGameRoles.Count > 0; return user.UserGameRoles.Count > 0;
} }
else else

View File

@@ -7,6 +7,7 @@ using Microsoft.UI.Xaml.Data;
using System.Collections; using System.Collections;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Windows.Foundation; using Windows.Foundation;
using Windows.Foundation.Collections; using Windows.Foundation.Collections;
@@ -17,10 +18,10 @@ namespace Snap.Hutao.UI.Xaml.Data;
internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<T> internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<T>
where T : class, IAdvancedCollectionViewItem where T : class, IAdvancedCollectionViewItem
{ {
private readonly bool created;
private readonly List<T> view; private readonly List<T> view;
private readonly ObservableCollection<SortDescription> sortDescriptions; private readonly ObservableCollection<SortDescription> sortDescriptions;
private readonly bool liveShapingEnabled; //private readonly HashSet<string?> observedFilterProperties = [];
private readonly HashSet<string?> observedFilterProperties = [];
private IList<T> source; private IList<T> source;
private Predicate<T>? filter; private Predicate<T>? filter;
@@ -32,13 +33,14 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
{ {
} }
public AdvancedCollectionView(IList<T> source, bool isLiveShaping = true) public AdvancedCollectionView(IList<T> source)
{ {
liveShapingEnabled = isLiveShaping;
view = []; view = [];
sortDescriptions = []; sortDescriptions = [];
sortDescriptions.CollectionChanged += SortDescriptionsCollectionChanged; sortDescriptions.CollectionChanged += SortDescriptionsCollectionChanged;
Source = source; Source = source;
created = true;
} }
public event EventHandler<object>? CurrentChanged; public event EventHandler<object>? CurrentChanged;
@@ -49,7 +51,61 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
public event VectorChangedEventHandler<object>? VectorChanged; public event VectorChangedEventHandler<object>? VectorChanged;
public IList<T> Source public int Count
{
get => view.Count;
}
public bool IsReadOnly { get => source is null || source is not INotifyCollectionChanged || source.IsReadOnly; }
public IObservableVector<object> CollectionGroups
{
get => default!;
}
public T? CurrentItem
{
get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : default;
set => MoveCurrentTo(value);
}
public int CurrentPosition { get; private set; }
public bool HasMoreItems { get => source is ISupportIncrementalLoading { HasMoreItems: true }; }
public bool IsCurrentAfterLast { get => CurrentPosition >= view.Count; }
public bool IsCurrentBeforeFirst { get => CurrentPosition < 0; }
public bool CanFilter { get => true; }
public Predicate<T>? Filter
{
get => filter;
set
{
if (filter == value)
{
return;
}
filter = value;
HandleFilterChanged();
}
}
public bool CanSort
{
get => true;
}
public ObservableCollection<SortDescription> SortDescriptions { get => sortDescriptions; }
public IList<T> SourceCollection { get => source; }
public List<T> View { get => view; }
private IList<T> Source
{ {
get => source; get => source;
@@ -73,101 +129,24 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
if (source is INotifyCollectionChanged sourceINCC) if (source is INotifyCollectionChanged sourceINCC)
{ {
sourceWeakEventListener = new WeakEventListener<AdvancedCollectionView<T>, object?, NotifyCollectionChangedEventArgs>(this) sourceWeakEventListener = new(this)
{ {
OnEventAction = static (target, source, args) => target.SourceNotifyCollectionChangedCollectionChanged(args), OnEventAction = OnSourceNotifyCollectionCollectionChanged,
OnDetachAction = (listener) => sourceINCC.CollectionChanged -= listener.OnEvent, OnDetachAction = listener => sourceINCC.CollectionChanged -= listener.OnEvent,
}; };
sourceINCC.CollectionChanged += sourceWeakEventListener.OnEvent; sourceINCC.CollectionChanged += sourceWeakEventListener.OnEvent;
} }
HandleSourceChanged(); HandleSourceChanged();
OnPropertyChanged(); OnPropertyChanged();
}
}
public int Count static void OnSourceNotifyCollectionCollectionChanged(AdvancedCollectionView<T> target, object? source, NotifyCollectionChangedEventArgs args)
{
get => view.Count;
}
public bool IsReadOnly
{
get => source is null;
}
public IObservableVector<object> CollectionGroups
{
get => default!;
}
public T? CurrentItem
{
get => CurrentPosition > -1 && CurrentPosition < view.Count ? view[CurrentPosition] : default;
set => MoveCurrentTo(value);
}
public int CurrentPosition
{
get;
private set;
}
public bool HasMoreItems
{
get => source is ISupportIncrementalLoading { HasMoreItems: true };
}
public bool IsCurrentAfterLast
{
get => CurrentPosition >= view.Count;
}
public bool IsCurrentBeforeFirst
{
get => CurrentPosition < 0;
}
public bool CanFilter
{
get => true;
}
public Predicate<T>? Filter
{
get => filter;
set
{
if (filter == value)
{ {
return; target.SourceNotifyCollectionChangedCollectionChanged(args);
} }
filter = value;
HandleFilterChanged();
} }
} }
public bool CanSort
{
get => true;
}
public ObservableCollection<SortDescription> SortDescriptions
{
get => sortDescriptions;
}
public IList<T> SourceCollection
{
get => source;
}
public List<T> View
{
get => view;
}
public T this[int index] public T this[int index]
{ {
get => view[index]; get => view[index];
@@ -275,16 +254,6 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
return (source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count); return (source as ISupportIncrementalLoading)?.LoadMoreItemsAsync(count);
} }
public void ObserveFilterProperty(string propertyName)
{
observedFilterProperties.Add(propertyName);
}
public void ClearObservedFilterProperties()
{
observedFilterProperties.Clear();
}
public IDisposable DeferRefresh() public IDisposable DeferRefresh()
{ {
return new NotificationDeferrer(this); return new NotificationDeferrer(this);
@@ -324,36 +293,20 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
private void OnPropertyChanged([CallerMemberName] string propertyName = default!) private void OnPropertyChanged([CallerMemberName] string propertyName = default!)
{ {
if (!created)
{
return;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
private void ItemOnPropertyChanged(object? item, PropertyChangedEventArgs e) private void ItemOnPropertyChanged(object? item, PropertyChangedEventArgs e)
{ {
if (!liveShapingEnabled)
{
return;
}
ArgumentNullException.ThrowIfNull(item); ArgumentNullException.ThrowIfNull(item);
T typedItem = (T)item; T typedItem = (T)item;
bool? filterResult = filter?.Invoke(typedItem); if ((filter?.Invoke(typedItem) ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName))
if (filterResult.HasValue && observedFilterProperties.Contains(e.PropertyName))
{
int viewIndex = view.IndexOf(typedItem);
if (viewIndex != -1 && !filterResult.Value)
{
RemoveFromView(viewIndex, typedItem);
}
else if (viewIndex == -1 && filterResult.Value)
{
int index = source.IndexOf(typedItem);
HandleSourceItemAdded(index, typedItem);
}
}
if ((filterResult ?? true) && SortDescriptions.Any(sd => sd.PropertyName == e.PropertyName))
{ {
int oldIndex = view.IndexOf(typedItem); int oldIndex = view.IndexOf(typedItem);
@@ -392,7 +345,7 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
private void AttachPropertyChangedHandler(IEnumerable items) private void AttachPropertyChangedHandler(IEnumerable items)
{ {
if (!liveShapingEnabled || items is null) if (items is null)
{ {
return; return;
} }
@@ -408,7 +361,7 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
private void DetachPropertyChangedHandler(IEnumerable items) private void DetachPropertyChangedHandler(IEnumerable items)
{ {
if (!liveShapingEnabled || items is null) if (items is null)
{ {
return; return;
} }
@@ -467,26 +420,36 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
{ {
T? currentItem = CurrentItem; T? currentItem = CurrentItem;
view.Clear(); view.Clear();
foreach (T item in Source) view.TrimExcess();
{
if (filter is not null && !filter(item))
{
continue;
}
if (sortDescriptions.Count > 0) if (filter is null && sortDescriptions.Count <= 0)
{
// Fast path
View.AddRange(Source);
}
else
{
foreach (T item in Source)
{ {
int targetIndex = view.BinarySearch(item, this); if (filter is not null && !filter(item))
if (targetIndex < 0)
{ {
targetIndex = ~targetIndex; continue;
} }
view.Insert(targetIndex, item); if (sortDescriptions.Count > 0)
} {
else int targetIndex = view.BinarySearch(item, this);
{ if (targetIndex < 0)
view.Add(item); {
targetIndex = ~targetIndex;
}
view.Insert(targetIndex, item);
}
else
{
view.Add(item);
}
} }
} }
@@ -664,6 +627,7 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
if (i < -1 || i >= view.Count) if (i < -1 || i >= view.Count)
{ {
Debugger.Break(); // Figure out how this will hit.
OnPropertyChanged(nameof(CurrentItem)); OnPropertyChanged(nameof(CurrentItem));
return false; return false;
} }
@@ -682,7 +646,7 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
private void OnCurrentChanging(CurrentChangingEventArgs e) private void OnCurrentChanging(CurrentChangingEventArgs e)
{ {
if (deferCounter > 0) if (!created || deferCounter > 0)
{ {
return; return;
} }
@@ -692,7 +656,7 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
private void OnCurrentChanged() private void OnCurrentChanged()
{ {
if (deferCounter > 0) if (!created || deferCounter > 0)
{ {
return; return;
} }
@@ -704,7 +668,7 @@ internal class AdvancedCollectionView<T> : IAdvancedCollectionView<T>, INotifyPr
private void OnVectorChanged(IVectorChangedEventArgs e) private void OnVectorChanged(IVectorChangedEventArgs e)
{ {
if (deferCounter > 0) if (!created || deferCounter > 0)
{ {
return; return;
} }

View File

@@ -44,8 +44,6 @@ internal interface IAdvancedCollectionView<T> : ICollectionView, IEnumerable
void Add(T item); void Add(T item);
void ClearObservedFilterProperties();
bool ICollection<object>.Contains(object item) bool ICollection<object>.Contains(object item)
{ {
return Contains((T)item); return Contains((T)item);
@@ -101,8 +99,6 @@ internal interface IAdvancedCollectionView<T> : ICollectionView, IEnumerable
bool MoveCurrentTo(T? item); bool MoveCurrentTo(T? item);
void ObserveFilterProperty(string propertyName);
void Refresh(); void Refresh();
void RefreshFilter(); void RefreshFilter();

View File

@@ -140,7 +140,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
IAdvancedDbCollectionView<EntityArchive> archives = await scopeContext.AchievementService.GetArchivesAsync(CancellationToken).ConfigureAwait(false); IAdvancedDbCollectionView<EntityArchive> archives = await scopeContext.AchievementService.GetArchivesAsync(CancellationToken).ConfigureAwait(false);
await scopeContext.TaskContext.SwitchToMainThreadAsync(); await scopeContext.TaskContext.SwitchToMainThreadAsync();
AchievementGoals = new(sortedGoals, true); AchievementGoals = new(sortedGoals);
Archives = archives; Archives = archives;
Archives.MoveCurrentTo(Archives.SourceCollection.SelectedOrDefault()); Archives.MoveCurrentTo(Archives.SourceCollection.SelectedOrDefault());
return true; return true;
@@ -299,7 +299,7 @@ internal sealed partial class AchievementViewModel : Abstraction.ViewModel, INav
} }
await scopeContext.TaskContext.SwitchToMainThreadAsync(); await scopeContext.TaskContext.SwitchToMainThreadAsync();
Achievements = new(combined, true); Achievements = new(combined);
AchievementFinishPercent.Update(this); AchievementFinishPercent.Update(this);
UpdateAchievementsFilterByGoal(AchievementGoals?.CurrentItem); UpdateAchievementsFilterByGoal(AchievementGoals?.CurrentItem);
UpdateAchievementsSort(); UpdateAchievementsSort();

View File

@@ -328,7 +328,7 @@ internal sealed partial class LaunchGameViewModel : Abstraction.ViewModel, IView
ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection; ObservableReorderableDbCollection<GameAccount> accounts = gameService.GameAccountCollection;
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true) GameAccountsView = new(accounts)
{ {
Filter = gameAccountFilter.Filter, Filter = gameAccountFilter.Filter,
}; };

View File

@@ -61,7 +61,7 @@ internal sealed partial class LaunchGameViewModelSlim : Abstraction.ViewModelSli
gameAccountFilter = new(scheme?.GetSchemeType()); gameAccountFilter = new(scheme?.GetSchemeType());
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
GameAccountsView = new(accounts, true) GameAccountsView = new(accounts)
{ {
Filter = gameAccountFilter.Filter, Filter = gameAccountFilter.Filter,
}; };

View File

@@ -86,7 +86,7 @@ internal sealed partial class SpiralAbyssRecordViewModel : Abstraction.ViewModel
} }
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
SpiralAbyssEntries = new(collection, true); SpiralAbyssEntries = new(collection);
SpiralAbyssEntries.MoveCurrentTo(SpiralAbyssEntries.SourceCollection.FirstOrDefault(s => s.Engaged)); SpiralAbyssEntries.MoveCurrentTo(SpiralAbyssEntries.SourceCollection.FirstOrDefault(s => s.Engaged));
} }
catch (OperationCanceledException) catch (OperationCanceledException)

View File

@@ -110,7 +110,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
using (await EnterCriticalSectionAsync().ConfigureAwait(false)) using (await EnterCriticalSectionAsync().ConfigureAwait(false))
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
Avatars = new(list, true); Avatars = new(list);
Selected = Avatars.View.ElementAtOrDefault(0); Selected = Avatars.View.ElementAtOrDefault(0);
} }

View File

@@ -69,7 +69,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
using (await EnterCriticalSectionAsync().ConfigureAwait(false)) using (await EnterCriticalSectionAsync().ConfigureAwait(false))
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
Monsters = new(ordered, true); Monsters = new(ordered);
Selected = Monsters.View.ElementAtOrDefault(0); Selected = Monsters.View.ElementAtOrDefault(0);
} }

View File

@@ -110,7 +110,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
{ {
await taskContext.SwitchToMainThreadAsync(); await taskContext.SwitchToMainThreadAsync();
Weapons = new(list, true); Weapons = new(list);
Selected = Weapons.View.ElementAtOrDefault(0); Selected = Weapons.View.ElementAtOrDefault(0);
} }