Refactoring.

This commit is contained in:
Michael Bucari-Tovo 2021-08-10 10:22:03 -06:00
parent e1dfefbadf
commit 95766a43c5
11 changed files with 197 additions and 242 deletions

View File

@ -0,0 +1,37 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
namespace LibationWinForms
{
public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId;
private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId;
private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
var propertyChangedArgs = new PropertyChangedEventArgs(propertyName);
if (InvokeRequired)
{
SyncContext.Post(
PostPropertyChangedCallback,
new AsyncCompletedEventArgs(null, false, propertyChangedArgs));
}
else
{
OnPropertyChanged(propertyChangedArgs);
}
}
private void PostPropertyChangedCallback(object asyncArgs)
{
var e = asyncArgs as AsyncCompletedEventArgs;
OnPropertyChanged(e.UserState as PropertyChangedEventArgs);
}
private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
}
}

View File

@ -37,7 +37,7 @@ namespace LibationWinForms
public class DataGridViewImageButtonCell : DataGridViewButtonCell public class DataGridViewImageButtonCell : DataGridViewButtonCell
{ {
protected void DrawImage(Graphics graphics, Bitmap image, Rectangle cellBounds) protected void DrawButtonImage(Graphics graphics, Image image, Rectangle cellBounds)
{ {
var w = image.Width; var w = image.Width;
var h = image.Height; var h = image.Height;

View File

@ -31,7 +31,7 @@ namespace LibationWinForms.Dialogs
{ {
this.components = new System.ComponentModel.Container(); this.components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
this.dataGridView1 = new System.Windows.Forms.DataGridView(); this._dataGridView = new System.Windows.Forms.DataGridView();
this.removeDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.removeDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn(); this.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn();
this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
@ -41,25 +41,25 @@ namespace LibationWinForms.Dialogs
this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.btnRemoveBooks = new System.Windows.Forms.Button(); this.btnRemoveBooks = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
// dataGridView1 // _dataGridView
// //
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this._dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.dataGridView1.AutoGenerateColumns = false; this._dataGridView.AutoGenerateColumns = false;
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this._dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this._dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.removeDataGridViewCheckBoxColumn, this.removeDataGridViewCheckBoxColumn,
this.coverDataGridViewImageColumn, this.coverDataGridViewImageColumn,
this.titleDataGridViewTextBoxColumn, this.titleDataGridViewTextBoxColumn,
this.authorsDataGridViewTextBoxColumn, this.authorsDataGridViewTextBoxColumn,
this.miscDataGridViewTextBoxColumn, this.miscDataGridViewTextBoxColumn,
this.purchaseDateGridViewTextBoxColumn}); this.purchaseDateGridViewTextBoxColumn});
this.dataGridView1.DataSource = this.gridEntryBindingSource; this._dataGridView.DataSource = this.gridEntryBindingSource;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
@ -67,13 +67,13 @@ namespace LibationWinForms.Dialogs
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle2; this._dataGridView.DefaultCellStyle = dataGridViewCellStyle2;
this.dataGridView1.Location = new System.Drawing.Point(0, 0); this._dataGridView.Location = new System.Drawing.Point(0, 0);
this.dataGridView1.Name = "dataGridView1"; this._dataGridView.Name = "_dataGridView";
this.dataGridView1.RowHeadersVisible = false; this._dataGridView.RowHeadersVisible = false;
this.dataGridView1.RowTemplate.Height = 82; this._dataGridView.RowTemplate.Height = 82;
this.dataGridView1.Size = new System.Drawing.Size(800, 409); this._dataGridView.Size = new System.Drawing.Size(800, 409);
this.dataGridView1.TabIndex = 0; this._dataGridView.TabIndex = 0;
// //
// removeDataGridViewCheckBoxColumn // removeDataGridViewCheckBoxColumn
// //
@ -162,11 +162,11 @@ namespace LibationWinForms.Dialogs
this.ClientSize = new System.Drawing.Size(800, 450); this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.label1); this.Controls.Add(this.label1);
this.Controls.Add(this.btnRemoveBooks); this.Controls.Add(this.btnRemoveBooks);
this.Controls.Add(this.dataGridView1); this.Controls.Add(this._dataGridView);
this.Name = "RemoveBooksDialog"; this.Name = "RemoveBooksDialog";
this.Text = "RemoveBooksDialog"; this.Text = "RemoveBooksDialog";
this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown); this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown);
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@ -175,7 +175,7 @@ namespace LibationWinForms.Dialogs
#endregion #endregion
private System.Windows.Forms.DataGridView dataGridView1; private System.Windows.Forms.DataGridView _dataGridView;
private System.Windows.Forms.BindingSource gridEntryBindingSource; private System.Windows.Forms.BindingSource gridEntryBindingSource;
private System.Windows.Forms.Button btnRemoveBooks; private System.Windows.Forms.Button btnRemoveBooks;
private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label1;

