最近想实现类似于在软件中点击工具栏的Help,软件会自动根据文件的后缀来打开软件的手册或帮助文档的功能,比如:软件的手册是Manual.pdf,那么单击Help中对应的项,则Manual.pdf文件会被电脑上默认的pdf文件阅读器(e.g. Foxit Reader)打开以供User浏览。那么问题可以转化为: 如何程序实现根据文件后缀来判断调用电脑上的默认程序来打开文件呢?其实也就是模拟了鼠标的双击事件。
关键词: 默认程序 打开文件 鼠标双击事件模拟 文件后缀 外部程序调用 注册表,
注: 以下涉及的代码均是在Qt5.10实现
1、对于系统配置好或者在环境变量配置好的程序(e.g., notepad.exe, mspain.exe),可以直接调用你已知的程序来打开文件,比如你想打开author.txt文件,而你知道这个文件可以notepad.exe程序打开,于是你可以直接以下的语句打开:
QProcess myProcess; // QProcess是Qt里面用来调用外部程序的类,记得 #include
myProcess.start("notepad.exe F:\\author.txt"); // 我把author.txt文件放在F盘了
但是现在大部分人安装软件都不会默认安装,更不会配置什么环境变量,比如像我的Foxit Reader就是安装在D:\Program Files\Foxit Reader下面的,那么按上面的方式则没法调用Foxit Reader来打开pdf文件:
myProcess.start("FoxitReader.exe F:\\author.pdf”);
这个时候失败的原因主要是因为程序不知道FoxitReader.exe放在哪个目录下,既然知道原因,那把目录加上就可以了:
myProcess.start("D:\\Program\" \"Files\\Foxit\" \"Reader\\FoxitReader.exe F:\\author.pdf"); // 注意路径包含空格的话需要转换一下
很显然,这种方法是极其不灵活的,对于不同的文件类型,我需要不停的改程序名称和安装路径。
2、我们知道,当一个文件可以被多种程序(比如pdf文件可以被Foxit Reader和Adobe Acrobat Reader打开)打开查看时,我们是可以设置默认的打开程序,那么这个时候我就会在想,这种设置是不是会被保存在系统的某个文件里面?鼠标双击文件时,电脑是不是也是根据文件后缀在某个系统文件中找到跟这种文件后缀相关的信息(比如,对应的打开程序,打开程序的安装位置等等),然后根据获得的信息来打开文件的呢?这个时候很自然的让我想到了注册表,于是在网上搜索,果然找到跟这个问题密切相关的文章(https://blog.csdn.net/qq2399431200/article/details/17957033), 下面我简单说说我的理解。
1) 文件拓展名 -> 对应程序启动路径的映射关系存放在HKEY_CLASS_ROOT根键中。键我理解为索引关键词,就如我们的身份证号码。
2)打开注册表(WIN+R -> 输入regedit -> Enter),定位到HKEY_CLASS_ROOT根键,你会发现其下有很多的键,每个键对应一个像文件夹的图标,所以你可以把注册表当成一个文件系统,通过单击鼠标来找到你想要的。比如找后缀为.pdf相关的启动程序:
① 在HKEY_CLASS_ROOT中找到.pdf这个键,点击,右侧会显示与之关联的数据,截图如下:
这个时候默认这个键所对应的数据FoxitReader.Document(你的可能不是这个值)是下一步定位的键
②根据①找到的键(FoxitReader.Document),继续在HKEY_CLASS_ROOT下找,找到后展开,进一步找FoxitReader.Document下的shell,展开shell再找open,展开open点击command,你会发现右侧与之对应的数据,其默认所对应的是"D:\PROGRAM FILES\FOXIT READER\FOXITREADER.EXE" "%1",我推测这一串字符就是shell下用Foxit Reader打开文件的命令,而后面的%1是占位符,其意思是在你双击pdf文件时,%1被替换成你所单击的文件名,然后执行命令打开文件。这过程的截图如下:
在程序中是没有鼠标操作的,只能通过上面说的键值在注册表中地位,找到相应的路径。在Qt中操作注册表的类是QSettings类,找到打开.pdf后缀的默认程序路径以及打开文件的代码段如下:
QString suffix=".pdf";
QString filename="author";
QString suffixKey;
QString exePath;
QSettings sufKeySet("HKEY_CLASSES_ROOT\\"+suffix,QSettings::NativeFormat);
suffixKey=sufKeySet.value(".").toString(); // suffixKey的内容为FoxitReader.Document
QSettings exePathSet("HKEY_CLASSES_ROOT\\"+suffixKey+"\\shell\\open\\command",QSettings::NativeFormat);
exePath=exePathSet.value(".").toString(); // exePath的内容为 "D:\PROGRAM FILES\FOXIT READER\FOXITREADER.EXE" "%1"
// 下面需要对提取出来的exePath需要进行处理,因为里面包含有空格、引号、%等
exePath=exePath.left(exePath.lastIndexOf(QString(".exe"),-1,Qt::CaseInsensitive)+4);
// exePath的内容为 "D:\PROGRAM FILES\FOXIT READER\FOXITREADER.EXE
if(exePath.at(0)=="\"")
exePath.remove(0,1); // 去掉前面的引号
exePath.replace(" ","\" \""); // 处理空格,这里也提醒大家安装或存文件的文件夹最后不要带空格
exePath.append(" "+filename+suffix);
QProcess *myProcess=new QProcess;
myProcess->start(exePath); // 至此,文件应该能成功打开了
由于整个过程对于不同的文件后缀都是一样,其他的查找关键字也是固定的,所以其可以用于不同的文件后缀的文件打开过程,相对方法1有了很大的改进。但是这个过程过于繁琐,需要找程序路径,找到后还需要处理字符段,无法保证对于其他的文件后缀的字符段处理过程也是这样。既然那么多的软件都有打开帮助文档的功能,那么就应该会有更安全的方式,到底是什么样的呢?
3、多亏我大师兄的提醒与指导,他说第2中方法更像是运行程序计算的调用,而根据文件后缀用电脑默认程序打开文件浏览,这个类似模拟鼠标的双击事件,Windows下应该会提供相应的API。也就这样的,大师兄给我指了一条路。经过在网上查找,找到了密切相关的一篇文章(http://blog.51cto.com/hhuayuan/1122469),有了这篇文章,感觉世界都敞亮了,一行代码就实现上面的操作:
ShellExecuteW(NULL,QString("open").toStdWString().c_str(),QString("F:\\author.pdf").toStdWString().c_str(),NULL,NULL,SW_SHOW); // 打开F盘下的author.pdf文件, 你想打开什么文件,直接替换就可以,就是别忘了路径
本人水平有限,有谬误之处还请谅解并指出,如果有其他的想法也欢迎交流。