diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs b/Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs index b4bfe0b9..9e688a2f 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs +++ b/Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs @@ -21,7 +21,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels private int _visibleNotLiberated = 1; /// The Process Queue's viewmodel - public ProcessQueueViewModel ProcessQueueViewModel { get; } = new ProcessQueueViewModel(); + public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel(); public ProductsDisplayViewModel ProductsDisplay { get; } = new ProductsDisplayViewModel(); diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs index f3859d95..15da47cf 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs @@ -1,12 +1,9 @@ -using Avalonia.Collections; using Avalonia.Controls; using DataLayer; using System; using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; using System.Linq; -using System.Text; using System.Threading.Tasks; using ReactiveUI; using System.Reflection; @@ -14,25 +11,25 @@ using System.Collections; using Avalonia.Threading; using ApplicationServices; using AudibleUtilities; +using LibationWinForms.AvaloniaUI.Views; namespace LibationWinForms.AvaloniaUI.ViewModels { public class ProductsDisplayViewModel : ViewModelBase { - /// Number of visible rows has changed public event EventHandler VisibleCountChanged; public event EventHandler RemovableCountChanged; public event EventHandler InitialLoaded; private DataGridColumn _currentSortColumn; + private DataGrid productsDataGrid; private GridEntryBindingList2 _gridEntries; private bool _removeColumnVisivle; public GridEntryBindingList2 GridEntries { get => _gridEntries; private set => this.RaiseAndSetIfChanged(ref _gridEntries, value); } public bool RemoveColumnVisivle { get => _removeColumnVisivle; private set => this.RaiseAndSetIfChanged(ref _removeColumnVisivle, value); } - public List GetVisibleBookEntries() => GridEntries .BookEntries() @@ -49,14 +46,47 @@ namespace LibationWinForms.AvaloniaUI.ViewModels { using var context = DbContexts.GetContext(); var book = context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"); - _gridEntries = new GridEntryBindingList2(CreateGridEntries(new List { book })); + GridEntries = new GridEntryBindingList2(CreateGridEntries(new List { book })); return; } } - public void InitialDisplay(List dbBooks, Views.ProductsDisplay2 productsGrid) - { + #region Display Functions + /// + /// Call once on load so we can modify access a private member with reflection + /// + public void RegisterCollectionChanged(ProductsDisplay2 productsDisplay = null) + { + productsDataGrid ??= productsDisplay?.productsGrid; + + if (GridEntries is null) + return; + + //Avalonia displays items in the DataConncetion from an internal copy of + //the bound list, not the actual bound list. So we need to reflect to get + //the current display order and set each GridEntry.ListIndex correctly. + var DataConnection_PI = typeof(DataGrid).GetProperty("DataConnection", BindingFlags.NonPublic | BindingFlags.Instance); + var DataSource_PI = DataConnection_PI.PropertyType.GetProperty("DataSource", BindingFlags.Public | BindingFlags.Instance); + + GridEntries.CollectionChanged += (s, e) => + { + if (s != GridEntries) return; + + var displayListGE = ((IEnumerable)DataSource_PI.GetValue(DataConnection_PI.GetValue(productsDataGrid))).Cast(); + int index = 0; + foreach (var di in displayListGE) + { + di.ListIndex = index++; + } + }; + } + + /// + /// Only call once per lifetime + /// + public void InitialDisplay(List dbBooks) + { try { GridEntries = new GridEntryBindingList2(CreateGridEntries(dbBooks)); @@ -67,21 +97,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels InitialLoaded?.Invoke(this, EventArgs.Empty); VisibleCountChanged?.Invoke(this, bookEntryCount); - //Avalonia displays items in the DataConncetion from an internal copy of - //the bound list, not the actual bound list. So we need to reflect to get - //the current display order and set each GridEntry.ListIndex correctly. - var DataConnection_PI = typeof(DataGrid).GetProperty("DataConnection", BindingFlags.NonPublic | BindingFlags.Instance); - var DataSource_PI = DataConnection_PI.PropertyType.GetProperty("DataSource", BindingFlags.Public | BindingFlags.Instance); - - GridEntries.CollectionChanged += (s, e) => - { - var displayListGE = ((IEnumerable)DataSource_PI.GetValue(DataConnection_PI.GetValue(productsGrid.productsGrid))).Cast(); - int index = 0; - foreach (var di in displayListGE) - { - di.ListIndex = index++; - } - }; + RegisterCollectionChanged(); } catch (Exception ex) { @@ -89,9 +105,11 @@ namespace LibationWinForms.AvaloniaUI.ViewModels } } + /// + /// Call when there's been a change to the library + /// public async Task DisplayBooks(List dbBooks) { - try { //List is already displayed. Replace all items with new ones, refilter, and re-sort @@ -148,7 +166,19 @@ namespace LibationWinForms.AvaloniaUI.ViewModels return geList.OrderByDescending(e => e.DateAdded); } + public void ToggleSeriesExpanded(SeriesEntrys2 seriesEntry) + { + if (seriesEntry.Liberate.Expanded) + GridEntries.CollapseItem(seriesEntry); + else + GridEntries.ExpandItem(seriesEntry); + VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count()); + } + + #endregion + + #region Filtering public async Task Filter(string searchString) { await Dispatcher.UIThread.InvokeAsync(() => @@ -168,16 +198,9 @@ namespace LibationWinForms.AvaloniaUI.ViewModels }); } - public void ToggleSeriesExpanded(SeriesEntrys2 seriesEntry) - { - if (seriesEntry.Liberate.Expanded) - GridEntries.CollapseItem(seriesEntry); - else - GridEntries.ExpandItem(seriesEntry); - - VisibleCountChanged?.Invoke(this, GridEntries.BookEntries().Count()); - } + #endregion + #region Sorting public void Sort(DataGridColumn sortColumn) { @@ -207,6 +230,10 @@ namespace LibationWinForms.AvaloniaUI.ViewModels } } + #endregion + + #region Scan and Remove Books + public void DoneRemovingBooks() { foreach (var item in GridEntries.AllItems()) @@ -311,5 +338,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels RemovableCountChanged?.Invoke(this, removeCount); } } + + #endregion } } diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs index c19ac0ce..d021f762 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs @@ -19,7 +19,7 @@ namespace LibationWinForms.AvaloniaUI.Views Serilog.Log.Logger.Information("Begin backing up all library books"); - _viewModel.ProcessQueueViewModel.AddDownloadDecrypt( + _viewModel.ProcessQueue.AddDownloadDecrypt( ApplicationServices.DbContexts .GetLibrary_Flat_NoTracking() .UnLiberated() @@ -34,7 +34,7 @@ namespace LibationWinForms.AvaloniaUI.Views public async void beginPdfBackupsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) { SetQueueCollapseState(false); - await Task.Run(() => _viewModel.ProcessQueueViewModel.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + await Task.Run(() => _viewModel.ProcessQueue.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() .Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated))); } @@ -51,7 +51,7 @@ namespace LibationWinForms.AvaloniaUI.Views if (result == DialogResult.Yes) { SetQueueCollapseState(false); - await Task.Run(() => _viewModel.ProcessQueueViewModel.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() + await Task.Run(() => _viewModel.ProcessQueue.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking() .Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product))); } //Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing. diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs index 05d80140..c334dbff 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs @@ -23,13 +23,13 @@ namespace LibationWinForms.AvaloniaUI.Views { Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", libraryBook); SetQueueCollapseState(false); - _viewModel.ProcessQueueViewModel.AddDownloadDecrypt(libraryBook); + _viewModel.ProcessQueue.AddDownloadDecrypt(libraryBook); } else if (libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated) { Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook); SetQueueCollapseState(false); - _viewModel.ProcessQueueViewModel.AddDownloadPdf(libraryBook); + _viewModel.ProcessQueue.AddDownloadPdf(libraryBook); } else if (libraryBook.Book.Audio_Exists()) { diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs index 7241b0de..cafcbc39 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs @@ -27,7 +27,7 @@ namespace LibationWinForms.AvaloniaUI.Views Serilog.Log.Logger.Information("Begin backing up visible library books"); - _viewModel.ProcessQueueViewModel.AddDownloadDecrypt( + _viewModel.ProcessQueue.AddDownloadDecrypt( _viewModel .ProductsDisplay .GetVisibleBookEntries() diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml index d42d0342..0693b3b7 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml +++ b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml @@ -167,14 +167,14 @@ - + + LiberateClicked="ProductsDisplay_LiberateClicked"/> diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs index 58188776..ec7d15e4 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs @@ -20,13 +20,14 @@ namespace LibationWinForms.AvaloniaUI.Views public MainWindow() { + this.DataContext = _viewModel = new MainWindowViewModel(); + InitializeComponent(); #if DEBUG this.AttachDevTools(); #endif this.FindAllControls(); - this.DataContext = _viewModel = new MainWindowViewModel(); // eg: if one of these init'd productsGrid, then another can't reliably subscribe to it Configure_BackupCounts(); @@ -52,16 +53,19 @@ namespace LibationWinForms.AvaloniaUI.Views this.LibraryLoaded += MainWindow_LibraryLoaded; LibraryCommands.LibrarySizeChanged += async (_, _) => await _viewModel.ProductsDisplay.DisplayBooks(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true)); - this.Closing += (_,_) => this.SaveSizeAndLocation(Configuration.Instance); + Closing += (_,_) => this.SaveSizeAndLocation(Configuration.Instance); } } + public void ProductsDisplay_Initialized1(object sender, EventArgs e) + { + if (sender is ProductsDisplay2 products) + _viewModel.ProductsDisplay.RegisterCollectionChanged(products); + } + private void MainWindow_LibraryLoaded(object sender, List dbBooks) { - if (Design.IsDesignMode) - return; - - _viewModel.ProductsDisplay.InitialDisplay(dbBooks, productsDisplay); + _viewModel.ProductsDisplay.InitialDisplay(dbBooks); } private void InitializeComponent() @@ -75,7 +79,6 @@ namespace LibationWinForms.AvaloniaUI.Views private void FindAllControls() { quickFiltersToolStripMenuItem = this.FindControl(nameof(quickFiltersToolStripMenuItem)); - productsDisplay = this.FindControl(nameof(productsDisplay)); } protected override void OnDataContextChanged(EventArgs e) diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay2.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay2.axaml.cs index f570d835..223b505c 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay2.axaml.cs +++ b/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay2.axaml.cs @@ -34,7 +34,7 @@ namespace LibationWinForms.AvaloniaUI.Views _viewModel.Sort(e.Column); } - private void RemoveColumn_PropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e) + private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) { if (sender is DataGridColumn col && e.Property.Name == nameof(DataGridColumn.IsVisible)) { @@ -86,7 +86,7 @@ namespace LibationWinForms.AvaloniaUI.Views { Header = ((string)column.Header).Replace((char)0xa, ' '), Tag = column, - Margin = new Avalonia.Thickness(6, 0), + Margin = new Thickness(6, 0), Icon = new CheckBox { Width = 50, @@ -151,7 +151,7 @@ namespace LibationWinForms.AvaloniaUI.Views config.GridColumnsVisibilities = dictionary; } - private void ProductsGrid_ColumnDisplayIndexChanged(object sender, Avalonia.Controls.DataGridColumnEventArgs e) + private void ProductsGrid_ColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e) { var config = Configuration.Instance; diff --git a/Source/LibationWinForms/LibationWinForms.csproj b/Source/LibationWinForms/LibationWinForms.csproj index 659fcd6f..4db4c4cd 100644 --- a/Source/LibationWinForms/LibationWinForms.csproj +++ b/Source/LibationWinForms/LibationWinForms.csproj @@ -109,18 +109,6 @@ - - ProcessQueueControl2.axaml - - - ProcessQueueControl2.axaml - - - ProcessQueueControl2.axaml.cs - - - ProcessBookControl2.axaml - True True