diff --git a/README.md b/README.md
index 671358e..a1b8863 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,81 @@
-
+
Taskbar Groups
-
-
-
-
+
+
+
+
Taskbar groups is a lightweight utility for Windows that lets the users create groups of shortcuts in the taskbar.
- Download & Release Notes
+ Download & Release Notes
-[ ](#table-of-contents)
+# Taskbar Groups – Unofficial Maintained Build
-## 📖 Table of Contents
+This is an unofficial maintained fork of the original Taskbar Groups project by **tjackenpacken**.
-* [➤ Installation](#-how-to-download-taskbar-groups)
-* [➤ Creating your first group](#%EF%B8%8F-creating-your-first-group)
-* [➤ Screen/Window Documentation](#%EF%B8%8F-screenwindow-documentation)
- * [Main Screen](#main-screen-)
- * [Group Creation Screen](#group-creation-screen-)
- * [Extra Notes](#extra-notes-)
-* [➤ Image/Icon Caching](#-imageicon-caching)
-* [➤ Program Shortcuts](#%EF%B8%8F-program-shortcuts)
-* [➤ File/Folder Structure](#-folder-structure-documentation)
-* [➤ License](#-License)
+Original project:
+https://github.com/tjackenpacken/taskbar-groups
- [](#how-to-download-taskbar-groups)
+Maintained fork:
+https://github.com/hummbugg/taskbar-groups
+
+---
+
+## 📦 Download Ready-to-Run Build
+
+Download the latest compiled release here:
+
+https://github.com/hummbugg/taskbar-groups/releases
+
+No Visual Studio is required to use the release ZIP.
+
+---
+
+## ⚠️ Windows Security Notice (Important)
+
+Because this application is distributed as a ZIP downloaded from the internet, Windows may block the files.
+
+### Recommended:
+
+1. Right-click the downloaded ZIP file
+2. Click **Properties**
+3. Check **Unblock**
+4. Click **Apply**
+5. Extract the ZIP
+6. Run `TaskbarGroups.exe`
+
+### If already extracted:
+If you accidently extract the zip file before unblocking the zip, you will have to delete the extracted folder, unblock the zip and extract the zip again.
+Alternately if you are familiar with PowerShell you can run the command below to unblock the files, just change the path "C:\Path\To\TaskbarGroups" to match the folder you extracted TaskbarGroups into.
+
+```powershell
+Get-ChildItem -Path "C:\Path\To\TaskbarGroups" -Recurse -File | Unblock-File
+```
+
+[](#demo-video)
+
+## 🎬 Demo Video
+
+
+
+
+
+
+
+
+ Click the thumbnail above to watch the Taskbar Groups v1.1.0-unofficial demo video on YouTube.
+
-## 🔽 How to download Taskbar groups:
- 1. Download the .zip-file from the latest release (link above)
- 2. Unpack the .zip-file at a desired location
- 3. Run the TaskbarGroups.exe file in the extracted folder
[ ](#creating-your-first-group)
## 🛠️ Creating your first group
@@ -52,14 +88,24 @@
enter image description here5. Left click on the group
6. In the folder that opens up, right click on the highlighted shortcut
7. Select "Pin to taskbar"
+
+ ## Runtime Requirement
+Taskbar Groups targets Microsoft .NET Framework 4.7.2.
+
+Windows 11 normally already includes a newer compatible .NET Framework 4.x runtime, so no separate runtime installation should be needed.
+
+If you are using an older or unsupported version of Windows and the app does not start, install the Microsoft .NET Framework 4.7.2 Runtime:
+
+https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net472-web-installer
+
[](#screenwindow-documentation)
## 🖥️ Screen/Window Documentation
Below will be some documentation for each of the screens with explaining the functionality of each of the components.
#### Main screen [](#main-screen)
-Here is the main group configuration screen. You get here by executing the TaskbarGroups.exe file. Here you can add groups and see what groups you have created.
+Here is the main group configuration screen. You get here by executing the TaskbarGroups.exe file. Here you can add groups and see what groups you have created.
#### Group Creation Screen [](#group-creation)
-
+
Here is the group creation screen. Here you can start customizing and configuring your group. Here is the quick rundown of the features of this window.
**Name the new group** - You can insert any group name (no special characters) that you would like with a maximum character limit of 49 characters in total.
@@ -125,3 +171,40 @@ Here is where all of your shortcuts to activate your group will go. All groups c
This project is licensed under the [MIT License](https://github.com/tjackenpacken/taskbar-groups/blob/master/LICENSE).

+
+## Developer Information
+
+### Supported Operating Systems
+
+Taskbar Groups v1.1.0-unofficial targets Microsoft .NET Framework 4.7.2.
+
+Windows 11 already includes a newer compatible .NET Framework 4.x runtime, so no additional runtime installation is normally required for end users.
+
+The project has been tested primarily on:
+- Windows 11
+- Windows 10
+
+Older unsupported versions of Windows may require manual installation of the .NET Framework 4.7.2 runtime.
+
+### Supported Visual Studio Versions
+
+Because this project remains based on the classic .NET Framework WinForms project system, it can still be opened and edited using multiple generations of Microsoft Visual Studio, including:
+
+- Visual Studio 2017
+- Visual Studio 2019
+- Visual Studio 2022
+- Visual Studio 2026
+
+This was intentionally preserved to maximize compatibility for developers who wish to maintain, enhance, or fork the project without requiring migration to newer SDK-style .NET project formats.
+
+### Project Dependencies
+
+The current release uses the following external libraries:
+
+- ChinhDo.Transactions.FileManager.dll
+- Microsoft.WindowsAPICodePack.dll
+- Microsoft.WindowsAPICodePack.Shell.dll
+
+These dependencies are included in the binary release ZIP.
+
+Future releases may reduce or eliminate some external dependencies in favor of native .NET implementations while preserving compatibility with classic Visual Studio versions and Windows-native deployment.
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..1ffc5e4
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,161 @@
+Taskbar Groups – Unofficial Build (hummbugg)
+Version: v1.1.0-unofficial
+
+Overview
+--------
+Taskbar Groups lets you create custom groups of shortcuts and pin them to the Windows taskbar
+for quick access.
+
+This is an unofficial build maintained by hummbugg, based on the original project by tjackenpacken.
+
+Original Project:
+https://github.com/tjackenpacken/taskbar-groups
+
+Maintained Fork (this build):
+https://github.com/hummbugg/taskbar-groups
+
+
+What’s New in This Version
+-------------------------
+- Fixed .ico image loading issues
+- Fixed multiple-instance behavior (single instance enforced)
+- Fixed New Group dialog (now modal; no hidden duplicates)
+- Improved Release build stability (x64 fix)
+- Added version check against GitHub
+- Added “update available” link when a newer version exists
+- Added “Original Author” attribution in UI
+- Improved dialogs to remember last used folder:
+ - Select Group Icon dialog
+ - Create New Shortcut dialog
+- Improved usability:
+ - ESC closes dialog
+ - “Exit” renamed to “Cancel”
+- Locked window sizes to prevent layout issues
+
+
+Installation
+------------
+1. Extract all files from the ZIP to a folder of your choice.
+2. Run TaskbarGroups.exe
+
+Runtime Requirement
+-------------------
+Taskbar Groups targets Microsoft .NET Framework 4.7.2.
+
+Windows 11 normally already includes a newer compatible .NET Framework 4.x runtime, so no separate runtime installation should be needed.
+
+If you are using an older or unsupported version of Windows and the app does not start, install the Microsoft .NET Framework 4.7.2 Runtime:
+
+https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net472-web-installer
+
+
+IMPORTANT – Windows Security Warning
+-----------------------------------
+If Windows blocks the app because it was downloaded from the internet:
+
+OPTION 1 (Recommended):
+1. Right-click the downloaded ZIP file
+2. Click Properties
+3. Check "Unblock"
+4. Click Apply
+5. Extract the ZIP
+6. Run TaskbarGroups.exe
+
+OPTION 2 (If already extracted):
+Run PowerShell and execute:
+If you accidently extract the zip file before unblocking the zip, you will have to delete the extracted folder, unblock the zip and extract the zip again.
+Alternately if you are familiar with PowerShell you can run the command below to unblock the files, just change the path "C:\Path\To\TaskbarGroups" to match the folder you extracted TaskbarGroups into.
+
+Get-ChildItem -Path "C:\Path\To\TaskbarGroups" -Recurse -File | Unblock-File
+
+Then run TaskbarGroups.exe again.
+
+Demo Video
+-----------
+A demonstration video for Taskbar Groups v1.1.0-unofficial is available on YouTube:
+https://youtu.be/TzaTYnm7Ua8
+
+Usage Tips
+----------
+- Create a new group using "Add Taskbar Group"
+- Add shortcuts and choose an icon
+- Pin the generated shortcut to your taskbar
+- The app remembers the last folders used for selecting icons and shortcuts
+
+
+Version Information
+-------------------
+- Current Version: version of the EXE you are running
+- Latest Version: latest release available on GitHub
+
+If a newer version is available, the Latest Version will appear as a clickable link.
+
+
+Credits
+-------
+Original Author:
+tjackenpacken
+https://github.com/tjackenpacken/taskbar-groups
+
+Maintained and enhanced by:
+hummbugg
+https://github.com/hummbugg/taskbar-groups
+
+
+Disclaimer
+----------
+This is an unofficial build. It is not affiliated with or endorsed by the original author.
+
+Use at your own risk.
+
+
+Support / Issues
+----------------
+Please report issues or suggestions here:
+https://github.com/hummbugg/taskbar-groups/issues
+
+Developer Information
+---------------------
+
+Supported Operating Systems
+---------------------------
+Taskbar Groups v1.1.0-unofficial targets Microsoft .NET Framework 4.7.2.
+
+Windows 11 already includes a newer compatible .NET Framework 4.x runtime,
+so no additional runtime installation is normally required for end users.
+
+The project has been tested primarily on:
+- Windows 11
+- Windows 10
+
+Older unsupported versions of Windows may require manual installation of the
+.NET Framework 4.7.2 runtime.
+
+Supported Visual Studio Versions
+--------------------------------
+Because this project remains based on the classic .NET Framework WinForms
+project system, it can still be opened and edited using multiple generations
+of Microsoft Visual Studio, including:
+
+- Visual Studio 2017
+- Visual Studio 2019
+- Visual Studio 2022
+- Visual Studio 2026
+
+This was intentionally preserved to maximize compatibility for developers
+who wish to maintain, enhance, or fork the project without requiring
+migration to newer SDK-style .NET project formats.
+
+Project Dependencies
+--------------------
+The current release uses the following external libraries:
+
+- ChinhDo.Transactions.FileManager.dll
+- Microsoft.WindowsAPICodePack.dll
+- Microsoft.WindowsAPICodePack.Shell.dll
+
+These dependencies are included in the binary release ZIP.
+
+Future releases may reduce or eliminate some external dependencies in favor
+of native .NET implementations while preserving compatibility with classic
+Visual Studio versions and Windows-native deployment.
diff --git a/create_group.jpg b/create_group.jpg
new file mode 100644
index 0000000..f4d062b
Binary files /dev/null and b/create_group.jpg differ
diff --git a/main/Classes/Category.cs b/main/Classes/Category.cs
index 4dc92b9..6d9f161 100644
--- a/main/Classes/Category.cs
+++ b/main/Classes/Category.cs
@@ -1,4 +1,4 @@
-using client.User_controls;
+using client.User_controls;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -104,9 +104,11 @@ public void CreateConfig(Image groupImage)
// Through shellLink.cs class, pass through into the function information on how to construct the icon
// Needed due to needing to set a unique AppUserModelID so the shortcut applications don't stack on the taskbar with the main application
// Tricks Windows to think they are from different applications even though they are from the same .exe
+ // CHANGED: Use hummbugg AppUserModelID for maintained fork.
ShellLink.InstallShortcut(
Path.GetFullPath(@System.AppDomain.CurrentDomain.FriendlyName),
- "tjackenpacken.taskbarGroup.menu." + this.Name,
+ //"tjackenpacken.taskbarGroup.menu." + this.Name,
+ "hummbugg.taskbarGroup.menu." + this.Name,
path + " shortcut",
Path.GetFullPath(@path),
Path.GetFullPath(path + @"\GroupIcon.ico"),
diff --git a/main/Forms/frmClient.cs b/main/Forms/frmClient.cs
index bbf9d67..1ce41b0 100644
--- a/main/Forms/frmClient.cs
+++ b/main/Forms/frmClient.cs
@@ -1,4 +1,4 @@
-using client.Classes;
+using client.Classes;
using client.User_controls;
using System;
using System.Drawing;
@@ -13,17 +13,77 @@ namespace client.Forms
public partial class frmClient : Form
{
private static readonly HttpClient client = new HttpClient();
+ // ADDED: Link label used to credit the original Taskbar Groups author.
+ private LinkLabel originalAuthorLink;
+
+ // ADDED: Link Label used only when a newer GitHub release is available.
+ private LinkLabel githubVersionUpdateLink;
+
public frmClient()
{
System.Runtime.ProfileOptimization.StartProfile("frmClient.Profile");
InitializeComponent();
+
+ // ADDED: Lock main window size to prevent non-responsive layout issues.
+ this.FormBorderStyle = FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+
+ // ADDED: Add original author credit/link above the issues/bugs section.
+ AddOriginalAuthorLink();
+
this.MaximumSize = new Size(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height);
Reload();
- currentVersion.Text = "v" + System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString();
+ // CHANGED: Store current/local version and latest GitHub version so display can be normalized.
+ string currentAssemblyVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString();
+ string latestGitHubVersion = Task.Run(() => getVersionData()).Result;
+
+ // ADDED: Normalize display text and show Latest Version as a link only when an update is available.
+ UpdateVersionDisplay(currentAssemblyVersion, latestGitHubVersion);
- githubVersion.Text = Task.Run(() => getVersionData()).Result;
}
+
+ // ADDED: Adds an original author credit link above the existing "Have Issues or bugs?" section.
+ private void AddOriginalAuthorLink()
+ {
+ const int addedHeight = 28;
+
+ // ADDED: Keep the bottom of pnlVersionInfo anchored in the same place
+ // while making room above the existing contents.
+ pnlVersionInfo.Top -= addedHeight;
+ pnlVersionInfo.Height += addedHeight;
+
+ // ADDED: Move existing controls down to make room for the original author link.
+ foreach (Control control in pnlVersionInfo.Controls)
+ {
+ control.Top += addedHeight;
+ }
+
+ originalAuthorLink = new LinkLabel();
+
+ originalAuthorLink.Text = "Original Author: tjackenpacken";
+ originalAuthorLink.AutoSize = false;
+ originalAuthorLink.Width = 243;
+ originalAuthorLink.Height = 22;
+ originalAuthorLink.Left = label4.Left;
+ originalAuthorLink.Top = 1;
+ originalAuthorLink.TextAlign = ContentAlignment.MiddleCenter;
+ originalAuthorLink.LinkArea = new LinkArea(0, originalAuthorLink.Text.Length);
+
+ originalAuthorLink.Font = new Font("Segoe UI", 9.75F);
+ originalAuthorLink.LinkColor = Color.White;
+ originalAuthorLink.ActiveLinkColor = Color.FromArgb(120, 170, 255);
+ originalAuthorLink.VisitedLinkColor = Color.White;
+ originalAuthorLink.BackColor = Color.Transparent;
+ originalAuthorLink.Cursor = Cursors.Hand;
+
+ originalAuthorLink.LinkClicked += originalAuthorLink_LinkClicked;
+
+ // ADDED: Add to pnlVersionInfo, which is the actual container for the GitHub/issues section.
+ pnlVersionInfo.Controls.Add(originalAuthorLink);
+ originalAuthorLink.BringToFront();
+ }
+
public void Reload()
{
// flush and reload existing groups
@@ -82,9 +142,12 @@ public void Reset()
private void cmdAddGroup_Click(object sender, EventArgs e)
{
- frmGroup newGroup = new frmGroup(this);
- newGroup.Show();
- newGroup.BringToFront();
+ // CHANGED: Open the New Group form as a modal dialog.
+ // This prevents multiple hidden New Group windows from being created.
+ using (frmGroup newGroup = new frmGroup(this))
+ {
+ newGroup.ShowDialog(this);
+ }
}
private void pnlAddGroup_MouseLeave(object sender, EventArgs e)
@@ -112,20 +175,193 @@ private static async Task getVersionData()
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("User-Agent", "taskbar-groups");
- var res = await client.GetAsync("https://api.github.com/repos/tjackenpacken/taskbar-groups/releases");
+
+ // CHANGED: Point to hummbugg fork instead of original repo
+ var res = await client.GetAsync("https://api.github.com/repos/hummbugg/taskbar-groups/releases");
res.EnsureSuccessStatusCode();
+
string responseBody = await res.Content.ReadAsStringAsync();
JsonArray responseJSON = JsonArray.Parse(responseBody);
- JsonObject jsonObjectData = responseJSON[0].GetObject();
- return jsonObjectData["tag_name"].GetString();
- } catch {return "Not found";}
+ // ADDED: Safely find the latest non-prerelease version
+ foreach (var item in responseJSON)
+ {
+ JsonObject jsonObjectData = item.GetObject();
+
+ // Skip prereleases if present
+ if (!jsonObjectData.GetNamedBoolean("prerelease", false))
+ {
+ return jsonObjectData["tag_name"].GetString();
+ }
+ }
+
+ // ADDED: Fallback if all releases are prerelease (unlikely)
+ if (responseJSON.Count > 0)
+ {
+ return responseJSON[0].GetObject()["tag_name"].GetString();
+ }
+
+ return "Not found";
+ }
+ catch
+ {
+ return "Not found";
+ }
+ }
+
+ // ADDED: Updates Current Version and Latest Version display.
+ // Latest Version becomes a clickable link only when GitHub has a newer version.
+ private void UpdateVersionDisplay(string currentAssemblyVersion, string latestGitHubVersion)
+ {
+ Version currentNumericVersion = ParseNumericVersion(currentAssemblyVersion);
+ Version latestNumericVersion = ParseNumericVersion(latestGitHubVersion);
+
+ bool latestVersionFound = latestNumericVersion > new Version(0, 0, 0, 0);
+ string latestSuffix = latestVersionFound ? GetVersionSuffix(latestGitHubVersion) : "";
+
+ // ADDED: Display current version using GitHub's suffix for visual consistency.
+ currentVersion.Text = BuildDisplayVersion(currentNumericVersion, latestSuffix);
+
+ if (latestVersionFound && currentNumericVersion < latestNumericVersion)
+ {
+ // ADDED: Newer GitHub version exists, so show Latest Version as a hyperlink.
+ ShowLatestVersionAsLink(latestGitHubVersion);
+ }
+ else
+ {
+ // ADDED: No update available, so show Latest Version as plain text.
+ ShowLatestVersionAsPlainText(latestGitHubVersion);
+ }
+ }
+
+ // ADDED: Extracts numeric version from values like "1.1.0.0", "v1.1.0-unofficial", or "v1.1.0".
+ private Version ParseNumericVersion(string versionText)
+ {
+ try
+ {
+ if (string.IsNullOrWhiteSpace(versionText))
+ return new Version(0, 0, 0, 0);
+
+ string cleaned = versionText.Trim();
+
+ if (cleaned.StartsWith("v", StringComparison.OrdinalIgnoreCase))
+ cleaned = cleaned.Substring(1);
+
+ int endIndex = 0;
+
+ while (endIndex < cleaned.Length &&
+ (char.IsDigit(cleaned[endIndex]) || cleaned[endIndex] == '.'))
+ {
+ endIndex++;
+ }
+
+ if (endIndex == 0)
+ return new Version(0, 0, 0, 0);
+
+ string numericPart = cleaned.Substring(0, endIndex).TrimEnd('.');
+
+ return new Version(numericPart);
+ }
+ catch
+ {
+ return new Version(0, 0, 0, 0);
+ }
+ }
+
+ // ADDED: Gets suffix from GitHub tag, such as "-unofficial" or "-official".
+ private string GetVersionSuffix(string latestGitHubVersion)
+ {
+ if (string.IsNullOrWhiteSpace(latestGitHubVersion))
+ return "";
+
+ string cleaned = latestGitHubVersion.Trim();
+
+ if (cleaned.StartsWith("v", StringComparison.OrdinalIgnoreCase))
+ cleaned = cleaned.Substring(1);
+
+ int endIndex = 0;
+
+ while (endIndex < cleaned.Length &&
+ (char.IsDigit(cleaned[endIndex]) || cleaned[endIndex] == '.'))
+ {
+ endIndex++;
+ }
+
+ if (endIndex >= cleaned.Length)
+ return "";
+
+ return cleaned.Substring(endIndex);
+ }
+
+ // ADDED: Builds display version like "v1.1.0-unofficial" from EXE version plus GitHub suffix.
+ private string BuildDisplayVersion(Version version, string suffix)
+ {
+ if (version == null)
+ return "v0.0.0";
+
+ string numericVersion;
+
+ if (version.Build >= 0)
+ numericVersion = $"{version.Major}.{version.Minor}.{version.Build}";
+ else
+ numericVersion = $"{version.Major}.{version.Minor}";
+
+ return "v" + numericVersion + suffix;
+ }
+
+ // ADDED: Shows Latest Version as plain non-clickable text.
+ private void ShowLatestVersionAsPlainText(string latestGitHubVersion)
+ {
+ if (githubVersionUpdateLink != null)
+ githubVersionUpdateLink.Visible = false;
+
+ githubVersion.Visible = true;
+ githubVersion.Text = latestGitHubVersion;
+ }
+
+ // ADDED: Shows Latest Version as a clickable hyperlink when an update is available.
+ private void ShowLatestVersionAsLink(string latestGitHubVersion)
+ {
+ githubVersion.Visible = false;
+
+ if (githubVersionUpdateLink == null)
+ {
+ githubVersionUpdateLink = new LinkLabel();
+
+ githubVersionUpdateLink.AutoSize = true;
+ githubVersionUpdateLink.Font = githubVersion.Font;
+ githubVersionUpdateLink.BackColor = Color.Transparent;
+ githubVersionUpdateLink.LinkColor = Color.White;
+ githubVersionUpdateLink.ActiveLinkColor = Color.FromArgb(120, 170, 255);
+ githubVersionUpdateLink.VisitedLinkColor = Color.White;
+ githubVersionUpdateLink.Location = githubVersion.Location;
+ githubVersionUpdateLink.LinkClicked += githubVersionUpdateLink_LinkClicked;
+
+ pnlVersionInfo.Controls.Add(githubVersionUpdateLink);
+ githubVersionUpdateLink.BringToFront();
+ }
+
+ githubVersionUpdateLink.Text = latestGitHubVersion;
+ githubVersionUpdateLink.Visible = true;
+ }
+
+ // ADDED: Opens hummbugg release page when Latest Version indicates an update is available.
+ private void githubVersionUpdateLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+ {
+ System.Diagnostics.Process.Start("https://github.com/hummbugg/taskbar-groups/releases");
+ }
+
+ // ADDED: Opens the original Taskbar Groups project page for attribution/reference.
+ private void originalAuthorLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+ {
+ System.Diagnostics.Process.Start("https://github.com/tjackenpacken/taskbar-groups");
}
private void githubLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
- System.Diagnostics.Process.Start("https://github.com/tjackenpacken/taskbar-groups/releases");
+ // CHANGED: Point to hummbugg releases page
+ System.Diagnostics.Process.Start("https://github.com/hummbugg/taskbar-groups/releases");
}
private void frmClient_Resize(object sender, EventArgs e)
diff --git a/main/Forms/frmGroup.Designer.cs b/main/Forms/frmGroup.Designer.cs
index ad82445..a77d90c 100644
--- a/main/Forms/frmGroup.Designer.cs
+++ b/main/Forms/frmGroup.Designer.cs
@@ -1,4 +1,4 @@
-namespace client.Forms
+namespace client.Forms
{
partial class frmGroup
{
@@ -170,7 +170,7 @@ private void InitializeComponent()
this.cmdExit.Name = "cmdExit";
this.cmdExit.Size = new System.Drawing.Size(129, 30);
this.cmdExit.TabIndex = 35;
- this.cmdExit.Text = "Exit";
+ this.cmdExit.Text = "Cancel";
this.cmdExit.UseVisualStyleBackColor = false;
this.cmdExit.Click += new System.EventHandler(this.cmdExit_Click);
//
@@ -735,4 +735,4 @@ private void InitializeComponent()
private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label7;
}
-}
\ No newline at end of file
+}
diff --git a/main/Forms/frmGroup.cs b/main/Forms/frmGroup.cs
index be43763..607f869 100644
--- a/main/Forms/frmGroup.cs
+++ b/main/Forms/frmGroup.cs
@@ -1,4 +1,4 @@
-using ChinhDo.Transactions;
+using ChinhDo.Transactions;
using client.Classes;
using client.User_controls;
using IWshRuntimeLibrary;
@@ -11,7 +11,9 @@
using System.Transactions;
using System.Windows.Forms;
using Microsoft.WindowsAPICodePack.Shell;
-using Microsoft.WindowsAPICodePack.Dialogs;
+using Microsoft.WindowsAPICodePack.Dialogs;
+using System.Runtime.InteropServices; // ADDED: Used for ExtractIconEx indexed icon extraction.
+
namespace client.Forms
{
@@ -31,7 +33,39 @@ public partial class frmGroup : Form
private List shortcutChanged = new List();
+ // ADDED: File used to persist last selected icon directory across sessions.
+ private static string LastIconDirectoryFile
+ {
+ get
+ {
+ return Path.Combine(MainPath.path, "config", "last_icon_directory.txt");
+ }
+ }
+ // ADDED: File used to persist last selected shortcut directory across sessions.
+ private static string LastShortcutDirectoryFile
+ {
+ get
+ {
+ return Path.Combine(MainPath.path, "config", "last_shortcut_directory.txt");
+ }
+ }
+
+ // ADDED: Extracts icons by index from EXE/DLL/ICO resources.
+ // Needed for shortcuts whose IconLocation includes an index, such as "SomeIcons.exe,1".
+ [DllImport("shell32.dll", CharSet = CharSet.Auto)]
+ private static extern uint ExtractIconEx(
+ string szFileName,
+ int nIconIndex,
+ IntPtr[] phiconLarge,
+ IntPtr[] phiconSmall,
+ uint nIcons
+ );
+
+ // ADDED: Releases icon handles returned by ExtractIconEx.
+ [DllImport("user32.dll", SetLastError = true)]
+ private static extern bool DestroyIcon(IntPtr hIcon);
+
//--------------------------------------
// CTOR AND LOAD
//--------------------------------------
@@ -43,6 +77,15 @@ public frmGroup(frmClient client)
System.Runtime.ProfileOptimization.StartProfile("frmGroup.Profile");
InitializeComponent();
+ // ADDED: Allow ESC key to close the dialog like a standard modal.
+ this.CancelButton = cmdExit;
+
+ // ADDED: Set dialog to fixed size for consistent modal behavior.
+ this.FormBorderStyle = FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ // OPTIONAL: Prevent resizing via dragging edges completely.
+ this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
// Setting default category properties
newExt = imageExt.Concat(specialImageExt).ToArray();
@@ -155,7 +198,8 @@ private void pnlAddShortcut_Click(object sender, EventArgs e)
OpenFileDialog openFileDialog = new OpenFileDialog // ask user to select exe file
{
- InitialDirectory = @"C:\ProgramData\Microsoft\Windows\Start Menu\Programs",
+ // CHANGED: Use last-used shortcut directory instead of always defaulting to Start Menu\Programs.
+ InitialDirectory = GetLastShortcutDirectory(),
Title = "Create New Shortcut",
CheckFileExists = true,
CheckPathExists = true,
@@ -169,6 +213,12 @@ private void pnlAddShortcut_Click(object sender, EventArgs e)
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
+ // ADDED: Persist the last used shortcut directory for future dialog opens.
+ if (openFileDialog.FileNames.Length > 0)
+ {
+ SaveLastShortcutDirectory(openFileDialog.FileNames[0]);
+ }
+
foreach (String file in openFileDialog.FileNames)
{
addShortcut(file);
@@ -313,7 +363,8 @@ private void cmdAddGroupIcon_Click(object sender, EventArgs e)
OpenFileDialog openFileDialog = new OpenFileDialog // ask user to select img as group icon
{
- InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
+ // CHANGED: Use last-used directory instead of always defaulting to Pictures.
+ InitialDirectory = GetLastIconDirectory(),
Title = "Select Group Icon",
CheckFileExists = true,
CheckPathExists = true,
@@ -331,6 +382,10 @@ private void cmdAddGroupIcon_Click(object sender, EventArgs e)
String imageExtension = Path.GetExtension(openFileDialog.FileName).ToLower();
handleIcon(openFileDialog.FileName, imageExtension);
+
+ // ADDED: Persist the last used directory for future dialog opens.
+ SaveLastIconDirectory(openFileDialog.FileName);
+
}
}
@@ -352,15 +407,23 @@ private void pnlDragDropImg(object sender, DragEventArgs e)
private void handleIcon(String file, String imageExtension)
{
- // Checks if the files being added/dropped are an .exe or .lnk in which tye icons need to be extracted/processed
+ // Checks if the files being added/dropped are an .exe or .lnk in which type icons need to be extracted/processed
if (specialImageExt.Contains(imageExtension))
{
if (imageExtension == ".lnk")
{
cmdAddGroupIcon.BackgroundImage = handleLnkExt(file);
}
+ // ADDED: Load the actual .ico image selected by the user.
+ // Icon.ExtractAssociatedIcon(file) returns the Windows file-type icon for .ico files,
+ // which can display as a generic document/text icon instead of the icon contents.
+ else if (imageExtension == ".ico")
+ {
+ cmdAddGroupIcon.BackgroundImage = new Bitmap(file);
+ }
else
{
+ //exe
cmdAddGroupIcon.BackgroundImage = Icon.ExtractAssociatedIcon(file).ToBitmap();
}
}
@@ -371,27 +434,161 @@ private void handleIcon(String file, String imageExtension)
lblAddGroupIcon.Text = "Change group icon";
}
+
+ // ADDED: Gets the last folder used for selecting group icons.
+ private string GetLastIconDirectory()
+ {
+ try
+ {
+ if (System.IO.File.Exists(LastIconDirectoryFile))
+ {
+ string savedPath = System.IO.File.ReadAllText(LastIconDirectoryFile).Trim();
+
+ if (Directory.Exists(savedPath))
+ return savedPath;
+ }
+ }
+ catch
+ {
+ // Ignore errors and fall back to default
+ }
+
+ return Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
+ }
+
+ // ADDED: Saves the last folder used for selecting group icons.
+ private void SaveLastIconDirectory(string filePath)
+ {
+ try
+ {
+ string directory = Path.GetDirectoryName(filePath);
+
+ if (Directory.Exists(directory))
+ System.IO.File.WriteAllText(LastIconDirectoryFile, directory);
+ }
+ catch
+ {
+ // Ignore save errors (non-critical)
+ }
+ }
+
+ // ADDED: Gets the last folder used for selecting program shortcuts.
+ private string GetLastShortcutDirectory()
+ {
+ try
+ {
+ if (System.IO.File.Exists(LastShortcutDirectoryFile))
+ {
+ string savedPath = System.IO.File.ReadAllText(LastShortcutDirectoryFile).Trim();
+
+ if (Directory.Exists(savedPath))
+ return savedPath;
+ }
+ }
+ catch
+ {
+ // Ignore errors and fall back to default
+ }
+
+ return @"C:\ProgramData\Microsoft\Windows\Start Menu\Programs";
+ }
+
+ // ADDED: Saves the last folder used for selecting program shortcuts.
+ private void SaveLastShortcutDirectory(string filePath)
+ {
+ try
+ {
+ string directory = Path.GetDirectoryName(filePath);
+
+ if (Directory.Exists(directory))
+ System.IO.File.WriteAllText(LastShortcutDirectoryFile, directory);
+ }
+ catch
+ {
+ // Ignore save errors (non-critical)
+ }
+ }
+
+ // ADDED: Extracts an icon from a file using an explicit icon index.
+ // This fixes shortcuts like Camtasia where IconLocation points to "CamtasiaIcons.exe,1".
+ private static Bitmap ExtractIconByIndex(string iconPath, int iconIndex)
+ {
+ IntPtr[] largeIcons = new IntPtr[1];
+ IntPtr[] smallIcons = new IntPtr[1];
+
+ uint extractedCount = ExtractIconEx(iconPath, iconIndex, largeIcons, smallIcons, 1);
+
+ try
+ {
+ if (extractedCount > 0 && largeIcons[0] != IntPtr.Zero)
+ {
+ using (Icon icon = (Icon)Icon.FromHandle(largeIcons[0]).Clone())
+ {
+ return icon.ToBitmap();
+ }
+ }
+
+ if (extractedCount > 0 && smallIcons[0] != IntPtr.Zero)
+ {
+ using (Icon icon = (Icon)Icon.FromHandle(smallIcons[0]).Clone())
+ {
+ return icon.ToBitmap();
+ }
+ }
+
+ return null;
+ }
+ finally
+ {
+ if (largeIcons[0] != IntPtr.Zero)
+ DestroyIcon(largeIcons[0]);
+
+ if (smallIcons[0] != IntPtr.Zero)
+ DestroyIcon(smallIcons[0]);
+ }
+ }
+
// Handle returning images of icon files (.lnk)
public static Bitmap handleLnkExt(String file)
{
IWshShortcut lnkIcon = (IWshShortcut)new WshShell().CreateShortcut(file);
String[] icLocation = lnkIcon.IconLocation.Split(',');
+
+ // ADDED: Resolve shortcut IconLocation path and index separately.
+ // Some shortcuts store the correct icon in an indexed resource file, e.g. "CamtasiaIcons.exe,1".
+ string iconPath = "";
+ int iconIndex = 0;
+
+ if (icLocation.Length > 0)
+ iconPath = Environment.ExpandEnvironmentVariables(icLocation[0]);
+
+ if (icLocation.Length > 1)
+ int.TryParse(icLocation[1], out iconIndex);
+
// Check if iconLocation exists to get an .ico from; if not then take the image from the .exe it is referring to
// Checks for link iconLocations as those are used by some applications
- if (icLocation[0] != "" && !lnkIcon.IconLocation.Contains("http"))
+ if (iconPath != "" && !lnkIcon.IconLocation.Contains("http") && System.IO.File.Exists(iconPath))
{
- return Icon.ExtractAssociatedIcon(Path.GetFullPath(Environment.ExpandEnvironmentVariables(icLocation[0]))).ToBitmap();
+ // CHANGED: Extract icon using IconLocation index instead of ignoring the index.
+ // Fixes shortcuts such as Camtasia where the desired icon is not index 0.
+ Bitmap indexedIcon = ExtractIconByIndex(Path.GetFullPath(iconPath), iconIndex);
+
+ if (indexedIcon != null)
+ return indexedIcon;
+
+ // ADDED: Fallback to original behavior if indexed extraction fails.
+ return Icon.ExtractAssociatedIcon(Path.GetFullPath(iconPath)).ToBitmap();
}
else if (icLocation[0] == "" && lnkIcon.TargetPath == "")
{
return handleWindowsApp.getWindowsAppIcon(file);
- } else
+ }
+ else
{
return Icon.ExtractAssociatedIcon(Path.GetFullPath(Environment.ExpandEnvironmentVariables(lnkIcon.TargetPath))).ToBitmap();
}
}
-
public static String handleExtName(String file)
{
string fileName = Path.GetFileName(file);
@@ -468,9 +665,9 @@ private Boolean checkExtensions(DragEventArgs e, String[] exts)
// Exit editor
private void cmdExit_Click(object sender, EventArgs e)
{
- this.Hide();
- this.Dispose();
- Client.Reload(); //flush and reload category panels
+ // CHANGED: Modal Cancel should simply close the dialog.
+ // Do not Hide/Dispose/Reload here because ShowDialog() and the using block handle cleanup.
+ this.Close();
}
// Save group
diff --git a/main/Forms/frmMain.Designer.cs b/main/Forms/frmMain.Designer.cs
index a8c4812..acd431c 100644
--- a/main/Forms/frmMain.Designer.cs
+++ b/main/Forms/frmMain.Designer.cs
@@ -1,4 +1,4 @@
-namespace client
+namespace client
{
partial class frmMain
{
@@ -28,6 +28,7 @@ protected override void Dispose(bool disposing)
///
private void InitializeComponent()
{
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmMain));
this.SuspendLayout();
//
// frmMain
@@ -37,8 +38,11 @@ private void InitializeComponent()
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(33)))), ((int)(((byte)(33)))));
this.ClientSize = new System.Drawing.Size(120, 0);
this.DoubleBuffered = true;
+ this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.KeyPreview = true;
this.Name = "frmMain";
+ this.ShowIcon = false;
+ this.ShowInTaskbar = false;
this.Text = "Group";
this.Deactivate += new System.EventHandler(this.frmMain_Deactivate);
this.Load += new System.EventHandler(this.frmMain_Load);
diff --git a/main/Forms/frmMain.cs b/main/Forms/frmMain.cs
index 19d67ba..d5e2754 100644
--- a/main/Forms/frmMain.cs
+++ b/main/Forms/frmMain.cs
@@ -1,4 +1,4 @@
-using client.Classes;
+using client.Classes;
using client.User_controls;
using System;
using System.Collections.Generic;
@@ -7,6 +7,8 @@
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
+using System.Runtime.InteropServices; // ADDED: Used to bring existing window to the front.
+using System.Threading; // ADDED: Used for single-instance mutex.
namespace client
{
@@ -342,6 +344,7 @@ public void OpenFile(string arguments, string path, string workingDirec)
proc.Arguments = arguments;
proc.FileName = path;
proc.WorkingDirectory = workingDirec;
+ proc.UseShellExecute = true; // Required for launching .lnk and shell-based apps (e.g., Office, UWP); fixes shortcuts not opening
/*
proc.EnableRaisingEvents = false;
diff --git a/main/Properties/AssemblyInfo.cs b/main/Properties/AssemblyInfo.cs
index 26b671a..1e895f3 100644
--- a/main/Properties/AssemblyInfo.cs
+++ b/main/Properties/AssemblyInfo.cs
@@ -1,4 +1,4 @@
-using System.Resources;
+using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -33,6 +33,6 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.2.0.0")]
-[assembly: AssemblyFileVersion("0.2.0.0")]
+[assembly: AssemblyVersion("1.1.0.0")]
+[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: NeutralResourcesLanguage("en")]
diff --git a/main/User controls/ucShortcut.cs b/main/User controls/ucShortcut.cs
index d3273b4..7432317 100644
--- a/main/User controls/ucShortcut.cs
+++ b/main/User controls/ucShortcut.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
@@ -19,6 +19,10 @@ public partial class ucShortcut : UserControl
public ProgramShortcut Psc { get; set; }
public frmMain MotherForm { get; set; }
public Category ThisCategory { get; set; }
+
+ // ADDED: Tooltip used to show the shortcut/app name when hovering over the icon.
+ private ToolTip shortcutToolTip = new ToolTip();
+
public ucShortcut()
{
InitializeComponent();
@@ -30,21 +34,33 @@ private void ucShortcut_Load(object sender, EventArgs e)
this.BringToFront();
this.BackColor = MotherForm.BackColor;
picIcon.BackgroundImage = ThisCategory.loadImageCache(Psc); // Use the local icon cache for the file specified as the icon image
+
+ // ADDED: Show a hover tooltip so identical icons can be distinguished.
+ string displayName = !string.IsNullOrWhiteSpace(Psc.name)
+ ? Psc.name
+ : Path.GetFileNameWithoutExtension(Psc.FilePath);
+
+ // ADDED: Attach tooltip to both the UserControl and the actual PictureBox.
+ // The mouse is usually over picIcon, so setting only "this" may not be enough.
+ shortcutToolTip.SetToolTip(this, displayName);
+ shortcutToolTip.SetToolTip(picIcon, displayName);
}
public void ucShortcut_Click(object sender, EventArgs e)
{
if (Psc.isWindowsApp)
{
- Process p = new Process() {StartInfo = new ProcessStartInfo() { UseShellExecute = true, FileName = $@"shell:appsFolder\{Psc.FilePath}" }};
+ Process p = new Process() { StartInfo = new ProcessStartInfo() { UseShellExecute = true, FileName = $@"shell:appsFolder\{Psc.FilePath}" } };
p.Start();
- } else
+ }
+ else
{
- if(Path.GetExtension(Psc.FilePath).ToLower() == ".lnk" && Psc.FilePath == MainPath.exeString)
+ if (Path.GetExtension(Psc.FilePath).ToLower() == ".lnk" && Psc.FilePath == MainPath.exeString)
{
MotherForm.OpenFile(Psc.Arguments, Psc.FilePath, MainPath.path);
- } else
+ }
+ else
{
MotherForm.OpenFile(Psc.Arguments, Psc.FilePath, Psc.WorkingDirectory);
}
diff --git a/main/client.cs b/main/client.cs
index 36903ce..2547515 100644
--- a/main/client.cs
+++ b/main/client.cs
@@ -1,10 +1,11 @@
-using client.Forms;
+using client.Classes;
+using client.Forms;
using System;
+using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
+using System.Threading;
using System.Windows.Forms;
-using client.Classes;
-using System.Diagnostics;
namespace client
{
@@ -19,9 +20,19 @@ static class client
// Define functions to set AppUserModelID
[DllImport("shell32.dll", SetLastError = true)]
static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
-
- [STAThread]
+ // ADDED: Win32 calls used to restore and focus the existing TaskbarGroups window.
+ [DllImport("user32.dll")]
+ private static extern bool SetForegroundWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
+
+ // ADDED: Restores minimized windows.
+ private const int SW_RESTORE = 9;
+
+
+ [STAThread]
static void Main()
{
// Use existing methods to obtain cursor already imported as to not import any extra functions
@@ -70,14 +81,48 @@ static void Main()
{
// Sets the AppUserModelID to tjackenpacken.taskbarGroup.menu.groupName
// Distinguishes each shortcut process from one another to prevent them from stacking with the main application
- SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.menu."+ arguments[1]);
+ //SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.menu."+ arguments[1]);
+
+ // CHANGED: Use hummbugg AppUserModelID for maintained fork.
+ SetCurrentProcessExplicitAppUserModelID("hummbugg.taskbarGroup.menu." + arguments[1]);
+
Application.Run(new frmMain(arguments[1], cursorX, cursorY));
- } else
+
+
+ }
+ else
{
- // See comment above
- SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.main");
- Application.Run(new frmClient());
+ // ADDED: Prevent multiple main TaskbarGroups configuration windows from running.
+ // This is only applied to the main app window, not the taskbar drawer/group windows.
+ bool createdNew;
+ using (Mutex singleInstanceMutex = new Mutex(true, "TaskbarGroups_Main_SingleInstance_Mutex", out createdNew))
+ {
+ if (!createdNew)
+ {
+ // ADDED: If the main app is already running, bring an existing TaskbarGroups window forward.
+ Process currentProcess = Process.GetCurrentProcess();
+ Process[] runningProcesses = Process.GetProcessesByName(currentProcess.ProcessName);
+
+ foreach (Process process in runningProcesses)
+ {
+ if (process.Id != currentProcess.Id && process.MainWindowHandle != IntPtr.Zero)
+ {
+ ShowWindowAsync(process.MainWindowHandle, SW_RESTORE);
+ SetForegroundWindow(process.MainWindowHandle);
+ break;
+ }
+ }
+
+ return;
+ }
+
+ // See comment above
+ //SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.main");
+ // CHANGED: Use hummbugg AppUserModelID for maintained fork.
+ SetCurrentProcessExplicitAppUserModelID("hummbugg.taskbarGroup.main");
+ Application.Run(new frmClient());
+ }
}
}
}
diff --git a/main/client.csproj b/main/client.csproj
index 137d4c9..890d995 100644
--- a/main/client.csproj
+++ b/main/client.csproj
@@ -1,4 +1,4 @@
-
+
@@ -13,6 +13,8 @@
true
true
false
+
+
publish\
true
Disk
@@ -27,8 +29,6 @@
1.0.0.%2a
false
true
-
-
AnyCPU
@@ -42,7 +42,7 @@
false
- AnyCPU
+ x64
pdbonly
true
bin\Release\
@@ -253,4 +253,4 @@
Microsoft.WindowsAPICodePack.xml
-
\ No newline at end of file
+
diff --git a/taskbar_groups.jpg b/taskbar_groups.jpg
new file mode 100644
index 0000000..567f443
Binary files /dev/null and b/taskbar_groups.jpg differ
diff --git a/taskbar_groups_video_thumbnail.jpg b/taskbar_groups_video_thumbnail.jpg
new file mode 100644
index 0000000..289b1c2
Binary files /dev/null and b/taskbar_groups_video_thumbnail.jpg differ