Incremental prgress.
This commit is contained in:
parent
6182b2bcee
commit
eb49dcfc54
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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; }
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
99
Source/LibationWinForms/AvaloniaUI/ViewModels/RowComparer.cs
Normal file
99
Source/LibationWinForms/AvaloniaUI/ViewModels/RowComparer.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user