625 lines
22 KiB
C#
625 lines
22 KiB
C#
using DataLayer;
|
|
using Dinah.Core;
|
|
using Dinah.Core.Collections.Generic;
|
|
using Dinah.Core.WindowsDesktop.Forms;
|
|
using LibationFileManager;
|
|
using LibationUiBase.GridView;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
#nullable enable
|
|
|
|
namespace LibationWinForms.GridView
|
|
{
|
|
public delegate void GridEntryClickedEventHandler(GridEntry liveGridEntry);
|
|
public delegate void LibraryBookEntryClickedEventHandler(LibraryBookEntry liveGridEntry);
|
|
public delegate void GridEntryRectangleClickedEventHandler(GridEntry liveGridEntry, Rectangle cellRectangle);
|
|
public delegate void ProductsGridCellContextMenuStripNeededEventHandler(GridEntry[] liveGridEntry, ContextMenuStrip ctxMenu);
|
|
|
|
public partial class ProductsGrid : UserControl
|
|
{
|
|
/// <summary>Number of visible rows has changed</summary>
|
|
public event EventHandler<int>? VisibleCountChanged;
|
|
public event LibraryBookEntryClickedEventHandler? LiberateClicked;
|
|
public event GridEntryClickedEventHandler? CoverClicked;
|
|
public event LibraryBookEntryClickedEventHandler? DetailsClicked;
|
|
public event GridEntryRectangleClickedEventHandler? DescriptionClicked;
|
|
public new event EventHandler<ScrollEventArgs>? Scroll;
|
|
public event EventHandler? RemovableCountChanged;
|
|
public event ProductsGridCellContextMenuStripNeededEventHandler? LiberateContextMenuStripNeeded;
|
|
|
|
private GridEntryBindingList? bindingList;
|
|
internal IEnumerable<LibraryBook> GetVisibleBooks()
|
|
=> bindingList
|
|
?.GetFilteredInItems()
|
|
.Select(lbe => lbe.LibraryBook) ?? Enumerable.Empty<LibraryBook>();
|
|
internal IEnumerable<LibraryBookEntry> GetAllBookEntries()
|
|
=> bindingList?.AllItems().BookEntries() ?? Enumerable.Empty<LibraryBookEntry>();
|
|
|
|
public ProductsGrid()
|
|
{
|
|
InitializeComponent();
|
|
EnableDoubleBuffering();
|
|
gridEntryDataGridView.Scroll += (_, s) => Scroll?.Invoke(this, s);
|
|
gridEntryDataGridView.CellContextMenuStripNeeded += GridEntryDataGridView_CellContextMenuStripNeeded;
|
|
removeGVColumn.Frozen = false;
|
|
|
|
defaultFont = gridEntryDataGridView.DefaultCellStyle.Font;
|
|
setGridFontScale(Configuration.Instance.GridFontScaleFactor);
|
|
setGridScale(Configuration.Instance.GridScaleFactor);
|
|
Configuration.Instance.PropertyChanged += Configuration_ScaleChanged;
|
|
Configuration.Instance.PropertyChanged += Configuration_FontScaleChanged;
|
|
|
|
gridEntryDataGridView.Disposed += (_, _) =>
|
|
{
|
|
Configuration.Instance.PropertyChanged -= Configuration_ScaleChanged;
|
|
Configuration.Instance.PropertyChanged -= Configuration_FontScaleChanged;
|
|
};
|
|
}
|
|
|
|
#region Scaling
|
|
|
|
[PropertyChangeFilter(nameof(Configuration.GridFontScaleFactor))]
|
|
private void Configuration_FontScaleChanged(object sender, PropertyChangedEventArgsEx e)
|
|
{
|
|
if (e.NewValue is float v)
|
|
setGridFontScale(v);
|
|
}
|
|
|
|
[PropertyChangeFilter(nameof(Configuration.GridScaleFactor))]
|
|
private void Configuration_ScaleChanged(object sender, PropertyChangedEventArgsEx e)
|
|
{
|
|
if (e.NewValue is float v)
|
|
setGridScale(v);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Keep track of the original dimensions for rescaling
|
|
/// </summary>
|
|
private static readonly Dictionary<DataGridViewElement, int> originalDims = new();
|
|
private readonly Font defaultFont;
|
|
private void setGridScale(float scale)
|
|
{
|
|
foreach (var col in gridEntryDataGridView.Columns.Cast<DataGridViewColumn>())
|
|
{
|
|
//Only resize fixed-width columns. The rest can be adjusted by users.
|
|
if (col.Resizable is DataGridViewTriState.False)
|
|
{
|
|
if (!originalDims.ContainsKey(col))
|
|
originalDims[col] = col.Width;
|
|
|
|
col.Width = this.DpiScale(originalDims[col], scale);
|
|
}
|
|
|
|
if (col is IDataGridScaleColumn scCol)
|
|
scCol.ScaleFactor = scale;
|
|
}
|
|
|
|
if (!originalDims.ContainsKey(gridEntryDataGridView.RowTemplate))
|
|
originalDims[gridEntryDataGridView.RowTemplate] = gridEntryDataGridView.RowTemplate.Height;
|
|
|
|
var height = gridEntryDataGridView.RowTemplate.Height = this.DpiScale(originalDims[gridEntryDataGridView.RowTemplate], scale);
|
|
|
|
foreach (var row in gridEntryDataGridView.Rows.Cast<DataGridViewRow>())
|
|
row.Height = height;
|
|
}
|
|
|
|
private void setGridFontScale(float scale)
|
|
=> gridEntryDataGridView.DefaultCellStyle.Font = new Font(defaultFont.FontFamily, defaultFont.Size * scale);
|
|
|
|
#endregion
|
|
|
|
private static string? RemoveLineBreaks(string? text)
|
|
=> text?.Replace("\r\n", "").Replace('\r', ' ').Replace('\n', ' ');
|
|
|
|
private void GridEntryDataGridView_CellContextMenuStripNeeded(object? sender, DataGridViewCellContextMenuStripNeededEventArgs e)
|
|
{
|
|
// header
|
|
if (e.RowIndex < 0 || sender is not DataGridView dgv)
|
|
return;
|
|
|
|
e.ContextMenuStrip = new ContextMenuStrip();
|
|
// any column except cover & stop light
|
|
if (e.ColumnIndex != liberateGVColumn.Index && e.ColumnIndex != coverGVColumn.Index)
|
|
{
|
|
e.ContextMenuStrip.Items.Add("Copy Cell Contents", null, (_, __) =>
|
|
{
|
|
try
|
|
{
|
|
string clipboardText;
|
|
|
|
if (dgv.SelectedCells.Count <= 1)
|
|
{
|
|
//Copy contents only of cell that was right-clicked on.
|
|
clipboardText = dgv[e.ColumnIndex, e.RowIndex].FormattedValue?.ToString() ?? string.Empty;
|
|
}
|
|
else
|
|
{
|
|
//Copy contents of selected cells. Each row is a new line,
|
|
//and columns are separated with tabs. Similar formatting to Microsoft Excel.
|
|
var selectedCells
|
|
= dgv.SelectedCells
|
|
.OfType<DataGridViewCell>()
|
|
.Where(c => c.OwningColumn is not null && c.OwningRow is not null)
|
|
.OrderBy(c => c.RowIndex)
|
|
.ThenBy(c => c.OwningColumn!.DisplayIndex)
|
|
.ToList();
|
|
|
|
var headerText
|
|
= string.Join("\t",
|
|
selectedCells
|
|
.Select(c => c.OwningColumn)
|
|
.Distinct()
|
|
.Select(c => RemoveLineBreaks(c?.HeaderText))
|
|
.OfType<string>());
|
|
|
|
List<string> linesOfText = [headerText];
|
|
foreach (var distinctRow in selectedCells.Select(c => c.RowIndex).Distinct())
|
|
{
|
|
linesOfText.Add(string.Join("\t",
|
|
selectedCells
|
|
.Where(c => c.RowIndex == distinctRow)
|
|
.Select(c => RemoveLineBreaks(c.FormattedValue?.ToString()) ?? string.Empty)
|
|
));
|
|
}
|
|
clipboardText = string.Join(Environment.NewLine, linesOfText);
|
|
}
|
|
Clipboard.SetDataObject(clipboardText, false, 5, 150);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
Serilog.Log.Logger.Error(ex, "Error copying text to clipboard");
|
|
}
|
|
});
|
|
e.ContextMenuStrip.Items.Add(new ToolStripSeparator());
|
|
}
|
|
|
|
var clickedEntry = getGridEntry(e.RowIndex);
|
|
|
|
var allSelected
|
|
= gridEntryDataGridView
|
|
.SelectedCells
|
|
.OfType<DataGridViewCell>()
|
|
.Select(c => c.OwningRow)
|
|
.OfType<DataGridViewRow>()
|
|
.Distinct()
|
|
.OrderBy(r => r.Index)
|
|
.Select(r => r.DataBoundItem)
|
|
.OfType<GridEntry>()
|
|
.ToArray();
|
|
|
|
var clickedIndex = Array.IndexOf(allSelected, clickedEntry);
|
|
if (clickedIndex == -1)
|
|
{
|
|
//User didn't right-click on a selected cell
|
|
gridEntryDataGridView.ClearSelection();
|
|
gridEntryDataGridView[e.ColumnIndex, e.RowIndex].Selected = true;
|
|
allSelected = [clickedEntry];
|
|
}
|
|
else if (clickedIndex > 0)
|
|
{
|
|
//Ensure the clicked entry is first in the list
|
|
(allSelected[0], allSelected[clickedIndex]) = (allSelected[clickedIndex], allSelected[0]);
|
|
}
|
|
LiberateContextMenuStripNeeded?.Invoke(allSelected, e.ContextMenuStrip);
|
|
}
|
|
|
|
private void EnableDoubleBuffering()
|
|
{
|
|
var propertyInfo = gridEntryDataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
|
|
|
propertyInfo?.SetValue(gridEntryDataGridView, true, null);
|
|
}
|
|
|
|
#region Button controls
|
|
private void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// handle grid button click: https://stackoverflow.com/a/13687844
|
|
if (e.RowIndex < 0)
|
|
return;
|
|
|
|
var entry = getGridEntry(e.RowIndex);
|
|
if (entry is LibraryBookEntry lbEntry)
|
|
{
|
|
if (e.ColumnIndex == liberateGVColumn.Index)
|
|
LiberateClicked?.Invoke(lbEntry);
|
|
else if (e.ColumnIndex == tagAndDetailsGVColumn.Index)
|
|
DetailsClicked?.Invoke(lbEntry);
|
|
else if (e.ColumnIndex == descriptionGVColumn.Index)
|
|
DescriptionClicked?.Invoke(lbEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false));
|
|
else if (e.ColumnIndex == coverGVColumn.Index)
|
|
CoverClicked?.Invoke(lbEntry);
|
|
}
|
|
else if (entry is SeriesEntry sEntry)
|
|
{
|
|
if (e.ColumnIndex == liberateGVColumn.Index)
|
|
{
|
|
if (sEntry.Liberate.Expanded)
|
|
bindingList?.CollapseItem(sEntry);
|
|
else
|
|
bindingList?.ExpandItem(sEntry);
|
|
|
|
VisibleCountChanged?.Invoke(this, bindingList?.GetFilteredInItems().Count() ?? 0);
|
|
}
|
|
else if (e.ColumnIndex == descriptionGVColumn.Index)
|
|
DescriptionClicked?.Invoke(sEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false));
|
|
else if (e.ColumnIndex == coverGVColumn.Index)
|
|
CoverClicked?.Invoke(sEntry);
|
|
}
|
|
|
|
if (e.ColumnIndex == removeGVColumn.Index)
|
|
{
|
|
gridEntryDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
|
RemovableCountChanged?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Logger.Error(ex, $"An error was encountered while processing a user click in the {nameof(ProductsGrid)}");
|
|
}
|
|
}
|
|
|
|
private GridEntry getGridEntry(int rowIndex) => gridEntryDataGridView.GetBoundItem<GridEntry>(rowIndex);
|
|
|
|
#endregion
|
|
|
|
#region UI display functions
|
|
|
|
internal bool RemoveColumnVisible
|
|
{
|
|
get => removeGVColumn.Visible;
|
|
set
|
|
{
|
|
if (value && bindingList is not null)
|
|
{
|
|
foreach (var book in bindingList.AllItems())
|
|
book.Remove = false;
|
|
}
|
|
|
|
removeGVColumn.DisplayIndex = 0;
|
|
removeGVColumn.Frozen = value;
|
|
removeGVColumn.Visible = value;
|
|
}
|
|
}
|
|
|
|
internal async Task BindToGridAsync(List<LibraryBook> dbBooks)
|
|
{
|
|
//Get the UI thread's synchronization context and set it on the current thread to ensure
|
|
//it's available for GetAllProductsAsync and GetAllSeriesEntriesAsync
|
|
var sc = Invoke(() => System.Threading.SynchronizationContext.Current);
|
|
System.Threading.SynchronizationContext.SetSynchronizationContext(sc);
|
|
|
|
var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks);
|
|
var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks);
|
|
|
|
geList.AddRange(seriesEntries);
|
|
//Sort descending by date (default sort property)
|
|
var comparer = new RowComparer();
|
|
geList.Sort((a, b) => comparer.Compare(b, a));
|
|
|
|
//Add all children beneath their parent
|
|
foreach (var series in seriesEntries)
|
|
{
|
|
var seriesIndex = geList.IndexOf(series);
|
|
foreach (var child in series.Children)
|
|
geList.Insert(++seriesIndex, child);
|
|
}
|
|
System.Threading.SynchronizationContext.SetSynchronizationContext(null);
|
|
|
|
bindingList = new GridEntryBindingList(geList);
|
|
bindingList.CollapseAll();
|
|
|
|
//The syncBindingSource ensures that the IGridEntry list is added on the UI thread
|
|
syncBindingSource.DataSource = bindingList;
|
|
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
|
}
|
|
|
|
internal void UpdateGrid(List<LibraryBook> dbBooks)
|
|
{
|
|
if (bindingList == null)
|
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(UpdateGrid)}");
|
|
|
|
//First row that is in view in the DataGridView
|
|
var topRow = gridEntryDataGridView.Rows.Cast<DataGridViewRow>().FirstOrDefault(r => r.Displayed)?.Index ?? 0;
|
|
|
|
#region Add new or update existing grid entries
|
|
|
|
//Remove filter prior to adding/updating books
|
|
string? existingFilter = syncBindingSource.Filter;
|
|
Filter(null);
|
|
|
|
//Add absent entries to grid, or update existing entry
|
|
|
|
var allEntries = bindingList.AllItems().BookEntries().ToDictionarySafe(b => b.AudibleProductId);
|
|
var seriesEntries = bindingList.AllItems().SeriesEntries().ToList();
|
|
var parentedEpisodes = dbBooks.ParentedEpisodes().ToHashSet();
|
|
|
|
//Get the UI thread's synchronization context and set it on the current thread to ensure
|
|
//it's available for creation of new IGridEntry items during upsert
|
|
var sc = Invoke(() => System.Threading.SynchronizationContext.Current);
|
|
System.Threading.SynchronizationContext.SetSynchronizationContext(sc);
|
|
|
|
bindingList.RaiseListChangedEvents = false;
|
|
foreach (var libraryBook in dbBooks.OrderBy(e => e.DateAdded))
|
|
{
|
|
var existingEntry = allEntries.TryGetValue(libraryBook.Book.AudibleProductId, out var e) ? e : null;
|
|
|
|
if (libraryBook.Book.IsProduct())
|
|
{
|
|
AddOrUpdateBook(libraryBook, existingEntry);
|
|
continue;
|
|
}
|
|
if (parentedEpisodes.Contains(libraryBook))
|
|
{
|
|
//Only try to add or update is this LibraryBook is a know child of a parent
|
|
AddOrUpdateEpisode(libraryBook, existingEntry, seriesEntries, dbBooks);
|
|
}
|
|
}
|
|
bindingList.RaiseListChangedEvents = true;
|
|
|
|
//Re-apply filter after adding new/updating existing books to capture any changes
|
|
//The Filter call also ensures that the binding list is reset so the DataGridView
|
|
//is made aware of all changes that were made while RaiseListChangedEvents was false
|
|
Filter(existingFilter);
|
|
|
|
#endregion
|
|
|
|
// remove deleted from grid.
|
|
// note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this
|
|
var removedBooks =
|
|
bindingList
|
|
.AllItems()
|
|
.BookEntries()
|
|
.ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId);
|
|
|
|
RemoveBooks(removedBooks);
|
|
|
|
gridEntryDataGridView.FirstDisplayedScrollingRowIndex = topRow;
|
|
}
|
|
|
|
public void RemoveBooks(IEnumerable<LibraryBookEntry> removedBooks)
|
|
{
|
|
if (bindingList == null)
|
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(RemoveBooks)}");
|
|
|
|
//Remove books in series from their parents' Children list
|
|
foreach (var removed in removedBooks.Where(b => b.Liberate.IsEpisode))
|
|
removed.Parent.RemoveChild(removed);
|
|
|
|
//Remove series that have no children
|
|
var removedSeries =
|
|
bindingList
|
|
.AllItems()
|
|
.EmptySeries();
|
|
|
|
foreach (var removed in removedBooks.Cast<GridEntry>().Concat(removedSeries))
|
|
//no need to re-filter for removed books
|
|
bindingList.Remove(removed);
|
|
|
|
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
|
}
|
|
|
|
private void AddOrUpdateBook(LibraryBook book, LibraryBookEntry? existingBookEntry)
|
|
{
|
|
if (bindingList == null)
|
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateBook)}");
|
|
|
|
if (existingBookEntry is null)
|
|
// Add the new product to top
|
|
bindingList.Insert(0, new LibraryBookEntry(book));
|
|
else
|
|
// update existing
|
|
existingBookEntry.UpdateLibraryBook(book);
|
|
}
|
|
|
|
private void AddOrUpdateEpisode(LibraryBook episodeBook, LibraryBookEntry? existingEpisodeEntry, List<SeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
|
|
{
|
|
if (bindingList == null)
|
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateEpisode)}");
|
|
|
|
if (existingEpisodeEntry is null)
|
|
{
|
|
LibraryBookEntry episodeEntry;
|
|
|
|
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
|
|
|
|
if (seriesEntry is null)
|
|
{
|
|
//Series doesn't exist yet, so create and add it
|
|
var seriesBook = dbBooks.FindSeriesParent(episodeBook);
|
|
|
|
if (seriesBook is null)
|
|
{
|
|
//This is only possible if the user's db has some malformed
|
|
//entries from earlier Libation releases that could not be
|
|
//automatically fixed. Log, but don't throw.
|
|
Serilog.Log.Logger.Error("Episode={0}, Episode Series: {1}", episodeBook, episodeBook.Book.SeriesNames());
|
|
return;
|
|
}
|
|
|
|
seriesEntry = new SeriesEntry(seriesBook, episodeBook);
|
|
seriesEntries.Add(seriesEntry);
|
|
|
|
episodeEntry = seriesEntry.Children[0];
|
|
seriesEntry.Liberate.Expanded = true;
|
|
bindingList.Insert(0, seriesEntry);
|
|
}
|
|
else
|
|
{
|
|
//Series exists. Create and add episode child then update the SeriesEntry
|
|
episodeEntry = new LibraryBookEntry(episodeBook, seriesEntry);
|
|
seriesEntry.Children.Add(episodeEntry);
|
|
seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex));
|
|
var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId);
|
|
seriesEntry.UpdateLibraryBook(seriesBook);
|
|
}
|
|
|
|
//Series entry must be expanded so its child can
|
|
//be placed in the correct position beneath it.
|
|
var isExpanded = seriesEntry.Liberate.Expanded;
|
|
bindingList.ExpandItem(seriesEntry);
|
|
|
|
//Add episode to the grid beneath the parent
|
|
int seriesIndex = bindingList.IndexOf(seriesEntry);
|
|
int episodeIndex = seriesEntry.Children.IndexOf(episodeEntry);
|
|
bindingList.Insert(seriesIndex + 1 + episodeIndex, episodeEntry);
|
|
|
|
if (isExpanded)
|
|
bindingList.ExpandItem(seriesEntry);
|
|
else
|
|
bindingList.CollapseItem(seriesEntry);
|
|
}
|
|
else
|
|
existingEpisodeEntry.UpdateLibraryBook(episodeBook);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Filter
|
|
|
|
public void Filter(string? searchString)
|
|
{
|
|
if (bindingList is null) return;
|
|
|
|
int visibleCount = bindingList.Count;
|
|
|
|
if (string.IsNullOrEmpty(searchString))
|
|
syncBindingSource.RemoveFilter();
|
|
else
|
|
syncBindingSource.Filter = searchString;
|
|
|
|
if (visibleCount != bindingList.Count)
|
|
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Column Customizations
|
|
|
|
private void ProductsGrid_Load(object sender, EventArgs e)
|
|
{
|
|
//https://stackoverflow.com/a/4498512/3335599
|
|
if (System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime) return;
|
|
|
|
gridEntryDataGridView.ColumnWidthChanged += gridEntryDataGridView_ColumnWidthChanged;
|
|
gridEntryDataGridView.ColumnDisplayIndexChanged += gridEntryDataGridView_ColumnDisplayIndexChanged;
|
|
|
|
showHideColumnsContextMenuStrip.Items.Add(new ToolStripLabel("Show / Hide Columns"));
|
|
showHideColumnsContextMenuStrip.Items.Add(new ToolStripSeparator());
|
|
|
|
//Restore Grid Display Settings
|
|
var config = Configuration.Instance;
|
|
var gridColumnsWidths = config.GridColumnsWidths;
|
|
var displayIndices = config.GridColumnsDisplayIndices;
|
|
|
|
var cmsKiller = new ContextMenuStrip();
|
|
|
|
foreach (DataGridViewColumn column in gridEntryDataGridView.Columns)
|
|
{
|
|
var itemName = column.DataPropertyName;
|
|
var visible = config.GetColumnVisibility(itemName);
|
|
|
|
var menuItem = new ToolStripMenuItem(column.HeaderText)
|
|
{
|
|
Checked = visible,
|
|
Tag = itemName
|
|
};
|
|
menuItem.Click += HideMenuItem_Click;
|
|
showHideColumnsContextMenuStrip.Items.Add(menuItem);
|
|
|
|
//Only set column widths for user resizable columns.
|
|
//Fixed column widths are set by setGridScale()
|
|
if (column.Resizable is not DataGridViewTriState.False)
|
|
column.Width = gridColumnsWidths.GetValueOrDefault(itemName, this.DpiScale(column.Width));
|
|
|
|
column.MinimumWidth = 10;
|
|
column.HeaderCell.ContextMenuStrip = showHideColumnsContextMenuStrip;
|
|
column.Visible = visible;
|
|
|
|
//Setting a default ContextMenuStrip will allow the columns to handle the
|
|
//Show() event so it is not passed up to the _dataGridView.ContextMenuStrip.
|
|
//This allows the ContextMenuStrip to be shown if right-clicking in the gray
|
|
//background of _dataGridView but not shown if right-clicking inside cells.
|
|
column.ContextMenuStrip = cmsKiller;
|
|
}
|
|
|
|
//We must set DisplayIndex properties in ascending order
|
|
foreach (var itemName in displayIndices.OrderBy(i => i.Value).Select(i => i.Key))
|
|
{
|
|
var column = gridEntryDataGridView.Columns
|
|
.Cast<DataGridViewColumn>()
|
|
.SingleOrDefault(c => c.DataPropertyName == itemName);
|
|
|
|
if (column is null) continue;
|
|
|
|
column.DisplayIndex = displayIndices.GetValueOrDefault(itemName, column.Index);
|
|
}
|
|
|
|
//Remove column is always first;
|
|
removeGVColumn.DisplayIndex = 0;
|
|
removeGVColumn.Visible = false;
|
|
removeGVColumn.ValueType = typeof(bool?);
|
|
removeGVColumn.FalseValue = false;
|
|
removeGVColumn.TrueValue = true;
|
|
removeGVColumn.IndeterminateValue = null;
|
|
}
|
|
|
|
private void HideMenuItem_Click(object? sender, EventArgs e)
|
|
{
|
|
var menuItem = sender as ToolStripMenuItem;
|
|
var propertyName = menuItem?.Tag as string;
|
|
|
|
var column = gridEntryDataGridView.Columns
|
|
.Cast<DataGridViewColumn>()
|
|
.FirstOrDefault(c => c.DataPropertyName == propertyName);
|
|
|
|
if (column != null && menuItem != null && propertyName != null)
|
|
{
|
|
var visible = menuItem.Checked;
|
|
menuItem.Checked = !visible;
|
|
column.Visible = !visible;
|
|
|
|
var config = Configuration.Instance;
|
|
|
|
var dictionary = config.GridColumnsVisibilities;
|
|
dictionary[propertyName] = column.Visible;
|
|
config.GridColumnsVisibilities = dictionary;
|
|
}
|
|
}
|
|
|
|
private void gridEntryDataGridView_ColumnDisplayIndexChanged(object? sender, DataGridViewColumnEventArgs e)
|
|
{
|
|
var config = Configuration.Instance;
|
|
|
|
var dictionary = config.GridColumnsDisplayIndices;
|
|
dictionary[e.Column.DataPropertyName] = e.Column.DisplayIndex;
|
|
config.GridColumnsDisplayIndices = dictionary;
|
|
}
|
|
|
|
private void gridEntryDataGridView_CellToolTipTextNeeded(object? sender, DataGridViewCellToolTipTextNeededEventArgs e)
|
|
{
|
|
if (e.ColumnIndex == descriptionGVColumn.Index)
|
|
e.ToolTipText = "Click to see full description";
|
|
else if (e.ColumnIndex == coverGVColumn.Index)
|
|
e.ToolTipText = "Click to see full size";
|
|
}
|
|
|
|
private void gridEntryDataGridView_ColumnWidthChanged(object? sender, DataGridViewColumnEventArgs e)
|
|
{
|
|
var config = Configuration.Instance;
|
|
|
|
var dictionary = config.GridColumnsWidths;
|
|
dictionary[e.Column.DataPropertyName] = e.Column.Width;
|
|
config.GridColumnsWidths = dictionary;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|