View File

@ -31,9 +31,9 @@ namespace LibationWinForms.Dialogs
InitializeComponent(); InitializeComponent();
_labelFormat = label1.Text; _labelFormat = label1.Text;
dataGridView1.CellContentClick += (s, e) => dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit); _dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; _dataGridView.CellValueChanged += DataGridView1_CellValueChanged;
dataGridView1.BindingContextChanged += (s, e) => UpdateSelection(); _dataGridView.BindingContextChanged += (s, e) => UpdateSelection();
var orderedGridEntries = _libraryBooks var orderedGridEntries = _libraryBooks
.Select(lb => new RemovableGridEntry(lb)) .Select(lb => new RemovableGridEntry(lb))
@ -43,7 +43,7 @@ namespace LibationWinForms.Dialogs
_removableGridEntries = new SortableBindingList2<RemovableGridEntry>(orderedGridEntries); _removableGridEntries = new SortableBindingList2<RemovableGridEntry>(orderedGridEntries);
gridEntryBindingSource.DataSource = _removableGridEntries; gridEntryBindingSource.DataSource = _removableGridEntries;
dataGridView1.Enabled = false; _dataGridView.Enabled = false;
} }
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
@ -79,7 +79,7 @@ namespace LibationWinForms.Dialogs
} }
finally finally
{ {
dataGridView1.Enabled = true; _dataGridView.Enabled = true;
} }
} }
@ -103,12 +103,12 @@ namespace LibationWinForms.Dialogs
MessageBoxDefaultButton.Button1); MessageBoxDefaultButton.Button1);
if (result == DialogResult.Yes) if (result == DialogResult.Yes)
{ {
using var context = DbContexts.GetContext(); using var context = DbContexts.GetContext();
var libBooks = context.GetLibrary_Flat_NoTracking(); var libBooks = context.GetLibrary_Flat_NoTracking();
var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToArray(); var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToList();
context.Library.RemoveRange(removeLibraryBooks); context.Library.RemoveRange(removeLibraryBooks);
context.SaveChanges(); context.SaveChanges();
@ -116,14 +116,14 @@ namespace LibationWinForms.Dialogs
foreach (var rEntry in selectedBooks) foreach (var rEntry in selectedBooks)
_removableGridEntries.Remove(rEntry); _removableGridEntries.Remove(rEntry);
BooksRemoved = removeLibraryBooks.Length > 0; BooksRemoved = removeLibraryBooks.Count > 0;
UpdateSelection(); UpdateSelection();
} }
} }
private void UpdateSelection() private void UpdateSelection()
{ {
dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending); _dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending);
var selectedCount = SelectedCount; var selectedCount = SelectedCount;
label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty); label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty);
btnRemoveBooks.Enabled = selectedCount > 0; btnRemoveBooks.Enabled = selectedCount > 0;
@ -153,19 +153,18 @@ namespace LibationWinForms.Dialogs
} }
} }
public override object GetMemberValue(string propertyName) public override object GetMemberValue(string memberName)
{ {
if (propertyName == nameof(Remove)) if (memberName == nameof(Remove))
return Remove; return Remove;
return base.GetMemberValue(propertyName); return base.GetMemberValue(memberName);
} }
public override IComparer GetComparer(Type propertyType) public override IComparer GetMemberComparer(Type memberType)
{ {
if (propertyType == typeof(bool)) if (memberType == typeof(bool))
return BoolComparer; return BoolComparer;
return base.GetMemberComparer(memberType);
return base.GetComparer(propertyType);
} }
} }
} }

View File

@ -1,64 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <root>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
@ -117,4 +57,7 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<metadata name="gridEntryBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root> </root>

View File

