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.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace LibationWinForms.AvaloniaUI.ViewModels
|
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
|
/// sorted by series index, ascending. Stable sorting is achieved by comparing the GridEntry.ListIndex
|
||||||
/// properties when 2 items compare equal.
|
/// properties when 2 items compare equal.
|
||||||
/// </summary>
|
/// </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 HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
private static readonly PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", 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);
|
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (SortDirection is ListSortDirection.Ascending ? 1 : -1);
|
||||||
|
|
||||||
//a and b are children of different series.
|
//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
|
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection
|
||||||
@ -102,5 +102,10 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
|
|||||||
else
|
else
|
||||||
return compareResult;
|
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)
|
public SeriesEntrys2(LibraryBook parent, IEnumerable<LibraryBook> children)
|
||||||
{
|
{
|
||||||
Liberate = new LiberateButtonStatus2(IsSeries);
|
Liberate = new LiberateButtonStatus2(IsSeries) { Expanded = true };
|
||||||
SeriesIndex = -1;
|
SeriesIndex = -1;
|
||||||
LibraryBook = parent;
|
LibraryBook = parent;
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,10 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
{
|
{
|
||||||
private void Configure_RemoveBooks()
|
private void Configure_RemoveBooks()
|
||||||
{
|
{
|
||||||
removeBooksBtn.IsVisible = false;
|
if (Avalonia.Controls.Design.IsDesignMode)
|
||||||
doneRemovingBtn.IsVisible = false;
|
return;
|
||||||
|
|
||||||
|
_viewModel.RemoveButtonsVisible = false;
|
||||||
removeLibraryBooksToolStripMenuItem.Click += removeLibraryBooksToolStripMenuItem_Click;
|
removeLibraryBooksToolStripMenuItem.Click += removeLibraryBooksToolStripMenuItem_Click;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,8 +24,7 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
|
|
||||||
public async void doneRemovingBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void doneRemovingBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
removeBooksBtn.IsVisible = false;
|
_viewModel.RemoveButtonsVisible = false;
|
||||||
doneRemovingBtn.IsVisible = false;
|
|
||||||
|
|
||||||
productsDisplay.CloseRemoveBooksColumn();
|
productsDisplay.CloseRemoveBooksColumn();
|
||||||
|
|
||||||
@ -80,15 +81,14 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
filterSearchTb.IsVisible = false;
|
filterSearchTb.IsVisible = false;
|
||||||
productsDisplay.Filter(null);
|
productsDisplay.Filter(null);
|
||||||
|
|
||||||
removeBooksBtn.IsVisible = true;
|
_viewModel.RemoveButtonsVisible = true;
|
||||||
doneRemovingBtn.IsVisible = true;
|
|
||||||
|
|
||||||
await productsDisplay.ScanAndRemoveBooksAsync(accounts);
|
await productsDisplay.ScanAndRemoveBooksAsync(accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void productsDisplay_RemovableCountChanged(object sender, int removeCount)
|
public void productsDisplay_RemovableCountChanged(object sender, int removeCount)
|
||||||
{
|
{
|
||||||
removeBooksBtn.Content = removeCount switch
|
_viewModel.RemoveBooksButtonText = removeCount switch
|
||||||
{
|
{
|
||||||
1 => "Remove 1 Book from Libation",
|
1 => "Remove 1 Book from Libation",
|
||||||
_ => $"Remove {removeCount} Books from Libation"
|
_ => $"Remove {removeCount} Books from Libation"
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls"
|
xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="1850" d:DesignHeight="700"
|
mc:Ignorable="d" d:DesignWidth="1850" d:DesignHeight="700"
|
||||||
x:Class="LibationWinForms.AvaloniaUI.Views.MainWindow"
|
x:Class="LibationWinForms.AvaloniaUI.Views.MainWindow"
|
||||||
Title="MainWindow"
|
Title="Libation"
|
||||||
Name="Form1"
|
Name="Form1"
|
||||||
Icon="/AvaloniaUI/Assets/glass-with-glow_16.png">
|
Icon="/AvaloniaUI/Assets/glass-with-glow_16.png">
|
||||||
|
|
||||||
@ -128,8 +128,8 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||||
<Button Name="removeBooksBtn" Click="removeBooksBtn_Click" Height="30" Width="220" Content="Remove # Books from Libation"/>
|
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="removeBooksBtn_Click" Height="30" Width="220" Content="{Binding RemoveBooksButtonText}"/>
|
||||||
<Button Name="doneRemovingBtn" Click="doneRemovingBtn_Click" Height="30" Width="160" Margin="10,0,0,0" Content="Done Removing Books"/>
|
<Button IsVisible="{Binding RemoveButtonsVisible}" Click="doneRemovingBtn_Click" Width="160" Margin="10,0,0,0" Content="Done Removing Books"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBox Grid.Column="1" Name="filterSearchTb" KeyDown="filterSearchTb_KeyPress" />
|
<TextBox Grid.Column="1" Name="filterSearchTb" KeyDown="filterSearchTb_KeyPress" />
|
||||||
|
|||||||
@ -17,6 +17,7 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
{
|
{
|
||||||
public event EventHandler Load;
|
public event EventHandler Load;
|
||||||
public event EventHandler<List<LibraryBook>> LibraryLoaded;
|
public event EventHandler<List<LibraryBook>> LibraryLoaded;
|
||||||
|
private MainWindowViewModel _viewModel;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
@ -26,6 +27,8 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
#endif
|
#endif
|
||||||
this.FindAllControls();
|
this.FindAllControls();
|
||||||
|
|
||||||
|
this.DataContext = _viewModel = new MainWindowViewModel();
|
||||||
|
|
||||||
// eg: if one of these init'd productsGrid, then another can't reliably subscribe to it
|
// eg: if one of these init'd productsGrid, then another can't reliably subscribe to it
|
||||||
Configure_BackupCounts();
|
Configure_BackupCounts();
|
||||||
Configure_ScanAuto();
|
Configure_ScanAuto();
|
||||||
@ -43,8 +46,8 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
Configure_NonUI();
|
Configure_NonUI();
|
||||||
|
|
||||||
{
|
{
|
||||||
this.LibraryLoaded += (_, dbBooks) => productsDisplay.Display(dbBooks);
|
this.LibraryLoaded += async (_, dbBooks) => await productsDisplay.Display(dbBooks);
|
||||||
LibraryCommands.LibrarySizeChanged += (_, _) => productsDisplay.Display(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
LibraryCommands.LibrarySizeChanged += async (_, _) => await productsDisplay.Display(DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
|
||||||
this.Closing += (_,_) => this.SaveSizeAndLocation(Configuration.Instance);
|
this.Closing += (_,_) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,9 +110,6 @@ namespace LibationWinForms.AvaloniaUI.Views
|
|||||||
filterBtn = this.FindControl<Button>(nameof(filterBtn));
|
filterBtn = this.FindControl<Button>(nameof(filterBtn));
|
||||||
toggleQueueHideBtn = this.FindControl<Button>(nameof(toggleQueueHideBtn));
|
toggleQueueHideBtn = this.FindControl<Button>(nameof(toggleQueueHideBtn));
|
||||||
|
|
||||||
removeBooksBtn = this.FindControl<Button>(nameof(removeBooksBtn));
|
|
||||||
doneRemovingBtn = this.FindControl<Button>(nameof(doneRemovingBtn));
|
|
||||||
|
|
||||||
splitContainer1 = this.FindControl<SplitView>(nameof(splitContainer1));
|
splitContainer1 = this.FindControl<SplitView>(nameof(splitContainer1));
|
||||||
productsDisplay = this.FindControl<ProductsDisplay2>(nameof(productsDisplay));
|
productsDisplay = this.FindControl<ProductsDisplay2>(nameof(productsDisplay));
|
||||||
processBookQueue1 = this.FindControl<ProcessQueueControl2>(nameof(processBookQueue1));
|
processBookQueue1 = this.FindControl<ProcessQueueControl2>(nameof(processBookQueue1));
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using LibationWinForms.AvaloniaUI.ViewModels;
|
using LibationWinForms.AvaloniaUI.ViewModels;
|
||||||
using System;
|
using System;
|
||||||
@ -6,6 +7,7 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
||||||
{
|
{
|
||||||
@ -13,7 +15,7 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
|||||||
{
|
{
|
||||||
private void Configure_Display() { }
|
private void Configure_Display() { }
|
||||||
|
|
||||||
public void Display(List<LibraryBook> dbBooks)
|
public async Task Display(List<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -50,7 +52,20 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
|||||||
//List is already displayed. Replace all items with new ones, refilter, and re-sort
|
//List is already displayed. Replace all items with new ones, refilter, and re-sort
|
||||||
string existingFilter = _viewModel?.GridEntries?.Filter;
|
string existingFilter = _viewModel?.GridEntries?.Filter;
|
||||||
var newEntries = ProductsDisplayViewModel.CreateGridEntries(dbBooks);
|
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;
|
bindingList.Filter = existingFilter;
|
||||||
ReSort();
|
ReSort();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,8 +30,14 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
|||||||
removeGVColumn.IsVisible = value;
|
removeGVColumn.IsVisible = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseRemoveBooksColumn()
|
public void CloseRemoveBooksColumn()
|
||||||
=> RemoveColumnVisible = false;
|
{
|
||||||
|
RemoveColumnVisible = false;
|
||||||
|
|
||||||
|
foreach (var item in bindingList.AllItems())
|
||||||
|
item.PropertyChanged -= Item_PropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RemoveCheckedBooksAsync()
|
public async Task RemoveCheckedBooksAsync()
|
||||||
{
|
{
|
||||||
@ -49,11 +55,34 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
|||||||
if (result != System.Windows.Forms.DialogResult.Yes)
|
if (result != System.Windows.Forms.DialogResult.Yes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RemoveBooks(selectedBooks);
|
foreach (var book in selectedBooks)
|
||||||
|
book.PropertyChanged -= Item_PropertyChanged;
|
||||||
|
|
||||||
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
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);
|
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)
|
public async Task ScanAndRemoveBooksAsync(params Account[] accounts)
|
||||||
@ -83,6 +112,9 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
|||||||
r.Remove = true;
|
r.Remove = true;
|
||||||
|
|
||||||
RemovableCountChanged?.Invoke(this, GetAllBookEntries().Count(lbe => lbe.Remove is true));
|
RemovableCountChanged?.Invoke(this, GetAllBookEntries().Count(lbe => lbe.Remove is true));
|
||||||
|
|
||||||
|
foreach (var item in bindingList.AllItems())
|
||||||
|
item.PropertyChanged += Item_PropertyChanged;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
if (e.PropertyName == nameof(GridEntry2.Remove) && sender is LibraryBookEntry2 lbEntry)
|
||||||
foreach (var removed in removedBooks.Where(b => b.Parent is not null))
|
|
||||||
{
|
{
|
||||||
removed.Parent.Children.Remove(removed);
|
int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true);
|
||||||
|
RemovableCountChanged?.Invoke(this, removeCount);
|
||||||
//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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,10 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
|
|||||||
{
|
{
|
||||||
if (CurrentSortColumn is null)
|
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();
|
bindingList.ResetCollection();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user