diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml index d0b241c..1841365 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml @@ -100,8 +100,17 @@ BorderThickness="0" ScrollViewer.HorizontalScrollBarVisibility="Auto"> - + + + + + + + + + + diff --git a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs index 16f4a37..d6fc363 100644 --- a/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs +++ b/src/PlanViewer.App/Controls/QueryStoreGridControl.axaml.cs @@ -209,6 +209,62 @@ private void LoadHighlightedPlan_Click(object? sender, RoutedEventArgs e) PlansSelected?.Invoke(this, new List { row.Plan }); } + // ── Context menu ──────────────────────────────────────────────────────── + + private void ContextMenu_Opening(object? sender, System.ComponentModel.CancelEventArgs e) + { + var row = ResultsGrid.SelectedItem as QueryStoreRow; + var hasRow = row != null; + + CopyQueryIdItem.IsEnabled = hasRow; + CopyPlanIdItem.IsEnabled = hasRow; + CopyQueryHashItem.IsEnabled = hasRow && !string.IsNullOrEmpty(row!.QueryHash); + CopyPlanHashItem.IsEnabled = hasRow && !string.IsNullOrEmpty(row!.QueryPlanHash); + CopyModuleItem.IsEnabled = hasRow && !string.IsNullOrEmpty(row!.ModuleName); + CopyQueryTextItem.IsEnabled = hasRow; + CopyRowItem.IsEnabled = hasRow; + + // Wire click handlers (clear first to avoid stacking) + CopyQueryIdItem.Click -= CopyMenuItem_Click; + CopyPlanIdItem.Click -= CopyMenuItem_Click; + CopyQueryHashItem.Click -= CopyMenuItem_Click; + CopyPlanHashItem.Click -= CopyMenuItem_Click; + CopyModuleItem.Click -= CopyMenuItem_Click; + CopyQueryTextItem.Click -= CopyMenuItem_Click; + CopyRowItem.Click -= CopyMenuItem_Click; + + if (!hasRow) return; + + CopyQueryIdItem.Tag = row!.QueryId.ToString(); + CopyPlanIdItem.Tag = row.PlanId.ToString(); + CopyQueryHashItem.Tag = row.QueryHash; + CopyPlanHashItem.Tag = row.QueryPlanHash; + CopyModuleItem.Tag = row.ModuleName; + CopyQueryTextItem.Tag = row.FullQueryText; + CopyRowItem.Tag = $"{row.QueryId}\t{row.PlanId}\t{row.QueryHash}\t{row.QueryPlanHash}\t{row.ModuleName}\t{row.LastExecutedLocal}\t{row.ExecsDisplay}\t{row.TotalCpuDisplay}\t{row.AvgCpuDisplay}\t{row.TotalDurDisplay}\t{row.AvgDurDisplay}\t{row.TotalReadsDisplay}\t{row.AvgReadsDisplay}\t{row.TotalWritesDisplay}\t{row.AvgWritesDisplay}\t{row.TotalPhysReadsDisplay}\t{row.AvgPhysReadsDisplay}\t{row.TotalMemDisplay}\t{row.AvgMemDisplay}\t{row.FullQueryText}"; + + CopyQueryIdItem.Click += CopyMenuItem_Click; + CopyPlanIdItem.Click += CopyMenuItem_Click; + CopyQueryHashItem.Click += CopyMenuItem_Click; + CopyPlanHashItem.Click += CopyMenuItem_Click; + CopyModuleItem.Click += CopyMenuItem_Click; + CopyQueryTextItem.Click += CopyMenuItem_Click; + CopyRowItem.Click += CopyMenuItem_Click; + } + + private async void CopyMenuItem_Click(object? sender, RoutedEventArgs e) + { + if (sender is MenuItem item && item.Tag is string text) + await SetClipboardTextAsync(text); + } + + private async System.Threading.Tasks.Task SetClipboardTextAsync(string text) + { + var topLevel = Avalonia.Controls.TopLevel.GetTopLevel(this); + if (topLevel?.Clipboard != null) + await topLevel.Clipboard.SetTextAsync(text); + } + // ── Column filter infrastructure ─────────────────────────────────────── private static readonly Dictionary> TextAccessors = new()