@ -11,19 +11,26 @@ namespace LibationWinForms
internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell
{ {
private static readonly Bitmap ButtonImage = Properties.Resources.edit_tags_25x25; private static readonly Image ButtonImage = Properties.Resources.edit_tags_25x25;
private static readonly Color HiddenForeColor = Color.LightGray; private static readonly Color HiddenForeColor = Color.LightGray;
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{ {
var valueString = (string)value; var tagsString = (string)value;
DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = valueString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor; var foreColor = tagsString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor;
if (valueString.Length == 0) if (DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor != foreColor)
{
DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = foreColor;
DataGridView.InvalidateRow(RowIndex);
}
if (tagsString.Length == 0)
{ {
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
DrawImage(graphics, ButtonImage, cellBounds); DrawButtonImage(graphics, ButtonImage, cellBounds);
} }
else else
{ {

View File

@ -337,6 +337,7 @@ namespace LibationWinForms
scanLibraries(scanAccountsDialog.CheckedAccounts); scanLibraries(scanAccountsDialog.CheckedAccounts);
} }
private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
@ -363,6 +364,7 @@ namespace LibationWinForms
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
} }
private void scanLibrariesRemovedBooks(params Account[] accounts) private void scanLibrariesRemovedBooks(params Account[] accounts)
{ {
using var dialog = new RemoveBooksDialog(accounts); using var dialog = new RemoveBooksDialog(accounts);

View File

@ -4,36 +4,32 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using ApplicationServices; using ApplicationServices;
using DataLayer; using DataLayer;
using Dinah.Core.Drawing; using Dinah.Core.Drawing;
namespace LibationWinForms namespace LibationWinForms
{ {
internal class GridEntry : INotifyPropertyChanged, IObjectMemberComparable internal class GridEntry : AsyncNotifyPropertyChanged, IObjectMemberComparable
{ {
#region implementation properties #region implementation properties
// hide from public fields from Data Source GUI with [Browsable(false)] // hide from public fields from Data Source GUI with [Browsable(false)]
[Browsable(false)] [Browsable(false)]
public string AudibleProductId => Book.AudibleProductId; public string AudibleProductId => Book.AudibleProductId;
[Browsable(false)] [Browsable(false)]
public LibraryBook LibraryBook { get; } public LibraryBook LibraryBook { get; }
#endregion #endregion
public event PropertyChangedEventHandler PropertyChanged;
private Book Book => LibraryBook.Book; private Book Book => LibraryBook.Book;
private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current;
private Image _cover; private Image _cover;
public GridEntry(LibraryBook libraryBook) public GridEntry(LibraryBook libraryBook)
{ {
LibraryBook = libraryBook; LibraryBook = libraryBook;
_compareValues = CreatePropertyValueDictionary(); _memberValues = CreateMemberValueDictionary();
//Get cover art. If it's default, subscribe to PictureCached //Get cover art. If it's default, subscribe to PictureCached
var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80);
@ -42,7 +38,7 @@ namespace LibationWinForms
if (isDefault) if (isDefault)
FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached;
//Mutable property. Set the field so PropertyChanged doesn't fire. //Mutable property. Set the field so PropertyChanged isn't fired.
_cover = ImageReader.ToImage(picture); _cover = ImageReader.ToImage(picture);
//Immutable properties //Immutable properties
@ -50,9 +46,9 @@ namespace LibationWinForms
Title = Book.Title; Title = Book.Title;
Series = Book.SeriesNames; Series = Book.SeriesNames;
Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min"; Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min";
MyRating = GetStarString(Book.UserDefinedItem.Rating); MyRating = ValueOrDefault(Book.UserDefinedItem.Rating?.ToStarString(), "");
PurchaseDate = libraryBook.DateAdded.ToString("d"); PurchaseDate = libraryBook.DateAdded.ToString("d");
ProductRating = GetStarString(Book.Rating); ProductRating = ValueOrDefault(Book.Rating?.ToStarString(), "");
Authors = Book.AuthorNames; Authors = Book.AuthorNames;
Narrators = Book.NarratorNames; Narrators = Book.NarratorNames;
Category = string.Join(" > ", Book.CategoriesNames); Category = string.Join(" > ", Book.CategoriesNames);
@ -60,28 +56,20 @@ namespace LibationWinForms
Description = GetDescriptionDisplay(Book); Description = GetDescriptionDisplay(Book);
} }
//DisplayTags and Liberate are live. //DisplayTags and Liberate properties are live.
} }
private void PictureStorage_PictureCached(object sender, string pictureId) private void PictureStorage_PictureCached(object sender, string pictureId)
{ {
if (pictureId == Book.PictureId) if (pictureId == Book.PictureId)
{ {
//GridEntry SHOULD be UI-ignorant, but PropertyChanged
Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80);
FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached;
} }
} }
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
=> SyncContext.Post(
args => OnPropertyChangedAsync(args as AsyncCompletedEventArgs),
new AsyncCompletedEventArgs(null, false, new PropertyChangedEventArgs(propertyName)));
private void OnPropertyChangedAsync(AsyncCompletedEventArgs e) =>
PropertyChanged?.Invoke(this, e.UserState as PropertyChangedEventArgs);
#region Data Source properties #region Data Source properties
public Image Cover public Image Cover
{ {
get get
@ -106,7 +94,6 @@ namespace LibationWinForms
public string Category { get; } public string Category { get; }
public string Misc { get; } public string Misc { get; }
public string Description { get; } public string Description { get; }
public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book)); public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book));
@ -114,49 +101,43 @@ namespace LibationWinForms
#region Data Sorting #region Data Sorting
private Dictionary<string, Func<object>> _compareValues { get; } private Dictionary<string, Func<object>> _memberValues { get; }
private static Dictionary<Type, IComparer> _objectComparers;
public virtual object GetMemberValue(string memberName) => _compareValues[memberName]();
public virtual IComparer GetMemberComparer(Type memberType) => _objectComparers[memberType];
/// <summary> /// <summary>
/// Instantiate comparers for every type needed to sort columns. /// Create getters for all member object values by name
/// </summary> /// </summary>
static GridEntry() Dictionary<string, Func<object>> CreateMemberValueDictionary() => new()
{ {
_objectComparers = new Dictionary<Type, IComparer>() { nameof(Title), () => GetSortName(Book.Title) },
{ { nameof(Series), () => GetSortName(Book.SeriesNames) },
{ typeof(string), new ObjectComparer<string>() }, { nameof(Length), () => Book.LengthInMinutes },
{ typeof(int), new ObjectComparer<int>() }, { nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore },
{ typeof(float), new ObjectComparer<float>() }, { nameof(PurchaseDate), () => LibraryBook.DateAdded },
{ typeof(DateTime), new ObjectComparer<DateTime>() }, { nameof(ProductRating), () => Book.Rating.FirstScore },
{ typeof(LiberatedState), new ObjectComparer<LiberatedState>() }, { nameof(Authors), () => Authors },
}; { nameof(Narrators), () => Narrators },
} { nameof(Description), () => Description },
{ nameof(Category), () => Category },
{ nameof(Misc), () => Misc },
{ nameof(DisplayTags), () => DisplayTags },
{ nameof(Liberate), () => Liberate.Item1 }
};
/// <summary> // Instantiate comparers for every exposed member object type.
/// Create getters for all member values by name private static readonly Dictionary<Type, IComparer> _memberTypeComparers = new()
/// </summary> {
Dictionary<string, Func<object>> CreatePropertyValueDictionary() => new() { typeof(string), new ObjectComparer<string>() },
{ { typeof(int), new ObjectComparer<int>() },
{ nameof(Title), () => getSortName(Book.Title)}, { typeof(float), new ObjectComparer<float>() },
{ nameof(Series),() => getSortName(Book.SeriesNames)}, { typeof(DateTime), new ObjectComparer<DateTime>() },
{ nameof(Length), () => Book.LengthInMinutes}, { typeof(LiberatedState), new ObjectComparer<LiberatedState>() },
{ nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore}, };
{ nameof(PurchaseDate), () => LibraryBook.DateAdded},
{ nameof(ProductRating), () => Book.Rating.FirstScore},
{ nameof(Authors), () => Authors},
{ nameof(Narrators), () => Narrators},
{ nameof(Description), () => Description},
{ nameof(Category), () => Category},
{ nameof(Misc), () => Misc},
{ nameof(DisplayTags), () => DisplayTags},
{ nameof(Liberate), () => Liberate.Item1}
};
private static readonly string[] sortPrefixIgnores = { "the", "a", "an" }; public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
private static string getSortName(string unformattedName) public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType];
private static readonly string[] _sortPrefixIgnores = { "the", "a", "an" };
private static string GetSortName(string unformattedName)
{ {
var sortName = unformattedName var sortName = unformattedName
.Replace("|", "") .Replace("|", "")
@ -164,98 +145,71 @@ namespace LibationWinForms
.ToLowerInvariant() .ToLowerInvariant()
.Trim(); .Trim();
if (sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " "))) if (_sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " ")))
sortName = sortName.Substring(sortName.IndexOf(" ") + 1).TrimStart(); sortName = sortName.Substring(sortName.IndexOf(" ") + 1).TrimStart();
return sortName; return sortName;
} }
#endregion #endregion
#region Static library display functions #region Static library display functions
public static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus) public static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus)
{ {
string text; (string libState, string image_lib) = liberatedStatus switch
Bitmap image;
// get mouseover text
{ {
var libState = liberatedStatus switch LiberatedState.Liberated => ("Liberated", "green"),
{ LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
LiberatedState.Liberated => "Liberated", LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"),
LiberatedState.PartialDownload => "File has been at least\r\npartially downloaded", _ => throw new Exception("Unexpected liberation state")
LiberatedState.NotDownloaded => "Book NOT downloaded", };
_ => throw new Exception("Unexpected liberation state")
};
var pdfState = pdfStatus switch (string pdfState, string image_pdf) = pdfStatus switch
{
PdfState.Downloaded => "\r\nPDF downloaded",
PdfState.NotDownloaded => "\r\nPDF NOT downloaded",
PdfState.NoPdf => "",
_ => throw new Exception("Unexpected PDF state")
};
text = libState + pdfState;
if (liberatedStatus == LiberatedState.NotDownloaded ||
liberatedStatus == LiberatedState.PartialDownload ||
pdfStatus == PdfState.NotDownloaded)
text += "\r\nClick to complete";
}
// get image
{ {
var image_lib PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"),
= liberatedStatus == LiberatedState.NotDownloaded ? "red" PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"),
: liberatedStatus == LiberatedState.PartialDownload ? "yellow" PdfState.NoPdf => ("", ""),
: liberatedStatus == LiberatedState.Liberated ? "green" _ => throw new Exception("Unexpected PDF state")
: throw new Exception("Unexpected liberation state"); };
var image_pdf
= pdfStatus == PdfState.NoPdf ? ""
: pdfStatus == PdfState.NotDownloaded ? "_pdf_no"
: pdfStatus == PdfState.Downloaded ? "_pdf_yes"
: throw new Exception("Unexpected PDF state");
image = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); var mouseoverText = libState + pdfState;
}
return (text, image); if (liberatedStatus == LiberatedState.NotDownloaded ||
liberatedStatus == LiberatedState.PartialDownload ||
pdfStatus == PdfState.NotDownloaded)
mouseoverText += "\r\nClick to complete";
var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");
return (mouseoverText, buttonImage);
} }
/// <summary> /// <summary>
/// This information should not change during <see cref="GridEntry"/> lifetime, so call only once. /// This information should not change during <see cref="GridEntry"/> lifetime, so call only once.
/// </summary> /// </summary>
private static string GetDescriptionDisplay(Book book) private static string GetDescriptionDisplay(Book book)
{ {
var doc = new HtmlAgilityPack.HtmlDocument(); var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(book.Description); doc.LoadHtml(book.Description);
var noHtml = doc.DocumentNode.InnerText; var noHtml = doc.DocumentNode.InnerText;
return return
noHtml.Length < 63? noHtml.Length < 63 ?
noHtml : noHtml :
noHtml.Substring(0, 60) + "..."; noHtml.Substring(0, 60) + "...";
} }
/// <summary> /// <summary>
/// This information should not change during <see cref="GridEntry"/> lifetime, so call only once. /// This information should not change during <see cref="GridEntry"/> lifetime, so call only once.
/// Maximum of 5 text rows will fit in 80-pixel row height.
/// </summary> /// </summary>
private static string GetMiscDisplay(LibraryBook libraryBook) private static string GetMiscDisplay(LibraryBook libraryBook)
{ {
// max 5 text rows
var details = new List<string>(); var details = new List<string>();
var locale var locale = ValueOrDefault(libraryBook.Book.Locale, "[unknown]");
= string.IsNullOrWhiteSpace(libraryBook.Book.Locale) var acct = ValueOrDefault(libraryBook.Account, "[unknown]");
? "[unknown]"
: libraryBook.Book.Locale;
var acct
= string.IsNullOrWhiteSpace(libraryBook.Account)
? "[unknown]"
: libraryBook.Account;
details.Add($"Account: {locale} - {acct}"); details.Add($"Account: {locale} - {acct}");
if (libraryBook.Book.HasPdf) if (libraryBook.Book.HasPdf)
@ -274,10 +228,9 @@ namespace LibationWinForms
return string.Join("\r\n", details); return string.Join("\r\n", details);
} }
private static string GetStarString(Rating rating) //Maybe add to Dinah StringExtensions?
=> (rating?.FirstScore > 0f) private static string ValueOrDefault(string value, string defaultValue)
? rating?.ToStarString() => string.IsNullOrWhiteSpace(value) ? defaultValue : value;
: "";
#endregion #endregion
} }

