Incremental prgress.

This commit is contained in:
Michael Bucari-Tovo 2022-07-13 01:14:05 -06:00
parent 6182b2bcee
commit eb49dcfc54
6 changed files with 194 additions and 115 deletions

View File

@ -1,15 +1,79 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Utils;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using LibationWinForms.AvaloniaUI.ViewModels; using LibationWinForms.AvaloniaUI.ViewModels;
using System; using System;
using System.Linq; using System.Reflection;
namespace LibationWinForms.AvaloniaUI.Controls namespace LibationWinForms.AvaloniaUI.Controls
{ {
/// <summary> The purpose of this extension WAS to immediately commit any check state changes to the viewmodel, but for the life of me I cannot get it to work! </summary> /// <summary> The purpose of this extension it to immediately commit any check
/// state changes to the viewmodel. There must be a better way to do this, but
/// I sure as shit can't find it. </summary>
public partial class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn public partial class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
{ {
Func<DataGrid> _owningGrid_get;
Func<DataGridEditAction, bool, bool, bool, bool> _endCellEdit;
Func<Action, bool> _waitForLostFocus;
public DataGrid OwningGrid
{
get
{
if (_owningGrid_get == null)
{
var pi = typeof(DataGridColumn).GetProperty(nameof(OwningGrid), BindingFlags.NonPublic | BindingFlags.Instance);
var mi = pi.GetGetMethod(true);
_owningGrid_get = mi.CreateDelegate<Func<DataGrid>>(this);
}
return _owningGrid_get();
}
}
public Func<Action, bool> WaitForLostFocus
{
get
{
if (_endCellEdit == null)
{
var mi = typeof(DataGrid).GetMethod(nameof(WaitForLostFocus), BindingFlags.NonPublic | BindingFlags.Instance);
_waitForLostFocus = mi.CreateDelegate<Func<Action, bool>>(OwningGrid);
}
return _waitForLostFocus;
}
}
public Func<DataGridEditAction, bool, bool, bool, bool> EndCellEdit
{
get
{
if (_endCellEdit == null)
{
var mi = typeof(DataGrid).GetMethod(nameof(EndCellEdit), BindingFlags.NonPublic | BindingFlags.Instance);
_endCellEdit = mi.CreateDelegate<Func<DataGridEditAction, bool, bool, bool, bool>>(OwningGrid);
}
return _endCellEdit;
}
}
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{
var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox;
ele.Checked += EditingElement_Checked;
ele.Unchecked += EditingElement_Checked;
ele.Indeterminate += EditingElement_Checked;
return ele;
}
private void EditingElement_Checked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox cbox && cbox.DataContext is GridEntry2 gentry)
{
var check = cbox.IsChecked;
WaitForLostFocus(() =>
{
EndCellEdit(DataGridEditAction.Cancel, true, true, false);
gentry.Remove = check;
});
}
}
} }
} }

View File

@ -57,7 +57,6 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
public string ProductRating { get => _productRating; protected set { this.RaiseAndSetIfChanged(ref _productRating, value); } } public string ProductRating { get => _productRating; protected set { this.RaiseAndSetIfChanged(ref _productRating, value); } }
public string MyRating { get => _myRating; protected set { this.RaiseAndSetIfChanged(ref _myRating, value); } } public string MyRating { get => _myRating; protected set { this.RaiseAndSetIfChanged(ref _myRating, value); } }
protected bool? _remove = false; protected bool? _remove = false;
public abstract bool? Remove { get; set; } public abstract bool? Remove { get; set; }
public abstract LiberateButtonStatus2 Liberate { get; } public abstract LiberateButtonStatus2 Liberate { get; }

View File

