通过自己指定注册表的方式, 实现了自定义的右键菜单. 直接在文件或者目录上右键就可以执行指定的命令, 很方便. 我自己在工作中经常需要转换文件格式, 从一个数据文件转换为csv格式, 之前是通过一个swing gui工具, 然后把文件拖拽上去, 虽然方便但却不是最方便的, 直接绑定右键菜单的方式就好了很多, 可以不事先启动gui工具, 也不用管工具是在什么地方, 随时随地想转就转. 下面来研究实现:
右键菜单主要是通过改注册表实现的, 但是直接通过注册表文件.reg改注册表方式不够智能, 因为有些路径是写死的, 每次换了地方都需要手动先把.reg里的路径改了再导入, 很不方便, 所以这里通过java操作命令行调用注册表命令动态导入注册表, 优点是可以动态改变路径.
windows操作注册表的命令是reg命令, 具体用法可以在cmd里通过reg /? 或者reg add /?等查询.
用法就是:
reg query "HKEY_CLASSES_ROOT\*\shell\myutil"
reg add "HKEY_CLASSES_ROOT\*\shell\myutil" /v "SubCommands" /d "subcommand.1;subcommand.2" /f
reg add "HKEY_CLASSES_ROOT\*\shell\myutil" /ve /d "subcommand.1;subcommand.2" /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\subcommand.2\command" /ve /d "\"cmd.exe\" /k \"java -cp \"D:\tmp\ContextMenuTest.jar\" work.drqf.Test %1\"" /f
reg query命令没什么问题. 但是reg add要注意:
(1). /ve选项表示名称为默认, 在win7上可以写成/v “” , 但win10这样写会报错, 必须用/ve
(2). 注意key和data要加引号, data中有嵌套引号要记得转义, 简单的转义是上面的例子.
(3). 上面的转义是在cmd中执行时的转义用法, win7只有cmd, 所以没什么问题, 但是win10分cmd和power shell两个命令行工具, 这两个工具的用法不一样, power shell的转义是用两个双引号"“而不是\”, 所以上面的对应的reg add就是:
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\subcommand.2\command" /ve /d """cmd.exe"" /k ""java -cp ""D:\tmp\ContextMenuTest.jar"" work.drqf.Test %1""" /f
所以一定要分清楚是用cmd.exe来执行命令, 还是用powershell来执行. win10直接右键用的是cmd.exe.
这个路径下面是在文件上右键时的命令:
HKEY_CLASSES_ROOT\*
这个路径下面是在目录上右键时的命令:
HKEY_CLASSES_ROOT\Directory
HKEY_CLASSES_ROOT\Directory和HKEY_CLASSES_ROOT\Folder貌似都可以配置文件夹上右键菜单, 不知道有什么区别.
先看文件命令配置, 目录的其实是一样的, 照抄就好.
shell下面添加myutil这一项, myutil里添加两个值, MUIVerb表示的是右键菜单上实际显示出来的名称MyUtil, 如果没有MUIVerb这个值, 则显示的是key的值, 即myutil. SubCommands指定的是二级菜单, 名称是固定的, 必须叫这个名字, 值是各个子命令以分号分隔的值.
其中子命令存储在:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell
所以照葫芦画瓢, 在这里添加两个对应的子命令, 目录层次见下图:
然后文件上右键, 就会出现二级菜单了.
注意: 如果二级菜单不出现, 要看父级菜单myutil下面的那个默认项是否被设了值, 正确的显示结果应该是"(数值未设置)", 而不是空白. 我第一次就是误把这里设了个空白, 结果二级菜单死活不出现. 如果误设了值, 直接在那个默认上面delete就好.
目录的设置方法和文件的配置方法一模一样, 子目录命令不用再配置, 直接引用同样的.
MUIVerb不是必须的, 只有SubCommands也是可以的.
如果不需要二级菜单, 直接点击myutil执行命令的话, 那么不需要SubCommands值, 直接在myutil下追加command项目, 在command默认值上设置data为指定的命令就行了.
注意命令中的"cmd.exe"
引号里前后不要有空格, 写成"cmd.exe "
就执行不了.
好了, 注册表的项目清楚了, 那么通过java代码来实现:
package work.drqf;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.Arrays;
public class Test {
public static void main(String[] args) throws Exception {
// 如果不传参数, 表示首次执行, 就录入注册表信息.
if (args.length == 0 || "".equals(args[0])) {
registerUrlHandler();
System.out.println("-----Register Url Handler success!-----");
} else {
// 通过注册表执行了程序, 这里打印出参数. 可以添加其他自定义逻辑
System.out.println(Arrays.toString(args));
System.out.println("Hello World");
}
}
private static void registerUrlHandler() throws Exception {
String curPath = Paths.get("./").toAbsolutePath().getParent().toString();
addRegister("HKEY_CLASSES_ROOT\\*\\shell\\myutil", "MUIVerb", "MyUtil");
addRegister("HKEY_CLASSES_ROOT\\*\\shell\\myutil", "SubCommands", "subcommand.1;subcommand.2");
addRegister("HKEY_CLASSES_ROOT\\Directory\\shell\\myutil", "SubCommands", "subcommand.1;subcommand.2");
addRegister(
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell\\subcommand.1",
"MUIVerb", "Sub1");
addRegister(
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell\\subcommand.1\\command",
"", "\\\"cmd.exe\\\" /k \\\" echo aaaaa-%1\\\"");
addRegister(
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell\\subcommand.2",
"MUIVerb", "Sub2");
addRegister(
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CommandStore\\shell\\subcommand.2\\command",
"",
"\\\"cmd.exe\\\" /k \\\"java -cp \\\"" + curPath + "\\ContextMenuTest.jar\\\" work.drqf.Test %1\\\"");
}
private static void addRegister(String key, String v, String data) throws Exception {
Runtime runtime = Runtime.getRuntime();
String cmd0 = null;
if ("".equals(v)) {
cmd0 = "reg add \"" + key + "\" /ve /d \"" + data + "\" /f";
} else {
cmd0 = "reg add \"" + key + "\" /v \"" + v + "\" /d \"" + data + "\" /f";
}
System.out.println(cmd0);
Process proc = runtime.exec(cmd0);
proc.waitFor();
System.out.println(readStream(proc.getErrorStream()));
System.out.println(readStream(proc.getInputStream()));
}
private static String readStream(InputStream is) throws IOException {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is));) {
String content = "";
String line = "";
while ((line = br.readLine()) != null) {
content += line;
}
return content;
}
}
}
注意代码里面的转义\\\"
, 由于/d 后面是全部用""引起来的, 所以如果命令里有嵌套引号, 是需要用反斜杠转义的. 更深层次的嵌套好像不用加更多的反斜杠, 最多\\\"
就可以了.
把代码导出为jar包放在D:\tmp
下
再写一个bat文件用于首次调用jar包, 登录注册表信息:
runContextMenuTest.bat
%~d0
cd %~dp0
java -cp .\ContextMenuTest.jar work.drqf.Test %1
pause
前两行是切换到当前目录, 然后是调用java, 最后一行是控制台暂停, 方便查看控制台内容, 正式程序不需要的话可以去掉.
执行bat文件在win10下需要以管理员身份运行. 执行bat会自动导入注册表, 非常方便.
每次程序位置变更了, 手动执行bat重新导入一次就好.
如图, 在文件上右键, 打印出文件名和Hello World内容.
java程序拿到了文件名, 就可以做更多想做的事.