From ed5cd5295d3b0b3b9f5b3e382ba898dfd296ac8c Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Tue, 3 Mar 2026 17:43:11 +0000 Subject: [PATCH 1/4] Support Multi-file drag & drop and from "open file" --- Dashboard/MainWindow.xaml.cs | 48 ++++++++++++++++++++++++++---------- Lite/MainWindow.xaml.cs | 48 ++++++++++++++++++++++++++---------- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/Dashboard/MainWindow.xaml.cs b/Dashboard/MainWindow.xaml.cs index 2aaf20ad..29d7da98 100644 --- a/Dashboard/MainWindow.xaml.cs +++ b/Dashboard/MainWindow.xaml.cs @@ -1867,18 +1867,25 @@ private TabItem AddNewEmptyPlanSubTab() var dialog = new Microsoft.Win32.OpenFileDialog { Filter = "SQL Plan Files (*.sqlplan)|*.sqlplan|XML Files (*.xml)|*.xml|All Files (*.*)|*.*", - DefaultExt = ".sqlplan" + DefaultExt = ".sqlplan", + Multiselect = true }; if (dialog.ShowDialog() != true) return; - try - { - var xml = System.IO.File.ReadAllText(dialog.FileName); - LoadPlanIntoSubTab(subTab, xml, System.IO.Path.GetFileName(dialog.FileName)); - } - catch (Exception ex) + var isFirst = true; + foreach (var fileName in dialog.FileNames) { - MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", - MessageBoxButton.OK, MessageBoxImage.Error); + try + { + var xml = System.IO.File.ReadAllText(fileName); + var targetTab = isFirst ? subTab : AddNewEmptyPlanSubTab(); + LoadPlanIntoSubTab(targetTab, xml, System.IO.Path.GetFileName(fileName)); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + isFirst = false; } }; @@ -2013,7 +2020,7 @@ private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) + if (files?.Any(IsPlanFile) == true) { e.Effects = DragDropEffects.Copy; e.Handled = true; @@ -2027,9 +2034,24 @@ private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) private void MainWindowPlanViewer_Drop(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; - var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) - LoadMainWindowPlanFromFileIntoActiveTab(files[0]); + var planFiles = (e.Data.GetData(DataFormats.FileDrop) as string[]) + ?.Where(IsPlanFile).ToArray(); + if (planFiles == null || planFiles.Length == 0) return; + LoadMainWindowPlanFromFileIntoActiveTab(planFiles[0]); + for (var i = 1; i < planFiles.Length; i++) + { + var newTab = AddNewEmptyPlanSubTab(); + try + { + var xml = System.IO.File.ReadAllText(planFiles[i]); + LoadPlanIntoSubTab(newTab, xml, System.IO.Path.GetFileName(planFiles[i])); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } } private void MainWindowPlanViewer_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs index beba2b76..ac70c6dc 100644 --- a/Lite/MainWindow.xaml.cs +++ b/Lite/MainWindow.xaml.cs @@ -1630,18 +1630,25 @@ private TabItem AddNewEmptyPlanSubTab() var dialog = new Microsoft.Win32.OpenFileDialog { Filter = "SQL Plan Files (*.sqlplan)|*.sqlplan|XML Files (*.xml)|*.xml|All Files (*.*)|*.*", - DefaultExt = ".sqlplan" + DefaultExt = ".sqlplan", + Multiselect = true }; if (dialog.ShowDialog() != true) return; - try + var isFirst = true; + foreach (var fileName in dialog.FileNames) { - var xml = System.IO.File.ReadAllText(dialog.FileName); - LoadPlanIntoSubTab(subTab, xml, System.IO.Path.GetFileName(dialog.FileName)); - } - catch (Exception ex) - { - MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", - MessageBoxButton.OK, MessageBoxImage.Error); + try + { + var xml = System.IO.File.ReadAllText(fileName); + var targetTab = isFirst ? subTab : AddNewEmptyPlanSubTab(); + LoadPlanIntoSubTab(targetTab, xml, System.IO.Path.GetFileName(fileName)); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + isFirst = false; } }; @@ -1772,7 +1779,7 @@ private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) + if (files?.Any(IsPlanFile) == true) { e.Effects = DragDropEffects.Copy; e.Handled = true; @@ -1786,9 +1793,24 @@ private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) private void MainWindowPlanViewer_Drop(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; - var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) - LoadMainWindowPlanFromFileIntoActiveTab(files[0]); + var planFiles = (e.Data.GetData(DataFormats.FileDrop) as string[]) + ?.Where(IsPlanFile).ToArray(); + if (planFiles == null || planFiles.Length == 0) return; + LoadMainWindowPlanFromFileIntoActiveTab(planFiles[0]); + for (var i = 1; i < planFiles.Length; i++) + { + var newTab = AddNewEmptyPlanSubTab(); + try + { + var xml = System.IO.File.ReadAllText(planFiles[i]); + LoadPlanIntoSubTab(newTab, xml, System.IO.Path.GetFileName(planFiles[i])); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to load plan file:\n{ex.Message}", "Load Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } } private void MainWindowPlanViewer_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) From eddb43198bcd7c98e6d8c31f13e42d313c4ffbfc Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Tue, 3 Mar 2026 17:50:11 +0000 Subject: [PATCH 2/4] Missing Ctrl+V on Lite's per-server Plan Viewer empty state --- Lite/Controls/ServerTab.xaml.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Lite/Controls/ServerTab.xaml.cs b/Lite/Controls/ServerTab.xaml.cs index 87ff451b..9c5a61a5 100644 --- a/Lite/Controls/ServerTab.xaml.cs +++ b/Lite/Controls/ServerTab.xaml.cs @@ -235,6 +235,26 @@ public ServerTab(ServerConnection server, DuckDbInitializer duckDb, CredentialSe /* Initial load is triggered by MainWindow.ConnectToServer calling RefreshData() after collectors finish - no Loaded handler needed */ + + KeyDown += ServerTab_KeyDown; + Focusable = true; + } + + private void ServerTab_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.V && + System.Windows.Input.Keyboard.Modifiers == System.Windows.Input.ModifierKeys.Control && + e.OriginalSource is not System.Windows.Controls.TextBox && + PlanViewerTabItem.IsSelected) + { + var xml = System.Windows.Clipboard.GetText(); + if (!string.IsNullOrWhiteSpace(xml)) + { + e.Handled = true; + OpenPlanTab(xml, "Pasted Plan"); + PlanViewerTabItem.IsSelected = true; + } + } } private void InitializeTimeComboBoxes() From 54a5571a14446cf378e901feacf887816b6d9968 Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Tue, 3 Mar 2026 17:50:49 +0000 Subject: [PATCH 3/4] Minor formatting --- Dashboard/MainWindow.xaml.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Dashboard/MainWindow.xaml.cs b/Dashboard/MainWindow.xaml.cs index 29d7da98..4332475c 100644 --- a/Dashboard/MainWindow.xaml.cs +++ b/Dashboard/MainWindow.xaml.cs @@ -1691,7 +1691,6 @@ private void OpenPlanViewerTab() } _mainPlanTabControl = new TabControl - { Background = System.Windows.Media.Brushes.Transparent, BorderThickness = new Thickness(0) From e7f69cacec2e230ea26814d057f94056f49c59a1 Mon Sep 17 00:00:00 2001 From: ClaudioESSilva Date: Tue, 3 Mar 2026 17:57:09 +0000 Subject: [PATCH 4/4] Clean "OpenMainWindowPlanTab" method - was public but uncalled --- Dashboard/MainWindow.xaml.cs | 23 ----------------------- Lite/MainWindow.xaml.cs | 22 ---------------------- 2 files changed, 45 deletions(-) diff --git a/Dashboard/MainWindow.xaml.cs b/Dashboard/MainWindow.xaml.cs index 4332475c..6b72b1bb 100644 --- a/Dashboard/MainWindow.xaml.cs +++ b/Dashboard/MainWindow.xaml.cs @@ -1991,29 +1991,6 @@ subTab.Header is StackPanel sp && return _mainPlanTabControl.SelectedItem as TabItem; } - public void OpenMainWindowPlanTab(string planXml, string label, string? queryText = null) - { - if (_planViewerTab == null || !ServerTabControl.Items.Contains(_planViewerTab)) - OpenPlanViewerTab(); - - var activeSubTab = GetActivePlanSubTab(); - if (activeSubTab?.Content is Grid g && - g.Children.Count >= 2 && - g.Children[1] is Controls.PlanViewerControl { Visibility: Visibility.Collapsed }) - { - // Active sub-tab is empty — load into it - LoadPlanIntoSubTab(activeSubTab, planXml, label, queryText); - } - else - { - // Active sub-tab already has a plan — open a new one - var newSubTab = AddNewEmptyPlanSubTab(); - LoadPlanIntoSubTab(newSubTab, planXml, label, queryText); - } - - ServerTabControl.SelectedItem = _planViewerTab; - } - private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs index ac70c6dc..62c6eba4 100644 --- a/Lite/MainWindow.xaml.cs +++ b/Lite/MainWindow.xaml.cs @@ -1752,28 +1752,6 @@ subTab.Header is StackPanel sp && return MainWindowPlanTabControl.SelectedItem as TabItem; } - public void OpenMainWindowPlanTab(string planXml, string label, string? queryText = null) - { - EnsurePlanTabControlInitialized(); - MainWindowPlanViewerTab.Visibility = Visibility.Visible; - - var activeSubTab = GetActivePlanSubTab(); - if (activeSubTab?.Content is Grid g && - g.Children.Count >= 2 && - g.Children[1] is Controls.PlanViewerControl { Visibility: Visibility.Collapsed }) - { - LoadPlanIntoSubTab(activeSubTab, planXml, label, queryText); - } - else - { - var newSubTab = AddNewEmptyPlanSubTab(); - LoadPlanIntoSubTab(newSubTab, planXml, label, queryText); - } - - MainWindowPlanViewerTab.IsSelected = true; - ServerTabControl.Visibility = Visibility.Visible; - } - private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop))