From 0cc6d6337a918e3899269ef31d06bd0f99ffba5a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 12 Dec 2022 16:38:47 -0700 Subject: [PATCH 1/5] Add dynamic context menus to main grid --- .../Controls/DataGridTemplateColumnExt.axaml | 7 ++ .../DataGridTemplateColumnExt.axaml.cs | 73 +++++++++++++++++++ .../LibationAvalonia/LibationAvalonia.csproj | 3 + .../Views/ProductsDisplay.axaml | 64 ++++++++-------- .../Views/ProductsDisplay.axaml.cs | 47 +++++++----- 5 files changed, 141 insertions(+), 53 deletions(-) create mode 100644 Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml create mode 100644 Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs diff --git a/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml new file mode 100644 index 00000000..69d12455 --- /dev/null +++ b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml @@ -0,0 +1,7 @@ + + + diff --git a/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs new file mode 100644 index 00000000..f7cece5e --- /dev/null +++ b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs @@ -0,0 +1,73 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using LibationAvalonia.ViewModels; +using System; +using System.Reflection; + +namespace LibationAvalonia.Controls +{ + public class DataGridViewCellContextMenuStripNeededEventArgs + { + private static readonly MethodInfo GetCellValueMethod; + static DataGridViewCellContextMenuStripNeededEventArgs() + { + GetCellValueMethod = typeof(DataGridColumn).GetMethod("GetCellValue", BindingFlags.NonPublic | BindingFlags.Instance); + } + + private static string GetCellValue(DataGridColumn column, object item) + => GetCellValueMethod.Invoke(column, new object[] { item, column.ClipboardContentBinding })?.ToString() ?? ""; + + public string CellClipboardContents => GetCellValue(Column, GridEntry); + public DataGridTemplateColumnExt Column { get; init; } + public GridEntry GridEntry { get; init; } + public ContextMenu ContextMenu { get; init; } + public AvaloniaList ContextMenuItems + => ContextMenu.Items as AvaloniaList; + } + + public partial class DataGridTemplateColumnExt : DataGridTemplateColumn + { + public event EventHandler CellContextMenuStripNeeded; + + private readonly ContextMenu ContextMenu = new(); + private readonly AvaloniaList MenuItems = new(); + + public DataGridTemplateColumnExt() + { + AvaloniaXamlLoader.Load(this); + ContextMenu.Items = MenuItems; + } + + private void Cell_ContextRequested(object sender, ContextRequestedEventArgs e) + { + if (sender is DataGridCell cell && cell.DataContext is GridEntry entry) + { + var args = new DataGridViewCellContextMenuStripNeededEventArgs + { + Column = this, + GridEntry = entry, + ContextMenu = ContextMenu + }; + args.ContextMenuItems.Clear(); + + CellContextMenuStripNeeded?.Invoke(sender, args); + + e.Handled = args.ContextMenuItems.Count == 0; + } + else + e.Handled = true; + } + + protected override IControl GenerateElement(DataGridCell cell, object dataItem) + { + if (cell.ContextMenu is null) + { + cell.ContextRequested += Cell_ContextRequested; + cell.ContextMenu = ContextMenu; + } + + return base.GenerateElement(cell, dataItem); + } + } +} diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj index b31929d4..274e0548 100644 --- a/Source/LibationAvalonia/LibationAvalonia.csproj +++ b/Source/LibationAvalonia/LibationAvalonia.csproj @@ -89,6 +89,9 @@ + + DataGridTemplateColumnExt.axaml + True True diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index 5c3748a3..e2e4d9d5 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -16,10 +16,11 @@ AutoGenerateColumns="False" Items="{Binding GridEntries}" CanUserSortColumns="True" + SelectionMode="Single" CanUserReorderColumns="True"> - + - + - + - + @@ -64,7 +58,7 @@ - + @@ -74,9 +68,9 @@ - + - + @@ -86,9 +80,9 @@ - + - + @@ -98,9 +92,9 @@ - + - + @@ -110,9 +104,9 @@ - + - + @@ -122,9 +116,9 @@ - + - + @@ -134,9 +128,9 @@ - + - + @@ -146,9 +140,9 @@ - + - + @@ -158,9 +152,9 @@ - + - + @@ -170,9 +164,9 @@ - + - + @@ -182,9 +176,9 @@ - + - + @@ -194,9 +188,9 @@ - + - + @@ -209,7 +203,7 @@ - + diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index bd4e7fae..3cac4a9e 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -10,7 +10,7 @@ using LibationAvalonia.Dialogs; using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Interactivity; +using LibationAvalonia.Controls; namespace LibationAvalonia.Views { @@ -53,7 +53,7 @@ namespace LibationAvalonia.Views { column.CustomSortComparer = new RowComparer(column); } - } + } private void RemoveColumn_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) { @@ -71,6 +71,29 @@ namespace LibationAvalonia.Views productsGrid = this.FindControl(nameof(productsGrid)); } + #region Cell Context Menu + + public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs args) + { + if (args.Column.SortMemberPath == "Liberate") + { + + } + else + { + // any non-stop light column + // (except for the Cover column which does not have a context menu) + var menuItem = new MenuItem { Header = "_Copy Cell Contents" }; + + menuItem.Click += async (s, e) + => await Application.Current.Clipboard.SetTextAsync(args.CellClipboardContents); + + args.ContextMenuItems.Add(menuItem); + } + } + + #endregion + #region Column Customizations private void Configure_ColumnCustomization() @@ -96,6 +119,10 @@ namespace LibationAvalonia.Views foreach (var column in productsGrid.Columns) { + //Wire up column context menu + if (column is DataGridTemplateColumnExt tc) + tc.CellContextMenuStripNeeded += ProductsGrid_CellContextMenuStripNeeded; + var itemName = column.SortMemberPath; if (itemName == nameof(GridEntry.Remove)) @@ -185,22 +212,6 @@ namespace LibationAvalonia.Views #region Button Click Handlers - public void ContextMenuItem1_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) - { - var lbe = getBoundEntry(args.Source); - } - public void ContextMenuItem2_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) - { - var lbe = getBoundEntry(args.Source); - } - public void ContextMenuItem3_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) - { - var lbe = getBoundEntry(args.Source); - } - - private static LibraryBookEntry getBoundEntry(IInteractive source) - => (source is IStyledElement se && se.DataContext is LibraryBookEntry lbe ? lbe : null); - public void LiberateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) { var button = args.Source as Button; From e40daecfb871530afb3e090c3759c7781dbc4aa6 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 12 Dec 2022 17:12:08 -0700 Subject: [PATCH 2/5] Remove old static context menu --- Source/LibationAvalonia/Views/ProductsDisplay.axaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index fa6e77d3..e2e4d9d5 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -45,13 +45,6 @@ From bb3854f5120ea6214ab51994b350fd20551bd2d2 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 12 Dec 2022 17:21:41 -0700 Subject: [PATCH 3/5] Finishing touch --- Source/LibationAvalonia/LibationAvalonia.csproj | 3 --- Source/LibationAvalonia/Views/ProductsDisplay.axaml | 1 - 2 files changed, 4 deletions(-) diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj index 274e0548..b31929d4 100644 --- a/Source/LibationAvalonia/LibationAvalonia.csproj +++ b/Source/LibationAvalonia/LibationAvalonia.csproj @@ -89,9 +89,6 @@ - - DataGridTemplateColumnExt.axaml - True True diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index e2e4d9d5..8fda75af 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -16,7 +16,6 @@ AutoGenerateColumns="False" Items="{Binding GridEntries}" CanUserSortColumns="True" - SelectionMode="Single" CanUserReorderColumns="True"> From f39d272e6acc52adee52f7ac2602a864def221e1 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 12 Dec 2022 18:23:12 -0700 Subject: [PATCH 4/5] Make reused ContextMenu static --- .../Controls/DataGridTemplateColumnExt.axaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs index f7cece5e..5eddd4bc 100644 --- a/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs +++ b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs @@ -30,8 +30,8 @@ namespace LibationAvalonia.Controls { public event EventHandler CellContextMenuStripNeeded; - private readonly ContextMenu ContextMenu = new(); - private readonly AvaloniaList MenuItems = new(); + private static readonly ContextMenu ContextMenu = new(); + private static readonly AvaloniaList MenuItems = new(); public DataGridTemplateColumnExt() { From 6fbd90a6b33b15b537f91cb09f44242a2d7e6c03 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 13 Dec 2022 02:42:53 -0700 Subject: [PATCH 5/5] Fix hidden tag --- .../Assets/LibationStyles.xaml | 4 +- .../DataGridTemplateColumnExt.axaml.cs | 8 +- .../LibationAvalonia/ViewModels/GridEntry.cs | 3 +- .../ViewModels/LibraryBookEntry.cs | 2 + .../ViewModels/SeriesEntry.cs | 1 + .../Views/ProductsDisplay.axaml | 101 ++++++++---------- .../Views/ProductsDisplay.axaml.cs | 6 +- 7 files changed, 59 insertions(+), 66 deletions(-) diff --git a/Source/LibationAvalonia/Assets/LibationStyles.xaml b/Source/LibationAvalonia/Assets/LibationStyles.xaml index 02ab0277..7b3ae474 100644 --- a/Source/LibationAvalonia/Assets/LibationStyles.xaml +++ b/Source/LibationAvalonia/Assets/LibationStyles.xaml @@ -1,8 +1,8 @@ - #FFE6FFE6 + #cdffcd - + diff --git a/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs index 5eddd4bc..b46fac89 100644 --- a/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs +++ b/Source/LibationAvalonia/Controls/DataGridTemplateColumnExt.axaml.cs @@ -7,10 +7,10 @@ using System.Reflection; namespace LibationAvalonia.Controls { - public class DataGridViewCellContextMenuStripNeededEventArgs + public class DataGridCellContextMenuStripNeededEventArgs { private static readonly MethodInfo GetCellValueMethod; - static DataGridViewCellContextMenuStripNeededEventArgs() + static DataGridCellContextMenuStripNeededEventArgs() { GetCellValueMethod = typeof(DataGridColumn).GetMethod("GetCellValue", BindingFlags.NonPublic | BindingFlags.Instance); } @@ -28,7 +28,7 @@ namespace LibationAvalonia.Controls public partial class DataGridTemplateColumnExt : DataGridTemplateColumn { - public event EventHandler CellContextMenuStripNeeded; + public event EventHandler CellContextMenuStripNeeded; private static readonly ContextMenu ContextMenu = new(); private static readonly AvaloniaList MenuItems = new(); @@ -43,7 +43,7 @@ namespace LibationAvalonia.Controls { if (sender is DataGridCell cell && cell.DataContext is GridEntry entry) { - var args = new DataGridViewCellContextMenuStripNeededEventArgs + var args = new DataGridCellContextMenuStripNeededEventArgs { Column = this, GridEntry = entry, diff --git a/Source/LibationAvalonia/ViewModels/GridEntry.cs b/Source/LibationAvalonia/ViewModels/GridEntry.cs index 4f055750..ec30d49f 100644 --- a/Source/LibationAvalonia/ViewModels/GridEntry.cs +++ b/Source/LibationAvalonia/ViewModels/GridEntry.cs @@ -51,7 +51,8 @@ namespace LibationAvalonia.ViewModels public abstract bool IsSeries { get; } public abstract bool IsEpisode { get; } public abstract bool IsBook { get; } - public IBrush BackgroundBrush => IsEpisode ? App.SeriesEntryGridBackgroundBrush : null; + public abstract double Opacity { get; } + public IBrush BackgroundBrush => IsEpisode ? App.SeriesEntryGridBackgroundBrush : Brushes.Transparent; #endregion diff --git a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs index 54dc7fb8..5a2423e7 100644 --- a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs +++ b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs @@ -53,6 +53,7 @@ namespace LibationAvalonia.ViewModels public override bool IsSeries => false; public override bool IsEpisode => Parent is not null; public override bool IsBook => Parent is null; + public override double Opacity => Book.UserDefinedItem.Tags.ToLower().Contains("hidden") ? 0.4 : 1; #endregion @@ -99,6 +100,7 @@ namespace LibationAvalonia.ViewModels case nameof(udi.Tags): Book.UserDefinedItem.Tags = udi.Tags; this.RaisePropertyChanged(nameof(BookTags)); + this.RaisePropertyChanged(nameof(Opacity)); break; case nameof(udi.BookStatus): Book.UserDefinedItem.BookStatus = udi.BookStatus; diff --git a/Source/LibationAvalonia/ViewModels/SeriesEntry.cs b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs index bd457171..eb82a99b 100644 --- a/Source/LibationAvalonia/ViewModels/SeriesEntry.cs +++ b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs @@ -50,6 +50,7 @@ namespace LibationAvalonia.ViewModels public override bool IsSeries => true; public override bool IsEpisode => false; public override bool IsBook => false; + public override double Opacity => 1; #endregion diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index 8fda75af..a4a7fbae 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -15,9 +15,22 @@ GridLinesVisibility="All" AutoGenerateColumns="False" Items="{Binding GridEntries}" - CanUserSortColumns="True" + CanUserSortColumns="True" BorderThickness="3" CanUserReorderColumns="True"> + + + + + - @@ -52,7 +65,7 @@ - + @@ -60,10 +73,8 @@ - - - - + + @@ -72,10 +83,8 @@ - - - - + + @@ -84,10 +93,8 @@ - - - - + + @@ -96,10 +103,8 @@ - - - - + + @@ -108,10 +113,8 @@ - - - - + + @@ -120,10 +123,8 @@ - - - - + + @@ -132,10 +133,8 @@ - - - - + + @@ -144,10 +143,8 @@ - - - - + + @@ -156,10 +153,8 @@ - - - - + + @@ -168,10 +163,8 @@ - - - - + + @@ -180,10 +173,8 @@ - - - - + + @@ -192,14 +183,12 @@ - - - + diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index 3cac4a9e..ac57d42b 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -73,7 +73,7 @@ namespace LibationAvalonia.Views #region Cell Context Menu - public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs args) + public void ProductsGrid_CellContextMenuStripNeeded(object sender, DataGridCellContextMenuStripNeededEventArgs args) { if (args.Column.SortMemberPath == "Liberate") { @@ -277,9 +277,9 @@ namespace LibationAvalonia.Views public void Description_Click(object sender, Avalonia.Input.TappedEventArgs args) { - if (sender is TextBlock tblock && tblock.DataContext is GridEntry gEntry) + if (sender is Control tblock && tblock.DataContext is GridEntry gEntry) { - var pt = tblock.Parent.PointToScreen(tblock.Parent.Bounds.TopRight); + var pt = tblock.PointToScreen(tblock.Bounds.TopRight); var displayWindow = new DescriptionDisplayDialog { SpawnLocation = new Point(pt.X, pt.Y),