本帖最后由 苏紫方璇 于 2026-11-7 22:01 编辑
JumpBar v1.0.9 软件介绍
作者:大军哥 · 2026/11
一句话介绍:JumpBar 是一个常驻 Windows 托盘的小工具——在文件的「打开 / 保存 / 另存为 / 选择文件夹」对话框里按一下 Ctrl+G,就把对话框直接切到你刚才在资源管理器里打开的那个文件夹。
一、它解决什么问题
在 Photoshop、Word、聊天软件、上传窗口里挑文件时,常常要先在一层层目录里点半天,才能回到那个「刚才明明已经打开过」的文件夹。
JumpBar 把这一步省掉:你用资源管理器找到目录,回到软件的打开窗口,按一下快捷键(或让它自动完成),地址栏立刻变成同一个目录。它只帮你定位到目录,选哪个文件、点不点保存,仍然由你决定。
二、主要功能
| 功能 |
说明 |
| 全局快捷键跳转 |
默认 Ctrl + G,在文件类对话框里按下即切换到最近文件夹 |
| 自动切换 |
可勾选:文件对话框一出现就自动切过去,连快捷键都不用按 |
| 认得活动标签页 |
Windows 11 资源管理器多标签时,取当前选中的那个标签,不是窗口里的第一个标签 |
| 覆盖多种对话框 |
打开、保存、另存为、选择文件夹、浏览目录等都适用 |
| 托盘常驻 |
启动后不弹窗、不占任务栏,双击托盘图标才打开设置;悬浮提示只显示当前路径 |
| 可自定义 |
快捷键(须含 Ctrl / Alt / Shift / Win 至少一个)、开机自动启动、自动切换开关 |
三、它是怎么工作的
- 后台跟踪资源管理器当前标签页所在的文件夹,只记路径,不扫描文件。
- 在文件对话框里按快捷键时,改写对话框的地址栏完成目录导航。
- 切换过程一次性写入完整路径,没有逐字打字的动画。
- 目录变化时,托盘图标的悬浮提示会显示当前路径。
四、它不会做什么
- 不会替你打开文件、不会替你保存文件、不会替你选中文件。
- 不会修改、复制、上传你的任何文件,也不联网。
- 不会弹广告、不会捆绑安装、不会在后台做别的事。
一句话:只负责「把对话框带到那个文件夹」,到此为止。
五、典型用法
- 修图:在资源管理器里找到客户图片目录 → 回到 PS「打开」→
Ctrl+G,地址栏直接到该目录,不用再从「此电脑」一层层点进去。
- 交单 / 上传:把报关单、图片放到某个文件夹并保持资源管理器打开,在任何软件的上传窗口里按
Ctrl+G,立刻定位到同一目录。
- 批量另存:多个软件连续「另存为」到同一个交付目录,只需按快捷键。
六、运行要求与体积
- 单个 exe,约 150 KB,绿色免安装,复制到固定目录双击即用。
- Windows 7 及以上 + .NET Framework 4.x(Win10 / Win11 自带 4.8)。
- 不需要管理员权限;配置与日志只写在当前用户目录
%APPDATA%\JumpBar。
- 无第三方 DLL、无外部配置、无注册表以外的系统改动。
小提示:程序未购买商业代码签名证书,首次运行 Windows SmartScreen 可能提示「未知发布者」,点「更多信息 → 仍要运行」即可;个别杀毒软件会把「全局键盘钩子」判为风险行为,加入信任区即可,属常见误报。
七、三分钟上手
- 把
JumpBar_v1.0.9.exe 放到一个固定位置,双击运行(托盘出现图标)。
- 用资源管理器打开你常用的文件夹,保持那个标签页选中。
- 在任意软件的「打开 / 保存」窗口里按
Ctrl + G。
- 需要改快捷键、开机启动或自动切换:双击托盘图标进设置。
详细的安装步骤、常见问题与校验值,见同包内的《使用说明_v1.0.9.txt》。
八、小结
功能只做一件,但做得彻底:把「最近打开的文件夹」和「文件对话框」之间那段重复操作抹平。适合每天要在多个软件里反复挑文件、存文件的人。
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace JumpBar
{
/// <summary>
/// 低级键盘钩子:只在「前台是对话框类窗口」时吞掉热键,其它场景完全放行。
///
/// 【硬约束】WH_KEYBOARD_LL 回调超时会被系统静默卸载(LowLevelHooksTimeout)。
/// 因此本回调里只允许:读结构体、GetForegroundWindow、取一次窗口类名、投递工作项。
/// 严禁在此枚举子窗口、跨进程 SendMessage、做文件 I/O 或任何耗时操作。
/// </summary>
internal sealed class KeyboardHook : IDisposable
{
private readonly NativeMethods.LowLevelKeyboardProc _proc;
private readonly Action<IntPtr> _onTrigger;
private IntPtr _hookHandle;
private Hotkey _hotkey;
private bool _disposed;
private long _lastTriggerTicks;
internal KeyboardHook(Hotkey hotkey, Action<IntPtr> onTrigger)
{
if (onTrigger == null)
{
throw new ArgumentNullException("onTrigger");
}
_onTrigger = onTrigger;
_hotkey = hotkey;
_proc = HookProc; // 保持委托引用,防止回调被 GC 回收
IntPtr module = NativeMethods.GetModuleHandle(null);
_hookHandle = NativeMethods.SetWindowsHookEx(
NativeMethods.WH_KEYBOARD_LL, _proc, module, 0);
if (_hookHandle == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "无法安装全局键盘钩子。");
}
DiagLog.Write("hook installed");
}
internal void UpdateHotkey(Hotkey hotkey)
{
_hotkey = hotkey;
}
/// <summary>定时自检:万一钩子被系统卸载,无条件重装一次(自愈)。</summary>
internal void Reinstall()
{
try
{
if (_hookHandle != IntPtr.Zero)
{
NativeMethods.UnhookWindowsHookEx(_hookHandle);
_hookHandle = IntPtr.Zero;
}
IntPtr module = NativeMethods.GetModuleHandle(null);
_hookHandle = NativeMethods.SetWindowsHookEx(
NativeMethods.WH_KEYBOARD_LL, _proc, module, 0);
if (_hookHandle == IntPtr.Zero)
{
DiagLog.Write("hook rearm FAILED");
}
}
catch (Exception ex)
{
DiagLog.Write("hook rearm error: " + ex.Message);
}
}
private IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
try
{
if (nCode >= 0 && IsKeyDown(wParam))
{
NativeMethods.KBDLLHOOKSTRUCT data =
(NativeMethods.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(
lParam, typeof(NativeMethods.KBDLLHOOKSTRUCT));
if (_hotkey.IsValid
&& data.vkCode == (uint)_hotkey.Key
&& ModifiersMatch(_hotkey.Modifiers))
{
IntPtr foreground = NativeMethods.GetForegroundWindow();
string className = NativeMethods.GetWindowClassName(foreground);
if (DialogNavigator.IsCandidateDialog(foreground))
{
// 按住热键会产生键盘自动重复:400ms 内只认第一次,避免多个导航线程抢焦点。
long now = DateTime.UtcNow.Ticks;
long last = Interlocked.Read(ref _lastTriggerTicks);
if (last != 0 && now - last < TimeSpan.TicksPerMillisecond * 400)
{
DiagLog.Write("hotkey repeat ignored");
return new IntPtr(1);
}
Interlocked.Exchange(ref _lastTriggerTicks, now);
DiagLog.Write("hotkey matched, foreground=" + foreground + " class=" + className);
IntPtr captured = foreground;
// 立刻投递到线程池并返回,UI 线程与钩子线程都不做任何重活。
ThreadPool.QueueUserWorkItem(delegate(object state)
{
try
{
_onTrigger(captured);
}
catch (Exception ex)
{
DiagLog.Write("trigger error: " + ex.Message);
}
});
return new IntPtr(1); // 吞掉按键
}
}
}
}
catch (Exception ex)
{
// 钩子回调里抛出托管异常会让系统直接摘掉钩子,必须全兜住。
DiagLog.Write("hook error: " + ex.Message);
}
return NativeMethods.CallNextHookEx(_hookHandle, nCode, wParam, lParam);
}
private static bool IsKeyDown(IntPtr wParam)
{
int message = wParam.ToInt32();
return message == NativeMethods.WM_KEYDOWN || message == NativeMethods.WM_SYSKEYDOWN;
}
private static bool ModifiersMatch(int expected)
{
return IsPressed(NativeMethods.VK_CONTROL) == ((expected & (int)KeyModifiers.Ctrl) != 0)
&& IsPressed(NativeMethods.VK_MENU) == ((expected & (int)KeyModifiers.Alt) != 0)
&& IsPressed(NativeMethods.VK_SHIFT) == ((expected & (int)KeyModifiers.Shift) != 0)
&& (IsPressed(NativeMethods.VK_LWIN) || IsPressed(NativeMethods.VK_RWIN))
== ((expected & (int)KeyModifiers.Win) != 0);
}
private static bool IsPressed(int virtualKey)
{
return (NativeMethods.GetAsyncKeyState(virtualKey) & 0x8000) != 0;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
if (_hookHandle != IntPtr.Zero)
{
NativeMethods.UnhookWindowsHookEx(_hookHandle);
_hookHandle = IntPtr.Zero;
}
DiagLog.Write("hook removed");
}
}
}
因为没在帖子中插入部分关键代码,帖子被删除了。
我也不知道哪些是关键代码,所以,我将整个源代码都上传了。 |