View File

@ -21,7 +21,7 @@ namespace LibationWinForms
{ {
(string mouseoverText, Bitmap buttonImage) = GridEntry.GetLiberateDisplay(liberatedState, pdfState); (string mouseoverText, Bitmap buttonImage) = GridEntry.GetLiberateDisplay(liberatedState, pdfState);
DrawImage(graphics, buttonImage, cellBounds); DrawButtonImage(graphics, buttonImage, cellBounds);
ToolTipText = mouseoverText; ToolTipText = mouseoverText;
} }

View File

@ -137,9 +137,7 @@ namespace LibationWinForms
// .ThenBy(ge => ge.Title) // .ThenBy(ge => ge.Title)
.ToList(); .ToList();
//
// BIND // BIND
//
gridEntryBindingSource.DataSource = new SortableBindingList2<GridEntry>(orderedGridEntries); gridEntryBindingSource.DataSource = new SortableBindingList2<GridEntry>(orderedGridEntries);
// FILTER // FILTER
@ -192,7 +190,7 @@ namespace LibationWinForms
#endregion #endregion
#region DataGridView Macros #region DataGridView Macro
private int getRowIndex(Func<GridEntry, bool> func) => _dataGridView.GetRowIdOfBoundItem(func); private int getRowIndex(Func<GridEntry, bool> func) => _dataGridView.GetRowIdOfBoundItem(func);
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex); private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);

