@@ -209,6 +209,62 @@ private void LoadHighlightedPlan_Click(object? sender, RoutedEventArgs e)
209209 PlansSelected ? . Invoke ( this , new List < QueryStorePlan > { row . Plan } ) ;
210210 }
211211
212+ // ── Context menu ────────────────────────────────────────────────────────
213+
214+ private void ContextMenu_Opening ( object ? sender , System . ComponentModel . CancelEventArgs e )
215+ {
216+ var row = ResultsGrid . SelectedItem as QueryStoreRow ;
217+ var hasRow = row != null ;
218+
219+ CopyQueryIdItem . IsEnabled = hasRow ;
220+ CopyPlanIdItem . IsEnabled = hasRow ;
221+ CopyQueryHashItem . IsEnabled = hasRow && ! string . IsNullOrEmpty ( row ! . QueryHash ) ;
222+ CopyPlanHashItem . IsEnabled = hasRow && ! string . IsNullOrEmpty ( row ! . QueryPlanHash ) ;
223+ CopyModuleItem . IsEnabled = hasRow && ! string . IsNullOrEmpty ( row ! . ModuleName ) ;
224+ CopyQueryTextItem . IsEnabled = hasRow ;
225+ CopyRowItem . IsEnabled = hasRow ;
226+
227+ // Wire click handlers (clear first to avoid stacking)
228+ CopyQueryIdItem . Click -= CopyMenuItem_Click ;
229+ CopyPlanIdItem . Click -= CopyMenuItem_Click ;
230+ CopyQueryHashItem . Click -= CopyMenuItem_Click ;
231+ CopyPlanHashItem . Click -= CopyMenuItem_Click ;
232+ CopyModuleItem . Click -= CopyMenuItem_Click ;
233+ CopyQueryTextItem . Click -= CopyMenuItem_Click ;
234+ CopyRowItem . Click -= CopyMenuItem_Click ;
235+
236+ if ( ! hasRow ) return ;
237+
238+ CopyQueryIdItem . Tag = row ! . QueryId . ToString ( ) ;
239+ CopyPlanIdItem . Tag = row . PlanId . ToString ( ) ;
240+ CopyQueryHashItem . Tag = row . QueryHash ;
241+ CopyPlanHashItem . Tag = row . QueryPlanHash ;
242+ CopyModuleItem . Tag = row . ModuleName ;
243+ CopyQueryTextItem . Tag = row . FullQueryText ;
244+ 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 } ";
245+
246+ CopyQueryIdItem . Click += CopyMenuItem_Click ;
247+ CopyPlanIdItem . Click += CopyMenuItem_Click ;
248+ CopyQueryHashItem . Click += CopyMenuItem_Click ;
249+ CopyPlanHashItem . Click += CopyMenuItem_Click ;
250+ CopyModuleItem . Click += CopyMenuItem_Click ;
251+ CopyQueryTextItem . Click += CopyMenuItem_Click ;
252+ CopyRowItem . Click += CopyMenuItem_Click ;
253+ }
254+
255+ private async void CopyMenuItem_Click ( object ? sender , RoutedEventArgs e )
256+ {
257+ if ( sender is MenuItem item && item . Tag is string text )
258+ await SetClipboardTextAsync ( text ) ;
259+ }
260+
261+ private async System . Threading . Tasks . Task SetClipboardTextAsync ( string text )
262+ {
263+ var topLevel = Avalonia . Controls . TopLevel . GetTopLevel ( this ) ;
264+ if ( topLevel ? . Clipboard != null )
265+ await topLevel . Clipboard . SetTextAsync ( text ) ;
266+ }
267+
212268 // ── Column filter infrastructure ───────────────────────────────────────
213269
214270 private static readonly Dictionary < string , Func < QueryStoreRow , string > > TextAccessors = new ( )
0 commit comments