From 992ac9a7b5ffc3d3b613da73a4de370ffb5c0aaf Mon Sep 17 00:00:00 2001 From: Ray Berezowski <6616212+rberezowski@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:24:25 -0400 Subject: [PATCH] Add open containing folder action --- MainWindow.xaml.cs | 27 ++++++++++++++++++++ README.md | 1 + Services/LauncherController.cs | 46 +++++++++++++++++++++++++++++++++- TaskbarLauncher.csproj | 4 +-- Views/LauncherPopup.xaml.cs | 26 ++++++++++++++++++- docs/INSTALL.md | 2 ++ 6 files changed, 102 insertions(+), 4 deletions(-) diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 58a794b..451496b 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -96,6 +96,13 @@ public partial class MainWindow : Window }; disableMenuItem.Click += ToggleDisabled_Click; + var openContainingFolderMenuItem = new System.Windows.Controls.MenuItem + { + Header = "Open Containing Folder", + IsEnabled = CanOpenContainingFolder(item) + }; + openContainingFolderMenuItem.Click += OpenContainingFolder_Click; + var duplicateMenuItem = new System.Windows.Controls.MenuItem { Header = "Duplicate Item" }; duplicateMenuItem.Click += DuplicateItem_Click; @@ -107,6 +114,7 @@ public partial class MainWindow : Window }; treeItem.ContextMenu.Items.Add(pinMenuItem); treeItem.ContextMenu.Items.Add(disableMenuItem); + treeItem.ContextMenu.Items.Add(openContainingFolderMenuItem); treeItem.ContextMenu.Items.Add(duplicateMenuItem); treeItem.PreviewMouseRightButtonDown += (_, e) => { @@ -867,6 +875,25 @@ public partial class MainWindow : Window : $"Enabled {itemToSelect.Title}. Click Save to keep it."; } + private void OpenContainingFolder_Click(object sender, RoutedEventArgs e) + { + if (selectedItem is null) + { + return; + } + + ApplyCurrentItem(); + if (!LauncherController.OpenContainingFolder(selectedItem, out string? error) && !string.IsNullOrWhiteSpace(error)) + { + MessageBox.Show(error, "Taskbar Launcher"); + } + } + + private static bool CanOpenContainingFolder(LauncherItem item) + { + return item.Type is LauncherItemType.App or LauncherItemType.File && !string.IsNullOrWhiteSpace(item.Target); + } + private void DuplicateItem_Click(object sender, RoutedEventArgs e) { if (controller is null || selectedItem is null) diff --git a/README.md b/README.md index 9c0507d..b43f9eb 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Download the compiled Windows release from: - Right-click launcher entries to edit them in Settings - Right-click launcher entries to pin or unpin them from the top - Right-click launcher entries or Settings items to temporarily disable them +- Right-click app/file entries to open their containing folder - Right-click item duplication in Settings - Right-click items in Settings to pin them to the top of their menu - Per-item icon and display mode diff --git a/Services/LauncherController.cs b/Services/LauncherController.cs index 7810161..cbcfd69 100644 --- a/Services/LauncherController.cs +++ b/Services/LauncherController.cs @@ -72,7 +72,7 @@ public sealed class LauncherController : IDisposable return; } - popup = new LauncherPopup(Config, LaunchItem, OpenAllItems, ShowSettings, ShowSettings); + popup = new LauncherPopup(Config, LaunchItem, OpenAllItems, ShowSettings, ShowSettings, OpenContainingFolderFromPopup); popup.Closed += (_, _) => popup = null; popup.ShowNearTaskbar(); } @@ -221,6 +221,50 @@ public sealed class LauncherController : IDisposable } } + private void OpenContainingFolderFromPopup(LauncherItem item) + { + if (!OpenContainingFolder(item, out string? error) && !string.IsNullOrWhiteSpace(error)) + { + System.Windows.MessageBox.Show(error, "Taskbar Launcher"); + } + } + + public static bool OpenContainingFolder(LauncherItem item, out string? error) + { + error = null; + + if (item.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(item.Target)) + { + return false; + } + + string target = Environment.ExpandEnvironmentVariables(item.Target); + string? folderPath = Directory.Exists(target) + ? target + : Path.GetDirectoryName(target); + + if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath)) + { + error = $"{item.Title}: Containing folder was not found."; + return false; + } + + try + { + Process.Start(new ProcessStartInfo + { + FileName = folderPath, + UseShellExecute = true + }); + return true; + } + catch (Exception ex) + { + error = $"{item.Title}: {ex.Message}"; + return false; + } + } + private static bool TryLaunchItem(LauncherItem item, out string? error) { error = null; diff --git a/TaskbarLauncher.csproj b/TaskbarLauncher.csproj index 5f59469..9cf2d89 100644 --- a/TaskbarLauncher.csproj +++ b/TaskbarLauncher.csproj @@ -12,8 +12,8 @@ Ray Berezowski Assets\AppIcon.ico 1.0.1 - 1.0.1.8 - 1.0.1.8 + 1.0.1.9 + 1.0.1.9 1.0.1 diff --git a/Views/LauncherPopup.xaml.cs b/Views/LauncherPopup.xaml.cs index 0e4056d..6505b68 100644 --- a/Views/LauncherPopup.xaml.cs +++ b/Views/LauncherPopup.xaml.cs @@ -20,8 +20,9 @@ public partial class LauncherPopup : Window private readonly Action openAllItems; private readonly Action showSettings; private readonly Action editItem; + private readonly Action openContainingFolder; - public LauncherPopup(LauncherConfig config, Action launchItem, Action openAllItems, Action showSettings, Action editItem) + public LauncherPopup(LauncherConfig config, Action launchItem, Action openAllItems, Action showSettings, Action editItem, Action openContainingFolder) { InitializeComponent(); this.config = config; @@ -29,6 +30,7 @@ public partial class LauncherPopup : Window this.openAllItems = openAllItems; this.showSettings = showSettings; this.editItem = editItem; + this.openContainingFolder = openContainingFolder; BuildItems(); SizeChanged += (_, _) => QueueReposition(); } @@ -210,6 +212,7 @@ public partial class LauncherPopup : Window System.Windows.Controls.ContextMenu contextMenu = new(); contextMenu.Items.Add(CreatePinMenuItem(item)); contextMenu.Items.Add(CreateDisableMenuItem(item)); + contextMenu.Items.Add(CreateOpenContainingFolderMenuItem(item)); contextMenu.Items.Add(CreateEditMenuItem(item)); return contextMenu; } @@ -250,6 +253,22 @@ public partial class LauncherPopup : Window return disableMenuItem; } + private System.Windows.Controls.MenuItem CreateOpenContainingFolderMenuItem(LauncherItem item) + { + System.Windows.Controls.MenuItem openFolderMenuItem = new() + { + Header = "Open Containing Folder", + IsEnabled = CanOpenContainingFolder(item) + }; + openFolderMenuItem.Click += (_, e) => + { + openContainingFolder(item); + e.Handled = true; + }; + + return openFolderMenuItem; + } + private System.Windows.Controls.MenuItem CreateEditMenuItem(LauncherItem item) { System.Windows.Controls.MenuItem editMenuItem = new() @@ -271,6 +290,11 @@ public partial class LauncherPopup : Window return item.Type != LauncherItemType.Menu && !item.IsDisabled && !string.IsNullOrWhiteSpace(item.Target); } + private static bool CanOpenContainingFolder(LauncherItem item) + { + return item.Type is LauncherItemType.App or LauncherItemType.File && !string.IsNullOrWhiteSpace(item.Target); + } + private static IEnumerable GetDisplayItems(IEnumerable items) { return items diff --git a/docs/INSTALL.md b/docs/INSTALL.md index f663cf3..ae63b9f 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -22,6 +22,7 @@ Install the **.NET Desktop Runtime** for **Windows x64**. 5. Right-click a launcher entry and choose **Edit in Settings** to jump to that item. 6. Right-click a launcher entry and choose **Pin to Top** or **Unpin from Top**. 7. Right-click a launcher entry and choose **Disable Item** to hide it until it is enabled again from Settings. +8. Right-click an app or file entry and choose **Open Containing Folder**. Recently launched entries appear in the launcher popup under **Recent**. @@ -39,6 +40,7 @@ From Settings you can: - Right-click an item and choose **Duplicate Item** to copy it - Right-click an item and choose **Pin to Top** to show it first in its menu - Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it +- Right-click an app or file item and choose **Open Containing Folder** - Set per-item icon sizes - Back up and restore `config.json` - Enable Start with Windows