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