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
{
protected void DrawImage(Graphics graphics, Bitmap image, Rectangle cellBounds)
protected void DrawButtonImage(Graphics graphics, Image image, Rectangle cellBounds)
{
var w = image.Width;
var h = image.Height;

View File

@ -31,7 +31,7 @@ namespace LibationWinForms.Dialogs
{
this.components = new System.ComponentModel.Container();
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.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn();
this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
@ -41,25 +41,25 @@ namespace LibationWinForms.Dialogs
this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.btnRemoveBooks = new System.Windows.Forms.Button();
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();
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.Right)));
this.dataGridView1.AutoGenerateColumns = false;
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this._dataGridView.AutoGenerateColumns = false;
this._dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this._dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.removeDataGridViewCheckBoxColumn,
this.coverDataGridViewImageColumn,
this.titleDataGridViewTextBoxColumn,
this.authorsDataGridViewTextBoxColumn,
this.miscDataGridViewTextBoxColumn,
this.purchaseDateGridViewTextBoxColumn});
this.dataGridView1.DataSource = this.gridEntryBindingSource;
this._dataGridView.DataSource = this.gridEntryBindingSource;
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
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.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle2;
this.dataGridView1.Location = new System.Drawing.Point(0, 0);
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.RowHeadersVisible = false;
this.dataGridView1.RowTemplate.Height = 82;
this.dataGridView1.Size = new System.Drawing.Size(800, 409);
this.dataGridView1.TabIndex = 0;
this._dataGridView.DefaultCellStyle = dataGridViewCellStyle2;
this._dataGridView.Location = new System.Drawing.Point(0, 0);
this._dataGridView.Name = "_dataGridView";
this._dataGridView.RowHeadersVisible = false;
this._dataGridView.RowTemplate.Height = 82;
this._dataGridView.Size = new System.Drawing.Size(800, 409);
this._dataGridView.TabIndex = 0;
//
// removeDataGridViewCheckBoxColumn
//
@ -162,11 +162,11 @@ namespace LibationWinForms.Dialogs
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.label1);
this.Controls.Add(this.btnRemoveBooks);
this.Controls.Add(this.dataGridView1);
this.Controls.Add(this._dataGridView);
this.Name = "RemoveBooksDialog";
this.Text = "RemoveBooksDialog";
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();
this.ResumeLayout(false);
this.PerformLayout();
@ -175,7 +175,7 @@ namespace LibationWinForms.Dialogs
#endregion
private System.Windows.Forms.DataGridView dataGridView1;
private System.Windows.Forms.DataGridView _dataGridView;
private System.Windows.Forms.BindingSource gridEntryBindingSource;
private System.Windows.Forms.Button btnRemoveBooks;
private System.Windows.Forms.Label label1;

View File

@ -31,9 +31,9 @@ namespace LibationWinForms.Dialogs
InitializeComponent();
_labelFormat = label1.Text;
dataGridView1.CellContentClick += (s, e) => dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
dataGridView1.BindingContextChanged += (s, e) => UpdateSelection();
_dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
_dataGridView.CellValueChanged += DataGridView1_CellValueChanged;
_dataGridView.BindingContextChanged += (s, e) => UpdateSelection();
var orderedGridEntries = _libraryBooks
.Select(lb => new RemovableGridEntry(lb))
@ -43,7 +43,7 @@ namespace LibationWinForms.Dialogs
_removableGridEntries = new SortableBindingList2<RemovableGridEntry>(orderedGridEntries);
gridEntryBindingSource.DataSource = _removableGridEntries;
dataGridView1.Enabled = false;
_dataGridView.Enabled = false;
}
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
@ -79,7 +79,7 @@ namespace LibationWinForms.Dialogs
}
finally
{
dataGridView1.Enabled = true;
_dataGridView.Enabled = true;
}
}
@ -108,7 +108,7 @@ namespace LibationWinForms.Dialogs
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.SaveChanges();
@ -116,14 +116,14 @@ namespace LibationWinForms.Dialogs
foreach (var rEntry in selectedBooks)
_removableGridEntries.Remove(rEntry);
BooksRemoved = removeLibraryBooks.Length > 0;
BooksRemoved = removeLibraryBooks.Count > 0;
UpdateSelection();
}
}
private void UpdateSelection()
{
dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending);
_dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending);
var selectedCount = SelectedCount;
label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty);
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 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 base.GetComparer(propertyType);
return base.GetMemberComparer(memberType);
}
}
}