@ -128,13 +128,11 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
public void ExpandItem(SeriesEntrys2 sEntry) public void ExpandItem(SeriesEntrys2 sEntry)
{ {
var sindex = Items.IndexOf(sEntry);
foreach (var episode in FilterRemoved.BookEntries().Where(b => b.Parent == sEntry).ToList()) foreach (var episode in FilterRemoved.BookEntries().Where(b => b.Parent == sEntry).ToList())
{ {
if (SearchResults is null || SearchResults.Docs.Any(d => d.ProductId == episode.AudibleProductId)) if (SearchResults is null || SearchResults.Docs.Any(d => d.ProductId == episode.AudibleProductId))
{ {
InsertItem(++sindex, episode); Add(episode);
} }
} }
sEntry.Liberate.Expanded = true; sEntry.Liberate.Expanded = true;

View File

@ -0,0 +1,99 @@
using Avalonia.Controls;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.ViewModels
{
//TODO keep episodes beneath their parents when other entries compare equal (because as it stands, children always compare > parents.
/// <summary>
/// This compare class ensures that all top-level grid entries (standalone books or series parents)
/// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain
/// sorted by series index, ascending.
/// </summary>
internal class RowComparer : IComparer
{
private static readonly System.Reflection.PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
private static readonly System.Reflection.PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
public DataGridColumn Column { get; init; }
public string PropertyName { get; private set; }
public ListSortDirection? SortDirection { get; set; }
public RowComparer(DataGridColumn column)
{
Column = column;
PropertyName = Column.SortMemberPath;
}
public int Compare(object x, object y)
{
if (x is null && y is not null) return -1;
if (x is not null && y is null) return 1;
if (x is null && y is null) return 0;
var geA = (GridEntry2)x;
var geB = (GridEntry2)y;
SortDirection ??= GetSortOrder();
SeriesEntrys2 parentA = null;
SeriesEntrys2 parentB = null;
if (geA is LibraryBookEntry2 lbA && lbA.Parent is SeriesEntrys2 seA)
parentA = seA;
if (geB is LibraryBookEntry2 lbB && lbB.Parent is SeriesEntrys2 seB)
parentB = seB;
//both a and b are standalone
if (parentA is null && parentB is null)
return Compare(geA, geB);
//a is a standalone, b is a child
if (parentA is null && parentB is not null)
{
// b is a child of a, parent is always first
if (parentB == geA)
return SortDirection is ListSortDirection.Ascending ? -1 : 1;
else
return Compare(geA, parentB);
}
//a is a child, b is a standalone
if (parentA is not null && parentB is null)
{
// a is a child of b, parent is always first
if (parentA == geB)
return SortDirection is ListSortDirection.Ascending ? 1 : -1;
else
return Compare(parentA, geB);
}
//both are children of the same series, always present in order of series index, ascending
if (parentA == parentB)
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (SortDirection is ListSortDirection.Ascending ? 1 : -1);
//a and b are children of different series.
return Compare(parentA, parentB);
}
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection
private ListSortDirection? GetSortOrder()
=> CurrentSortingStatePi.GetValue(HeaderCellPi.GetValue(Column)) as ListSortDirection?;
private int Compare(GridEntry2 x, GridEntry2 y)
{
var val1 = x.GetMemberValue(PropertyName);
var val2 = y.GetMemberValue(PropertyName);
return x.GetMemberComparer(val1.GetType()).Compare(val1, val2);
}
}
}

View File

@ -11,7 +11,7 @@
<DataGrid Name="productsGrid" AutoGenerateColumns="False" Items="{Binding GridEntries}"> <DataGrid Name="productsGrid" AutoGenerateColumns="False" Items="{Binding GridEntries}">
<DataGrid.Columns> <DataGrid.Columns>
<controls:DataGridCheckBoxColumnExt IsVisible="False" Header="Remove" IsThreeState="True" IsReadOnly="False" CanUserSort="True" Binding="{Binding Remove, Mode=TwoWay}" Width="70" SortMemberPath="Remove"/> <controls:DataGridCheckBoxColumnExt IsVisible="True" Header="Remove" IsThreeState="True" IsReadOnly="False" CanUserSort="True" Binding="{Binding Remove, Mode=TwoWay}" Width="70" SortMemberPath="Remove"/>
<DataGridTemplateColumn CanUserSort="True" Width="75" Header="Liberate" SortMemberPath="Liberate"> <DataGridTemplateColumn CanUserSort="True" Width="75" Header="Liberate" SortMemberPath="Liberate">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>

View File

@ -124,7 +124,27 @@ namespace LibationWinForms.AvaloniaUI.Views
}; };
} }
#endregion #endregion
#region Filter
public void Filter(string searchString)
{
int visibleCount = bindingList.Count;
if (string.IsNullOrEmpty(searchString))
bindingList.RemoveFilter();
else
bindingList.Filter = searchString;
if (visibleCount != bindingList.Count)
VisibleCountChanged?.Invoke(this, bindingList.BookEntries().Count());
//Re-sort after filtering
ReSort();
}
#endregion
#region Sorting #region Sorting
@ -150,7 +170,10 @@ namespace LibationWinForms.AvaloniaUI.Views
private void ReSort() private void ReSort()
{ {
if (CurrentSortColumn is null) if (CurrentSortColumn is null)
{
bindingList.InternalList.Sort((i1, i2) => i2.DateAdded.CompareTo(i1.DateAdded)); bindingList.InternalList.Sort((i1, i2) => i2.DateAdded.CompareTo(i1.DateAdded));
bindingList.ResetCollection();
}
else else
CurrentSortColumn.Sort(((RowComparer)CurrentSortColumn.CustomSortComparer).SortDirection ?? ListSortDirection.Ascending); CurrentSortColumn.Sort(((RowComparer)CurrentSortColumn.CustomSortComparer).SortDirection ?? ListSortDirection.Ascending);
} }
@ -168,88 +191,6 @@ namespace LibationWinForms.AvaloniaUI.Views
CurrentSortColumn = e.Column; CurrentSortColumn = e.Column;
} }
private class RowComparer : IComparer
{
private static readonly System.Reflection.PropertyInfo HeaderCellPi = typeof(DataGridColumn).GetProperty("HeaderCell", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
private static readonly System.Reflection.PropertyInfo CurrentSortingStatePi = typeof(DataGridColumnHeader).GetProperty("CurrentSortingState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
public DataGridColumn Column { get; init; }
public string PropertyName { get; init; }
public ListSortDirection? SortDirection { get; set; }
public RowComparer(DataGridColumn column)
{
Column = column;
PropertyName = column.SortMemberPath;
}
/// <summary>
/// This compare method ensures that all top-level grid entries (standalone books or series parents)
/// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain
/// sorted by series index, ascending.
/// </summary>
public int Compare(object x, object y)
{
if (x is null) return -1;
if (y is null) return 1;
if (x is null && y is null) return 0;
var geA = (GridEntry2)x;
var geB = (GridEntry2)y;
SortDirection ??= GetSortOrder();
SeriesEntrys2 parentA = null;
SeriesEntrys2 parentB = null;
if (geA is LibraryBookEntry2 lbA && lbA.Parent is SeriesEntrys2 seA)
parentA = seA;
if (geB is LibraryBookEntry2 lbB && lbB.Parent is SeriesEntrys2 seB)
parentB = seB;
//both a and b are standalone
if (parentA is null && parentB is null)
return Compare(geA, geB);
//a is a standalone, b is a child
if (parentA is null && parentB is not null)
{
// b is a child of a, parent is always first
if (parentB == geA)
return SortDirection is ListSortDirection.Ascending ? -1 : 1;
else
return Compare(geA, parentB);
}
//a is a child, b is a standalone
if (parentA is not null && parentB is null)
{
// a is a child of b, parent is always first
if (parentA == geB)
return SortDirection is ListSortDirection.Ascending ? 1 : -1;
else
return Compare(parentA, geB);
}
//both are children of the same series, always present in order of series index, ascending
if (parentA == parentB)
return geA.SeriesIndex.CompareTo(geB.SeriesIndex) * (SortDirection is ListSortDirection.Ascending ? 1 : -1);
//a and b are children of different series.
return Compare(parentA, parentB);
}
private ListSortDirection? GetSortOrder()
=> CurrentSortingStatePi.GetValue(HeaderCellPi.GetValue(Column)) as ListSortDirection?;
private int Compare(GridEntry2 x, GridEntry2 y)
{
var val1 = x.GetMemberValue(PropertyName);
var val2 = y.GetMemberValue(PropertyName);
return x.GetMemberComparer(val1.GetType()).Compare(val1, val2);
}
}
#endregion #endregion
@ -571,29 +512,7 @@ namespace LibationWinForms.AvaloniaUI.Views
existingEpisodeEntry.UpdateLibraryBook(episodeBook); existingEpisodeEntry.UpdateLibraryBook(episodeBook);
} }
#endregion #endregion
#region Filter
public void Filter(string searchString)
{
int visibleCount = bindingList.Count;
if (string.IsNullOrEmpty(searchString))
bindingList.RemoveFilter();
else
bindingList.Filter = searchString;
if (visibleCount != bindingList.Count)
VisibleCountChanged?.Invoke(this, bindingList.BookEntries().Count());
//Re-sort after filtering
bindingList.ResetCollection();
ReSort();
}
#endregion
#region Column Customizations #region Column Customizations