Further sorting and remove books refinements
This commit is contained in:
parent
efd6156fa8
commit
7b7e1d8574
@ -3,10 +3,15 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationWinForms.AvaloniaUI.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private string _removeBooksButtonText = "Remove # Books from Libation";
|
||||
private bool _removeButtonsVisible = true;
|
||||
public string RemoveBooksButtonText { get => _removeBooksButtonText; set => this.RaiseAndSetIfChanged(ref _removeBooksButtonText, value); }
|
||||
public bool RemoveButtonsVisible { get => _removeButtonsVisible; set => this.RaiseAndSetIfChanged(ref _removeButtonsVisible, value); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
|
||||
/// sorted by series index, ascending. Stable sorting is achieved by comparing the GridEntry.ListIndex
|
||||
/// properties when 2 items compare equal.
|
||||
/// </summary>
|
||||
internal class RowComparer : IComparer
|
||||
internal class RowComparer : IComparer, IComparer<GridEntry2>
|
||||
{
|
||||
private static readonly PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
private static readonly PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
@ -81,7 +81,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
|
||||
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (SortDirection is ListSortDirection.Ascending ? 1 : -1);
|
||||
|
||||
//a and b are children of different series.
|
||||
return Compare(parentA, parentB);
|
||||
return InternalCompare(parentA, parentB);
|
||||
}
|
||||
|
||||
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection
|
||||
@ -102,5 +102,10 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
|
||||
else
|
||||
return compareResult;
|
||||
}
|
||||
|
||||
public int Compare(GridEntry2 x, GridEntry2 y)
|
||||
{
|
||||
return Compare((object)x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
|
||||
|
||||
public SeriesEntrys2(LibraryBook parent, IEnumerable<LibraryBook> children)
|
||||
{
|
||||
Liberate = new LiberateButtonStatus2(IsSeries);
|
||||
Liberate = new LiberateButtonStatus2(IsSeries) { Expanded = true };
|
||||
SeriesIndex = -1;
|
||||
LibraryBook = parent;
|
||||
|
||||
|
||||
@ -10,8 +10,10 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
{
|
||||
private void Configure_RemoveBooks()
|
||||
{
|
||||
removeBooksBtn.IsVisible = false;
|
||||
doneRemovingBtn.IsVisible = false;
|
||||
if (Avalonia.Controls.Design.IsDesignMode)
|
||||
return;
|
||||
|
||||
_viewModel.RemoveButtonsVisible = false;
|
||||
removeLibraryBooksToolStripMenuItem.Click += removeLibraryBooksToolStripMenuItem_Click;
|
||||
}
|
||||
|
||||
@ -22,8 +24,7 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
|
||||
public async void doneRemovingBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
removeBooksBtn.IsVisible = false;
|
||||
doneRemovingBtn.IsVisible = false;
|
||||
_viewModel.RemoveButtonsVisible = false;
|
||||
|
||||
productsDisplay.CloseRemoveBooksColumn();
|
||||
|
||||
@ -80,15 +81,14 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
filterSearchTb.IsVisible = false;
|
||||
productsDisplay.Filter(null);
|
||||
|
||||
removeBooksBtn.IsVisible = true;
|
||||
doneRemovingBtn.IsVisible = true;
|
||||
_viewModel.RemoveButtonsVisible = true;
|
||||
|
||||
await productsDisplay.ScanAndRemoveBooksAsync(accounts);
|
||||
}
|
||||
|
||||
public void productsDisplay_RemovableCountChanged(object sender, int removeCount)
|
||||
{
|
||||
removeBooksBtn.Content = removeCount switch
|
||||
_viewModel.RemoveBooksButtonText = removeCount switch
|
||||
{
|
||||
1 => "Remove 1 Book from Libation",
|
||||
_ => $"Remove {removeCount} Books from Libation"
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="1850" d:DesignHeight="700"
|
||||
x:Class="LibationWinForms.AvaloniaUI.Views.MainWindow"
|
||||
Title="MainWindow"
|
||||
Title="Libation"
|
||||
Name="Form1"
|
||||
Icon="/AvaloniaUI/Assets/glass-with-glow_16.png">
|
||||
|
||||
@ -128,8 +128,8 @@
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button Name="removeBooksBtn" Click="removeBooksBtn_Click" Height="30" Width="220" Content="Remove # Books from Libation"/>
|
||||
<Button Name="doneRemovingBtn" Click="doneRemovingBtn_Click" Height="30" Width="160" Margin="10,0,0,0" Content="Done Removing Books"/>
|
||||
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="removeBooksBtn_Click" Height="30" Width="220" Content="{Binding RemoveBooksButtonText}"/>
|
||||
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="doneRemovingBtn_Click" Width="160" Margin="10,0,0,0" Content="Done Removing Books"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1" Name="filterSearchTb" KeyDown="filterSearchTb_KeyPress" />
|
||||
|
||||
@ -17,6 +17,7 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
{
|
||||
public event EventHandler Load;
|
||||
public event EventHandler<List<LibraryBook>> LibraryLoaded;
|
||||
private MainWindowViewModel _viewModel;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@ -26,6 +27,8 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
#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();
|
||||
Configure_ScanAuto();
|
||||
@ -43,8 +46,8 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
Configure_NonUI();
|
||||
|
||||
{
|
||||
this.LibraryLoaded += (_, dbBooks) => productsDisplay.Display(dbBooks);
|
||||
LibraryCommands.LibrarySizeChanged += (_, _) => productsDisplay.Display(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
this.LibraryLoaded += async (_, dbBooks) => await productsDisplay.Display(dbBooks);
|
||||
LibraryCommands.LibrarySizeChanged += async (_, _) => await productsDisplay.Display(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||
this.Closing += (_,_) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
}
|
||||
@ -107,9 +110,6 @@ namespace LibationWinForms.AvaloniaUI.Views
|
||||
filterBtn = this.FindControl<Button>(nameof(filterBtn));
|
||||
toggleQueueHideBtn = this.FindControl<Button>(nameof(toggleQueueHideBtn));
|
||||
|
||||
removeBooksBtn = this.FindControl<Button>(nameof(removeBooksBtn));
|
||||
doneRemovingBtn = this.FindControl<Button>(nameof(doneRemovingBtn));
|
||||
|
||||
splitContainer1 = this.FindControl<SplitView>(nameof(splitContainer1));
|
||||
productsDisplay = this.FindControl<ProductsDisplay2>(nameof(productsDisplay));
|
||||
processBookQueue1 = this.FindControl<ProcessQueueControl2>(nameof(processBookQueue1));
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using DataLayer;
|
||||
using LibationWinForms.AvaloniaUI.ViewModels;
|
||||
using System;
|
||||
@ -6,6 +7,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
{
|
||||
@ -13,7 +15,7 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
{
|
||||
private void Configure_Display() { }
|
||||
|
||||
public void Display(List<LibraryBook> dbBooks)
|
||||
public async Task Display(List<LibraryBook> dbBooks)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -50,7 +52,20 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
//List is already displayed. Replace all items with new ones, refilter, and re-sort
|
||||
string existingFilter = _viewModel?.GridEntries?.Filter;
|
||||
var newEntries = ProductsDisplayViewModel.CreateGridEntries(dbBooks);
|
||||
bindingList.ReplaceList(newEntries);
|
||||
|
||||
var existingSeriesEntries = bindingList.InternalList.SeriesEntries().ToList();
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() => bindingList.ReplaceList(newEntries));
|
||||
|
||||
//We're replacing the list, so preserve usere's existing collapse/expand
|
||||
//state. When resetting a list, default state is open.
|
||||
foreach (var series in existingSeriesEntries)
|
||||
{
|
||||
var sEntry = bindingList.InternalList.FirstOrDefault(ge => ge.AudibleProductId == series.AudibleProductId);
|
||||
if (sEntry is SeriesEntrys2 se && !series.Liberate.Expanded)
|
||||
await Dispatcher.UIThread.InvokeAsync(() => bindingList.CollapseItem(se));
|
||||
}
|
||||
|
||||
bindingList.Filter = existingFilter;
|
||||
ReSort();
|
||||
}
|
||||
|
||||
@ -30,8 +30,14 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
removeGVColumn.IsVisible = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseRemoveBooksColumn()
|
||||
=> RemoveColumnVisible = false;
|
||||
{
|
||||
RemoveColumnVisible = false;
|
||||
|
||||
foreach (var item in bindingList.AllItems())
|
||||
item.PropertyChanged -= Item_PropertyChanged;
|
||||
}
|
||||
|
||||
public async Task RemoveCheckedBooksAsync()
|
||||
{
|
||||
@ -49,11 +55,34 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
if (result != System.Windows.Forms.DialogResult.Yes)
|
||||
return;
|
||||
|
||||
RemoveBooks(selectedBooks);
|
||||
foreach (var book in selectedBooks)
|
||||
book.PropertyChanged -= Item_PropertyChanged;
|
||||
|
||||
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||
bindingList.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.
|
||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||
|
||||
RemovableCountChanged?.Invoke(this, GetAllBookEntries().Count(lbe => lbe.Remove is true));
|
||||
foreach (var b in GetAllBookEntries())
|
||||
b.Remove = false;
|
||||
|
||||
RemovableCountChanged?.Invoke(this, 0);
|
||||
}
|
||||
|
||||
void BindingList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
|
||||
return;
|
||||
|
||||
//After ProductsDisplay2.Display() re-creates the list,
|
||||
//re-subscribe to all items' PropertyChanged events.
|
||||
|
||||
foreach (var b in GetAllBookEntries())
|
||||
b.PropertyChanged += Item_PropertyChanged;
|
||||
|
||||
bindingList.CollectionChanged -= BindingList_CollectionChanged;
|
||||
}
|
||||
|
||||
public async Task ScanAndRemoveBooksAsync(params Account[] accounts)
|
||||
@ -83,6 +112,9 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
r.Remove = true;
|
||||
|
||||
RemovableCountChanged?.Invoke(this, GetAllBookEntries().Count(lbe => lbe.Remove is true));
|
||||
|
||||
foreach (var item in bindingList.AllItems())
|
||||
item.PropertyChanged += Item_PropertyChanged;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -94,30 +126,13 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveBooks(IEnumerable<LibraryBookEntry2> removedBooks)
|
||||
private void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
//Remove books in series from their parents' Children list
|
||||
foreach (var removed in removedBooks.Where(b => b.Parent is not null))
|
||||
if (e.PropertyName == nameof(GridEntry2.Remove) && sender is LibraryBookEntry2 lbEntry)
|
||||
{
|
||||
removed.Parent.Children.Remove(removed);
|
||||
|
||||
//In Avalonia, if you fire PropertyChanged with an empty or invalid property name, nothing is updated.
|
||||
//So we must notify for specific properties that we believed changed.
|
||||
removed.Parent.RaisePropertyChanged(nameof(SeriesEntrys2.Length));
|
||||
removed.Parent.RaisePropertyChanged(nameof(SeriesEntrys2.PurchaseDate));
|
||||
}
|
||||
|
||||
//Remove series that have no children
|
||||
var removedSeries =
|
||||
bindingList
|
||||
.AllItems()
|
||||
.EmptySeries();
|
||||
|
||||
foreach (var removed in removedBooks.Cast<GridEntry2>().Concat(removedSeries))
|
||||
//no need to re-filter for removed books
|
||||
bindingList.Remove(removed);
|
||||
|
||||
VisibleCountChanged?.Invoke(this, bindingList.BookEntries().Count());
|
||||
int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true);
|
||||
RemovableCountChanged?.Invoke(this, removeCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,10 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||
{
|
||||
if (CurrentSortColumn is null)
|
||||
{
|
||||
bindingList.InternalList.Sort((i1, i2) => i2.DateAdded.CompareTo(i1.DateAdded));
|
||||
//Sort ascending and reverse. That's how the comparer is designed to work to be compatible with Avalonia.
|
||||
var defaultComparer = new RowComparer(ListSortDirection.Descending, nameof(GridEntry2.DateAdded));
|
||||
bindingList.InternalList.Sort(defaultComparer);
|
||||
bindingList.InternalList.Reverse();
|
||||
bindingList.ResetCollection();
|
||||
}
|
||||
else
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user