View File

@ -1,64 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<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.
-->
<root>
<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:element name="root" msdata:IsDataSet="true">
@ -117,4 +57,7 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</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>

View File

@ -11,19 +11,26 @@ namespace LibationWinForms
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;
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);
DrawImage(graphics, ButtonImage, cellBounds);
DrawButtonImage(graphics, ButtonImage, cellBounds);
}
else
{

View File

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

View File

@ -4,15 +4,13 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using ApplicationServices;
using DataLayer;
using Dinah.Core.Drawing;
namespace LibationWinForms
{
internal class GridEntry : INotifyPropertyChanged, IObjectMemberComparable
internal class GridEntry : AsyncNotifyPropertyChanged, IObjectMemberComparable
{
#region implementation properties
// hide from public fields from Data Source GUI with [Browsable(false)]
@ -24,16 +22,14 @@ namespace LibationWinForms
#endregion
public event PropertyChangedEventHandler PropertyChanged;
private Book Book => LibraryBook.Book;
private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current;
private Image _cover;
public GridEntry(LibraryBook libraryBook)
{
LibraryBook = libraryBook;
_compareValues = CreatePropertyValueDictionary();
_memberValues = CreateMemberValueDictionary();
//Get cover art. If it's default, subscribe to PictureCached
var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80);
@ -42,7 +38,7 @@ namespace LibationWinForms
if (isDefault)
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);
//Immutable properties
@ -50,9 +46,9 @@ namespace LibationWinForms
Title = Book.Title;
Series = Book.SeriesNames;
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");
ProductRating = GetStarString(Book.Rating);
ProductRating = ValueOrDefault(Book.Rating?.ToStarString(), "");
Authors = Book.AuthorNames;
Narrators = Book.NarratorNames;
Category = string.Join(" > ", Book.CategoriesNames);
@ -60,28 +56,20 @@ namespace LibationWinForms
Description = GetDescriptionDisplay(Book);
}
//DisplayTags and Liberate are live.
//DisplayTags and Liberate properties are live.
}
private void PictureStorage_PictureCached(object sender, string pictureId)
{
if (pictureId == Book.PictureId)
{
//GridEntry SHOULD be UI-ignorant, but PropertyChanged
Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80);
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
public Image Cover
{
get
@ -106,7 +94,6 @@ namespace LibationWinForms
public string Category { get; }
public string Misc { get; }
public string Description { get; }
public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book));
@ -114,18 +101,30 @@ namespace LibationWinForms
#region Data Sorting
private Dictionary<string, Func<object>> _compareValues { get; }
private static Dictionary<Type, IComparer> _objectComparers;
public virtual object GetMemberValue(string memberName) => _compareValues[memberName]();
public virtual IComparer GetMemberComparer(Type memberType) => _objectComparers[memberType];
private Dictionary<string, Func<object>> _memberValues { get; }
/// <summary>
/// Instantiate comparers for every type needed to sort columns.
/// Create getters for all member object values by name
/// </summary>
static GridEntry()
Dictionary<string, Func<object>> CreateMemberValueDictionary() => new()
{
_objectComparers = new Dictionary<Type, IComparer>()
{ nameof(Title), () => GetSortName(Book.Title) },
{ nameof(Series), () => GetSortName(Book.SeriesNames) },
{ nameof(Length), () => Book.LengthInMinutes },
{ 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 }
};
// Instantiate comparers for every exposed member object type.
private static readonly Dictionary<Type, IComparer> _memberTypeComparers = new()
{
{ typeof(string), new ObjectComparer<string>() },
{ typeof(int), new ObjectComparer<int>() },
@ -133,30 +132,12 @@ namespace LibationWinForms
{ typeof(DateTime), new ObjectComparer<DateTime>() },
{ typeof(LiberatedState), new ObjectComparer<LiberatedState>() },
};
}
/// <summary>
/// Create getters for all member values by name
/// </summary>
Dictionary<string, Func<object>> CreatePropertyValueDictionary() => new()
{
{ nameof(Title), () => getSortName(Book.Title)},
{ nameof(Series),() => getSortName(Book.SeriesNames)},
{ nameof(Length), () => Book.LengthInMinutes},
{ 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}
};
public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType];
private static readonly string[] sortPrefixIgnores = { "the", "a", "an" };
private static string getSortName(string unformattedName)
private static readonly string[] _sortPrefixIgnores = { "the", "a", "an" };
private static string GetSortName(string unformattedName)
{
var sortName = unformattedName
.Replace("|", "")
@ -164,7 +145,7 @@ namespace LibationWinForms
.ToLowerInvariant()
.Trim();
if (sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " ")))
if (_sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " ")))
sortName = sortName.Substring(sortName.IndexOf(" ") + 1).TrimStart();
return sortName;
@ -176,53 +157,32 @@ namespace LibationWinForms
public static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus)
{
string text;
Bitmap image;
// get mouseover text
(string libState, string image_lib) = liberatedStatus switch
{
var libState = liberatedStatus switch
{
LiberatedState.Liberated => "Liberated",
LiberatedState.PartialDownload => "File has been at least\r\npartially downloaded",
LiberatedState.NotDownloaded => "Book NOT downloaded",
LiberatedState.Liberated => ("Liberated", "green"),
LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"),
_ => 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 => "",
PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"),
PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"),
PdfState.NoPdf => ("", ""),
_ => throw new Exception("Unexpected PDF state")
};
text = libState + pdfState;
var mouseoverText = libState + pdfState;
if (liberatedStatus == LiberatedState.NotDownloaded ||
liberatedStatus == LiberatedState.PartialDownload ||
pdfStatus == PdfState.NotDownloaded)
text += "\r\nClick to complete";
mouseoverText += "\r\nClick to complete";
}
var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");
// get image
{
var image_lib
= liberatedStatus == LiberatedState.NotDownloaded ? "red"
: liberatedStatus == LiberatedState.PartialDownload ? "yellow"
: liberatedStatus == LiberatedState.Liberated ? "green"
: 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}");
}
return (text, image);
return (mouseoverText, buttonImage);
}
/// <summary>
@ -234,28 +194,22 @@ namespace LibationWinForms
doc.LoadHtml(book.Description);
var noHtml = doc.DocumentNode.InnerText;
return
noHtml.Length < 63?
noHtml.Length < 63 ?
noHtml :
noHtml.Substring(0, 60) + "...";
}
/// <summary>
/// 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>
private static string GetMiscDisplay(LibraryBook libraryBook)
{
// max 5 text rows
var details = new List<string>();
var locale
= string.IsNullOrWhiteSpace(libraryBook.Book.Locale)
? "[unknown]"
: libraryBook.Book.Locale;
var acct
= string.IsNullOrWhiteSpace(libraryBook.Account)
? "[unknown]"
: libraryBook.Account;
var locale = ValueOrDefault(libraryBook.Book.Locale, "[unknown]");
var acct = ValueOrDefault(libraryBook.Account, "[unknown]");
details.Add($"Account: {locale} - {acct}");
if (libraryBook.Book.HasPdf)
@ -274,10 +228,9 @@ namespace LibationWinForms
return string.Join("\r\n", details);
}
private static string GetStarString(Rating rating)
=> (rating?.FirstScore > 0f)
? rating?.ToStarString()
: "";
//Maybe add to Dinah StringExtensions?
private static string ValueOrDefault(string value, string defaultValue)
=> string.IsNullOrWhiteSpace(value) ? defaultValue : value;
#endregion
}