View File

@ -2,29 +2,23 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibationWinForms namespace LibationWinForms
{ {
class SortableBindingList2<T> : BindingList<T> where T : IObjectMemberComparable class SortableBindingList2<T> : BindingList<T> where T : IObjectMemberComparable
{ {
private ObjectMemberComparer<T> Comparer = new();
private bool isSorted; private bool isSorted;
private ListSortDirection listSortDirection; private ListSortDirection listSortDirection;
private PropertyDescriptor propertyDescriptor; private PropertyDescriptor propertyDescriptor;
public SortableBindingList2() : base(new List<T>()) { } public SortableBindingList2() : base(new List<T>()) { }
public SortableBindingList2(IEnumerable<T> enumeration) : base(new List<T>(enumeration)) { } public SortableBindingList2(IEnumerable<T> enumeration) : base(new List<T>(enumeration)) { }
private ObjectMemberComparer<T> Comparer { get; } = new();
protected override bool SupportsSortingCore => true; protected override bool SupportsSortingCore => true;
protected override bool SupportsSearchingCore => true;
protected override bool IsSortedCore => isSorted; protected override bool IsSortedCore => isSorted;
protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; protected override PropertyDescriptor SortPropertyCore => propertyDescriptor;
protected override ListSortDirection SortDirectionCore => listSortDirection; protected override ListSortDirection SortDirectionCore => listSortDirection;
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
@ -34,8 +28,8 @@ namespace LibationWinForms
Comparer.PropertyName = property.Name; Comparer.PropertyName = property.Name;
Comparer.Direction = direction; Comparer.Direction = direction;
//Array.Sort and Liat<T>.Sort are unstable sorts. OrderBy is stable. //Array.Sort() and List<T>.Sort() are unstable sorts. OrderBy is stable.
var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToArray(); var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList();
itemsList.Clear(); itemsList.Clear();
itemsList.AddRange(sortedItems); itemsList.AddRange(sortedItems);
@ -55,5 +49,27 @@ namespace LibationWinForms
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
} }
//NOTE: Libation does not currently use BindingSource.Find anywhere,
//so this override may be removed (along with SupportsSearchingCore)
protected override int FindCore(PropertyDescriptor property, object key)
{
int count = Count;
System.Collections.IComparer valueComparer = null;
for (int i = 0; i < count; ++i)
{
T element = this[i];
var elemValue = element.GetMemberValue(property.Name);
valueComparer ??= element.GetMemberComparer(elemValue.GetType());
if (valueComparer.Compare(elemValue, key) == 0)
{
return i;
}
}
return -1;
}
} }
} }