diff --git a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs index e77f80f3..406e6478 100644 --- a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs @@ -16,22 +16,23 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; +#nullable enable namespace LibationAvalonia.ViewModels { public class ProductsDisplayViewModel : ViewModelBase { /// Number of visible rows has changed - public event EventHandler VisibleCountChanged; - public event EventHandler RemovableCountChanged; + public event EventHandler? VisibleCountChanged; + public event EventHandler? RemovableCountChanged; /// Backing list of all grid entries private readonly AvaloniaList SOURCE = new(); /// Grid entries included in the filter set. If null, all grid entries are shown - private HashSet FilteredInGridEntries; - public string FilterString { get; private set; } + private HashSet? FilteredInGridEntries; + public string? FilterString { get; private set; } - private DataGridCollectionView _gridEntries; - public DataGridCollectionView GridEntries + private DataGridCollectionView? _gridEntries; + public DataGridCollectionView? GridEntries { get => _gridEntries; private set => this.RaiseAndSetIfChanged(ref _gridEntries, value); @@ -60,14 +61,14 @@ namespace LibationAvalonia.ViewModels VisibleCountChanged?.Invoke(this, 0); } - private static readonly System.Reflection.MethodInfo SetFlagsMethod; + private static readonly System.Reflection.MethodInfo? SetFlagsMethod; /// /// Tells the whether it should process changes to the underlying collection /// /// DataGridCollectionView.CollectionViewFlags.ShouldProcessCollectionChanged = 4 private void SetShouldProcessCollectionChanged(bool flagSet) - => SetFlagsMethod.Invoke(GridEntries, new object[] { 4, flagSet }); + => SetFlagsMethod?.Invoke(GridEntries, new object[] { 4, flagSet }); static ProductsDisplayViewModel() { @@ -131,13 +132,16 @@ namespace LibationAvalonia.ViewModels //Saves ~500 ms on a library of ~4500 books. //Perform on UI thread for safety, but at this time, merely setting the DataGridCollectionView //does not trigger UI actions in the way that modifying the list after it's been linked does. - await Dispatcher.UIThread.InvokeAsync(() => GridEntries = new(SOURCE) { Filter = CollectionFilter }); + await Dispatcher.UIThread.InvokeAsync(() => + { + GridEntries = new(SOURCE) { Filter = CollectionFilter }; + GridEntries.CollectionChanged += GridEntries_CollectionChanged; + }); - GridEntries.CollectionChanged += GridEntries_CollectionChanged; GridEntries_CollectionChanged(); } - private void GridEntries_CollectionChanged(object sender = null, EventArgs e = null) + private void GridEntries_CollectionChanged(object? sender = null, EventArgs? e = null) { var count = FilteredInGridEntries?.OfType().Count() @@ -151,7 +155,12 @@ namespace LibationAvalonia.ViewModels /// internal async Task UpdateGridAsync(List dbBooks) { - GridEntries.CollectionChanged -= GridEntries_CollectionChanged; + if (GridEntries == null) + { + //always bind before updating. Binding creates GridEntries. + await BindToGridAsync(dbBooks); + return; + } #region Add new or update existing grid entries @@ -203,7 +212,8 @@ namespace LibationAvalonia.ViewModels await Filter(FilterString); GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); - GridEntries.CollectionChanged += GridEntries_CollectionChanged; + if (GridEntries != null) + GridEntries.CollectionChanged += GridEntries_CollectionChanged; GridEntries_CollectionChanged(); } @@ -211,7 +221,7 @@ namespace LibationAvalonia.ViewModels { foreach (var removed in removedBooks.Cast().Concat(removedSeries).Where(b => b is not null).ToList()) { - if (GridEntries.PassesFilter(removed)) + if (GridEntries?.PassesFilter(removed) ?? false) GridEntries.Remove(removed); else { @@ -222,7 +232,7 @@ namespace LibationAvalonia.ViewModels } } - private void UpsertBook(LibraryBook book, ILibraryBookEntry existingBookEntry) + private void UpsertBook(LibraryBook book, ILibraryBookEntry? existingBookEntry) { if (existingBookEntry is null) // Add the new product to top @@ -232,7 +242,7 @@ namespace LibationAvalonia.ViewModels existingBookEntry.UpdateLibraryBook(book); } - private void UpsertEpisode(LibraryBook episodeBook, ILibraryBookEntry existingEpisodeEntry, List seriesEntries, IEnumerable dbBooks) + private void UpsertEpisode(LibraryBook episodeBook, ILibraryBookEntry? existingEpisodeEntry, List seriesEntries, IEnumerable dbBooks) { if (existingEpisodeEntry is null) { @@ -282,10 +292,13 @@ namespace LibationAvalonia.ViewModels private async Task refreshGrid() { - if (GridEntries.IsEditingItem) - await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit); + if (GridEntries != null) + { + if (GridEntries.IsEditingItem) + await Dispatcher.UIThread.InvokeAsync(GridEntries.CommitEdit); - await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh); + await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh); + } } public async Task ToggleSeriesExpanded(ISeriesEntry seriesEntry) @@ -299,7 +312,7 @@ namespace LibationAvalonia.ViewModels #region Filtering - public async Task Filter(string searchString) + public async Task Filter(string? searchString) { FilterString = searchString; @@ -323,7 +336,7 @@ namespace LibationAvalonia.ViewModels return FilteredInGridEntries.Contains(item); } - private async void SearchEngineCommands_SearchEngineUpdated(object sender, EventArgs e) + private async void SearchEngineCommands_SearchEngineUpdated(object? sender, EventArgs? e) { var filterResults = SOURCE.FilterEntries(FilterString); @@ -366,9 +379,9 @@ namespace LibationAvalonia.ViewModels foreach (var book in selectedBooks) book.PropertyChanged -= GridEntry_PropertyChanged; - void BindingList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + void BindingList_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs? e) { - if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset) + if (e?.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset) return; //After DisplayBooks() re-creates the list, @@ -380,7 +393,8 @@ namespace LibationAvalonia.ViewModels GridEntries.CollectionChanged -= BindingList_CollectionChanged; } - GridEntries.CollectionChanged += BindingList_CollectionChanged; + if (GridEntries != null) + GridEntries.CollectionChanged += BindingList_CollectionChanged; //The RemoveBooksAsync will fire LibrarySizeChanged, which calls ProductsDisplay2.Display(), //so there's no need to remove books from the grid display here. @@ -432,9 +446,9 @@ namespace LibationAvalonia.ViewModels } } - private void GridEntry_PropertyChanged(object sender, PropertyChangedEventArgs e) + private void GridEntry_PropertyChanged(object? sender, PropertyChangedEventArgs? e) { - if (e.PropertyName == nameof(IGridEntry.Remove) && sender is ILibraryBookEntry) + if (e?.PropertyName == nameof(IGridEntry.Remove) && sender is ILibraryBookEntry) { int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true); RemovableCountChanged?.Invoke(this, removeCount); diff --git a/Source/LibationAvalonia/Views/ProcessBookControl.axaml b/Source/LibationAvalonia/Views/ProcessBookControl.axaml index 0592c850..fc227280 100644 --- a/Source/LibationAvalonia/Views/ProcessBookControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessBookControl.axaml @@ -6,10 +6,20 @@ x:DataType="vm:ProcessBookViewModel" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="87" MaxHeight="87" MinHeight="87" MinWidth="300" x:Class="LibationAvalonia.Views.ProcessBookControl" Background="{CompiledBinding BackgroundColor}"> - - + + + + + + + + - + @@ -29,7 +39,7 @@ - + - - - - - - - @@ -65,7 +75,7 @@ - + diff --git a/Source/LibationUiBase/GridView/QueryExtensions.cs b/Source/LibationUiBase/GridView/QueryExtensions.cs index 571c0a3f..a39bb492 100644 --- a/Source/LibationUiBase/GridView/QueryExtensions.cs +++ b/Source/LibationUiBase/GridView/QueryExtensions.cs @@ -2,6 +2,7 @@ using DataLayer; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace LibationUiBase.GridView @@ -47,9 +48,11 @@ namespace LibationUiBase.GridView otherSet is not null && searchSet.Intersect(otherSet).Count() != searchSet.Count); - public static HashSet? FilterEntries(this IEnumerable entries, string searchString) + [return: NotNullIfNotNull(nameof(searchString))] + public static HashSet? FilterEntries(this IEnumerable entries, string? searchString) { - if (string.IsNullOrEmpty(searchString)) return null; + if (string.IsNullOrEmpty(searchString)) + return null; var searchResultSet = SearchEngineCommands.Search(searchString);