View File

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

View File

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

View File

@ -2,29 +2,23 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibationWinForms
{
class SortableBindingList2<T> : BindingList<T> where T : IObjectMemberComparable
{
private ObjectMemberComparer<T> Comparer = new();
private bool isSorted;
private ListSortDirection listSortDirection;
private PropertyDescriptor propertyDescriptor;
public SortableBindingList2() : base(new List<T>()) { }
public SortableBindingList2(IEnumerable<T> enumeration) : base(new List<T>(enumeration)) { }
private ObjectMemberComparer<T> Comparer { get; } = new();
protected override bool SupportsSortingCore => true;
protected override bool SupportsSearchingCore => true;
protected override bool IsSortedCore => isSorted;
protected override PropertyDescriptor SortPropertyCore => propertyDescriptor;
protected override ListSortDirection SortDirectionCore => listSortDirection;
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
@ -34,8 +28,8 @@ namespace LibationWinForms
Comparer.PropertyName = property.Name;
Comparer.Direction = direction;
//Array.Sort and Liat<T>.Sort are unstable sorts. OrderBy is stable.
var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToArray();
//Array.Sort() and List<T>.Sort() are unstable sorts. OrderBy is stable.
var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList();
itemsList.Clear();
itemsList.AddRange(sortedItems);
@ -55,5 +49,27 @@ namespace LibationWinForms
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;
}
}
}