Windows 资源管理器和 Total Commander 中的轻松导航






4.88/5 (12投票s)
Windows资源管理器和Total Commander的便捷导航。
引言
您是否曾想过,在Windows中导航可以更方便?为什么不采用类似于大多数常用IDE中的“导航到类”的技术呢?在这种技术中,相关的文件夹匹配项会在弹出的窗口中显示,并通过预定义的按键组合(例如,这样)触发。
想到这一点后,我编写了一个名为“导航助手”的小工具,如上所示。最新的可执行文件(以及完整的源代码)可在Google Code上获取,它是开源的,并根据MIT许可分发。希望它能对某些人有所帮助。本文的源代码仅包含最棘手的部分,下面将进行解释(主要是由于CodeProject的大小限制)。
为什么需要它
以下是Windows资源管理器和Total Commander的一些缺点,可能会让大多数用户感到沮丧:
- 需要进行大量鼠标点击才能访问深度嵌套的文件夹。
- Windows资源管理器中的搜索速度很慢,即使开启了索引。
- Windows资源管理器搜索的可用性低:需要打开资源管理器,将光标移至搜索框(不在屏幕中央),点击它,输入(最好是完整的)文件夹名称,然后等待。如果您在文件夹名称中输入错误,需要重新进行所有操作。
- Windows资源管理器和Total Commander地址栏提示(按Tab键在地址栏中出现的子文件夹名称)的可用性低。您仍然需要输入完整路径,并为每个子文件夹按Tab键,然后为每个子文件夹搜索正确的提示。如果您期望的是错误的文件夹路径(例如,“Program Files (x32)”而不是“Program Files”),则需要再次执行所有这些操作。
正是出于这些原因,我编写了“导航助手”,其操作等同于大多数常用IDE中的“导航到类”或“导航到文件”功能:JetBrains产品(ReSharper, IDEA, PhpStorm, WebStorm)中的Ctrl-N和Ctrl-Shift-N组合,Eclipse中的Ctrl-Shift-T。
其他优点
除了消除上述缺点外,还获得了以下好处:
- 无需知道确切的文件夹路径
- 无需知道确切的文件夹名称
- 更少的输入,搜索速度快,可用性高
- 每次按键时立即预览搜索结果(因此您可以快速修改搜索查询)
使用“导航助手”还可以附带获得更好的文件夹结构。确实,许多开发人员(尤其熟悉杰出的《代码大全》)都知道7±2法则(大脑工作记忆中最有效的元素数量)。因此,将给定文件夹中的子文件夹数量限制在7个以内,以及限制类中的字段数、方法中的参数数等,都是一个好策略。
不幸的是,这种方法会以不方便的文件夹导航为代价。导航助手将让您不必担心这个缺点。
如何使用
要快速导航到所需文件夹,只需在Windows资源管理器或Total Commander应用程序(或任何其他将暗示启动新的Windows资源管理器实例进行导航的应用程序)中按下预定义的按键组合(默认是Ctrl-Shift-M)。主应用程序窗口将出现。
开始输入目标文件夹名称。
杀手级功能:无需输入完整的文件夹名称;例如,输入“documents and”即可导航到“Documents and Settings”。更重要的是,甚至不必完全输入“documents”这个词,“doc and”也可以(致意,JetBrains!);此外,也不必从头开始输入文件夹名称,“and settings”就足够了。文件夹名称中的Pascal/Camel大小写也得到识别,以方便开发人员:搜索查询“nav assist”将匹配文件夹“NavigationAssistant”。
由于这些操作,将出现一个文件夹匹配列表。
您只需用鼠标点击合适的元素,或使用向上/向下键选择所需的路径,然后按Enter键。
如果您对导航窗口不满意,可以按Escape键快速将其最小化到系统托盘。
如果您在Windows资源管理器或Total Commander之外将程序置于前台,选择一个文件夹匹配项后将启动一个新的Windows资源管理器应用程序(已打开所需文件夹);可以在设置中将默认导航器更改为Total Commander。
选项在Google Code项目主页中有详细介绍。
如何实现
该工具是用Microsoft .NET 3.5和Windows Presentation Foundation编写的;因此,只要安装了 .NET 3.5 就可以运行。
安装程序是使用Inno Setup实现的,我发现它非常方便:它比Visual Studio Installer、Install Shield或WiX要好得多。
- 它是免费且开源的。
- 它是模块化的(意味着它与Microsoft技术脱钩)。
- 它是透明的(只需修改文本脚本,无需进行可视化编程,类似于Visual Studio Installer)。
- 因此,安装程序文件在每次更新时不会重新创建,也不会重新生成数千个GUID,从而避免了版本控制系统的问题。
- 它很灵活(支持Pascal/Delphi脚本);我上次编程是7年前用Delphi,但Inno Setup的优势肯定弥补了这一点。
- 哦,对了,它还能神奇地创建一个同时支持x32和x64版本的单一安装程序。
安装程序比较可以例如在此处找到:此处。
代码中包含一些非琐碎的部分
全局键盘钩子
该应用程序需要监听全局热键,以便在当前活动窗口的任何情况下都能适当地显示自己。.NET本身不提供此功能。我使用了这段代码,尽管也有其他解决方案,只需搜索“global hooks/global hotkeys”即可。
将非活动应用程序带到前台
如果应用程序遇到了预期的按键组合,它就必须将自己呈现为前台窗口。
WPF确实有一个方法Window.Activate
,它在内部使用SetForegroundWindow
WinAPI函数,因此具有其所有限制。更确切地说,如果在调用该方法时应用程序处于非活动状态,焦点将保留在最初活动的窗口中。实际上,这是一种好的行为,可以防止恶意程序将自己带到前台并打断用户体验。但在这款“导航助手”的特定情况下,这不是我们想要的。
我使用了这种方法,尽管存在其他解决方案(这里和这里),但它们在所有情况下都不能正常工作。不幸的是,启发我的链接是Visual Basic 6的,所以下面是有效的C#代码。
//WinApi functions declarations
[DllImport("user32.dll")]
static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd,
IntPtr lpdwProcessId);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach,
uint idAttachTo, bool fAttach);
//The function to bring the WPF window to the foreground
private void ReallySetForegroundWindow(Window window)
{
//Initial attempt
window.Show();
window.Activate();
//Get window handle
IntPtr hWnd = new WindowInteropHelper(window).Handle;
//Check to see if we are on the foreground thread
uint foregroundThreadId =
GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
uint ourThreadId = GetCurrentThreadId();
//If not, attach our thread's 'input' to the foreground thread's
if (foregroundThreadId != ourThreadId)
{
AttachThreadInput(foregroundThreadId, ourThreadId, true);
}
//Bring our window to the foreground
SetForegroundWindow(hWnd);
//If we are attached to our thread, detach it now
if (foregroundThreadId != ourThreadId)
{
AttachThreadInput(foregroundThreadId, ourThreadId, false);
}
//Force our window to redraw
InvalidateRect(hWnd, IntPtr.Zero, true);
}
Windows资源管理器集成
对Windows资源管理器窗口的操作受到了这篇文章的启发,但进行了大幅修改。核心代码如下,并附有详细注释。
//A function to return all windows explorer instances,
//not internet browsers (don't be confused with the class name InternetExplorer).
//InternetExplorer, ShellWindows, ShellWindowsClass
//are classes from SHDocVw namespace of the Interop.SHDocVw assembly.
private List<InternetExplorer> GetAllExplorers()
{
//Get the collection of shell windows
ShellWindows shellWindows = new ShellWindowsClass();
//First filter InternetExplorer instances from shell windows
//(includes Windows Explorers and Internet Explorer browsers).
//Then leave just windows explorers, not internet browsers.
List<InternetExplorer> explorers =
shellWindows.OfType<InternetExplorer>().Where(IsWindowsExplorer).ToList();
return explorers;
}
//Determines whether the InternetExplorer instance
//is actually a windows explorer, not internet browser
private bool IsWindowsExplorer(InternetExplorer ie)
{
string filename = Path.GetFileNameWithoutExtension(ie.FullName).ToLower();
return filename.Equals("explorer");
}
//Changes the current path in the windows explorer window
private void NavigateTo(InternetExplorer windowsExplorer, string path)
{
windowsExplorer.Navigate("file:///" + Path.GetFullPath(path));
}
新建Windows资源管理器实例也需要一些特殊处理。请参考源代码中的WindowsExplorerManager
类。
Total Commander集成
Total Commander的集成是通过命令行参数实现的;最重要的是/O参数,它会将活动路径设置到当前打开的Total Commander实例中,而不是新实例(如果存在)。示例代码如下。
public void NavigateTo(string path, bool openNewCommander,
string totalCommanderFolder, string totalCommanderFileName)
{
//Switches from help:
// /T Opens the passed dir(s) in new tab(s). Now also works
// when Total Commander hasn't been open yet;
// /L= Set path in left window;
// /O If Total Commander is already running, activate it
// and pass the path(s) in the command line to that instance
// (overrides the settings in the configuration dialog to have multiple windows);
// /S Interprets the passed dirs as source/target instead of left/right (for usage with /O).
//We use /S to make total commander set path for the currently active panel and tab.
//A small hack is used: if there are several open total commanders
//and /O option is used, totalcmd.exe /O will open
//a currently topmost visible commander.
string template = openNewCommander
? "/T /L=\"{0}\""
: "/O /S /L=\"{0}\"";
string arguments = string.Format(template, Path.GetFullPath(path));
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.WorkingDirectory = totalCommanderFolder;
processStartInfo.FileName = totalCommanderFileName;
processStartInfo.Arguments = arguments;
Process.Start(processStartInfo);
}
架构
代码中使用了标准的[多层架构](http://www.infoq.com/minibooks/domain-driven-design-quickly)(Avram和Marinescu在《领域驱动设计快速入门》中有精彩概述)。
- UI 层
- 视图
- Presenter层
- Presenter
- Presentation Services
- 视图模型
- View Model Mappers
- Domain Model(又名业务层)
- 应用服务
- Domain Data Objects(纯粹的数据传输对象)
- 没有数据访问层 :-)
起初我尝试使用 Model-View-ViewModel 模式(WPF常用),但后来出于以下原因切换到MVP:
- MVP增加了另一层抽象(Presenter);否则,大量的逻辑将进入ViewModel,而
- 这些逻辑在概念上不属于ViewModel(参见单一职责原则)。
- ViewModel变得过于臃肿。
- 因此,无法使用接口驱动开发来实现View,从而导致
- UI层变得混乱(没有关注点分离、模块化、清晰的接口)。
因此,目前使用MVP,但数据绑定到UI是通过ViewModel实现的,如下所示:
- 或者将ViewModel传递给
IView
接口(方法或属性)。 - 在View内部实例化ViewModel。
限制
如果“导航助手”在上次计算机关机前被手动关闭,或者在上次Windows启动时未启动,那么在“导航助手”启动时无法判断文件夹缓存是否为最新状态;因此,后者将在后台线程中静默更新,并在该过程完成之前使用最后一个缓存版本(可能已过时)。
.NET和WPF可能会相当慢、占用资源,但您必须接受这一点。
结论
我尝试分享一个用于在Windows资源管理器和Total Commander中便捷导航的小工具。希望它能对社区有所帮助。此外,我希望对Shell、Windows资源管理器和Total Commander的集成所做的技术决策概述和代码也能有所帮助。
再次强调,链接包括最新安装程序和Google Code项目(SVN被使用,因为大多数开发人员可能都知道它)。
感谢您的关注!