KumquatRoot2 - 本地文件查找工具
刚学习 C++ 不久, 这个小工具也算是对 C++ 的第一次实战演练, 采用 wxWidgets 作为图形库, 仅仅调用了 Windows 的两个 API, 一个是 ShellExecute 函数, 另一个是 WinExec 函数, 理论上稍加改动即可轻松移植到其他平台上。
完成了第一个 C++ 小工具, 心里还是十分高兴的, 因此十分希望能够与大家分享在这次 coding 中遇到的一些问题。
一、 出师不利
由于 C++ 和 wxWidgets 都是刚接触不久, 再加上 wxWidgets 中文资料并不多, 在网上找了好久只找到了一本不是太完整的 wxWidgets 的教程, 内容较少, 所以项目刚开始时就遇到了一些麻烦。
失败的界面布局
一般来说, 一个普通的窗口程序都允许用户对程序的窗口进行大小的调整, 其中窗口中的控件位置以及大小也能根据窗口大小的调整进行自身的调整, 但是由于对 wxWidgets 智能布局的不熟悉, 就决定自己来实现对窗口的智能布局, 一是不想固定了窗口的大小, 因为固定的太小了, 搜索的结果横向显示不完整, 二是想体验下对窗口自定义实现智能布局的复杂度。
通过 EVT_SIZE 事件来进行子窗口控件的相关调整, 但是到后来发现这种做法是十分愚蠢和错误的, 随着控件的不断增多, 需要调整的控件位置、大小也越来越多, 到最后长长的好几屏代码都是来响应对控件的调整的。
最终的解决方案: 查阅官方手册、google it来学习 wxWidgets 的智能布局类, wxSizer, 使用其中的 wxBoxSizer 类来实现对窗口的布局。
wxBoxSizer 是一个允许水平/垂直的布局类, 他仅允许垂直/水平布局, 不过这已经足够了, 一般来说, 任何一个常规界面都可以通过不断的布局嵌套来完成更加复杂的布局。
对于 wxBoxSizer 布局的详细使用, 笔者将会在接下来几天对项目知识点的整理中发布在博客中。
二、全部重写
当代码越写感觉越糟糕的时候, 心情自然是越写越差的, 在 这个月的 4 号终于受不了这种感觉了, 立即将项目从 github 上销毁, 本地将项目文件夹打包到 Backup, 重新在 github 上建立项目, 重新建立工程, 一切重写。有了第一次失败的经验, 写起来自然就顺畅多了, 但还是遇到了几个技术上的问题。
1>. 定时器 wxTimer 和 多线程 wxThread
在找到的这本中文教程上有关多线程编程的建议时有这句话引起了我的注意:"多线程的替代方案 - 如果线程使用的复杂性让你感到气馁,也许你可以尝试一些简单的替代方案,比如使用定时器,空闲时间处理或者两者一起使用. "。
正是由于这句话, 让我足足纠结了一个晚上。不能怪作者, 是我理解错了。
首先来介绍下这个搜索工具的工作原理, 目前的设计是将整个的搜索过程分为两部分, 一是用户无法直接看到的工作部分, 也就是程序背后的文件统计、分析、匹配工作, 另一部分就是用户可以看到的部分, 软件 UI 统计部分, 在搜索过程中, 程序会显示一个模态对话框提示搜索正在进行, 并且不断更新当前一共搜索了多少文件, 有多少个符合条件的。
那么这个过程就可以看做是由两个函数来进行, 一个负责搜索, 另一个负责更新UI, 既然那本书上说 "也许你可以尝试一些简单的替代方案,比如使用定时器", 还真尝试了, 定时器分为一次性定时器(只提醒一次)和常规的间歇提醒类型的定时器, 那么根据这样来说, 这不正好符合要求么, 一次性的定时器用来负责提醒下搜索函数的工作, 间歇型的定时器负责来更新 UI 上的统计数据, 想的确实挺完美的。
当真这样进行实现时问题就来了, 一次性定时器提醒搜索函数后, 搜索函数开始工作, 但是负责更新 UI 的函数并不能做出按照 150 毫秒/次的正常更新统计数据工作, 预设间隔是 150 毫秒, 但是即便等好几十秒也没见到 UI 上的统计数据更新, 直到搜索函数运行结果后这边的统计线程才缓缓来迟进行更新最后的统计数据。显然这样做是错误的, 原因经过一番推理和验证后得出了结果:
wxTimer 启动出来的函数同属于一个线程, 当 搜索函数启动后, 一直陷入查找文件、打开文件、匹配文件的循环中, 不到搜索完毕另一个负责更新UI的函数是无法开始工作的, 界面也不会有响应, 因为正常的事件循环被阻止掉了。
当初就是误认为 wxTimer 设计器会将这些函数放在不同的线程中才决定这么干的, 不过现在看起来也挺好, 起码验证了一个道理: "实践是检验真理的唯一标准"。
2>. 线程的终止
若用户在搜索过程中不要进行搜索了, 需要取消这次任务, 那么必然需要控制下线程使其结束工作, 搜索的过程就是一个 while 循环的过程, 因此退出 while 循环让函数自行结束自然就达到了退出线程的目的, 在这次线程的控制中, 笔者使用的是通过一个控制 bool 型的 thread_stop 变量的值来实现的, 这个值在对话框类中, 初始值为 false, 通过引用传递到搜索线程, 在搜索的 while 循环中:
while( dirItems.GetCount() && ( !threadstop ) ) //当目录队列不为空时且 !threadstop 值为真时执行 { //匹配的过程 }
在 UI 部分, 当用户点击停止时, 就将 threadstop 的值改为 true, 这样 while 的条件变为假, 就不再执行函数体, 搜索线程函数退出, 终止搜索。
线程的暂停与继续相对来说就好实现的多了,
wxThread::Pause() //用来暂停线程
wxThread::Resume() //用来恢复线程
3>. 向窗口发送消息
当搜索完成后, 需要通知 UI 部分说停止更新界面的计时器以及滚动条还有相关按钮的标签文字改变, 例如当搜索完成后, 停止按钮上的文字就由 "停止" 变成了 "完成!", 要完成这个步骤有两种解决方案, 一是将这些窗口的控件以及定时器通过指针/引用传递到线程, 由线程在返回前改变这些控件/定时器的状态, 但这样做实属麻烦。 第二种方案就是当搜索要完成时向对话框的消息队列中插入一个事件, 由对话框的事件绑定来完成搜索结束后的相关处理。
wxPostEvent函数是用来插入消息的函数之一, 他的函数原型如下:
void wxPostEvent(wxEvtHandler *dest, wxEvent& event)
通过函数原型可以看到, 该函数需要一个事件句柄和消息类型, 要解决这个事件句柄只需要将需要接受事件的窗口指针传入进来, 然后 wnd->GetEventHandler() 即可得到该窗口的事件句柄。
对于消息类型, 这里使用的是一个虚拟的按钮消息,
wxCommandEvent event( wxEVT_COMMAND_BUTTON_CLICKED, VIR_BTN_DONE );
实际上这个按钮并不存在, VIR_BTN_DONE 就是这个按钮的 ID, 他是一个自定义的常量, 但是通过 wxPostEvent 将该事件插入到消息队列后, 目标窗口就能接受到来自该 ID 按钮的消息, 即便实际上他是不存在的, 进而完成相关的处理。
//在线程中向目标窗口插入事件 wxCommandEvent event( wxEVT_COMMAND_BUTTON_CLICKED, VIR_BTN_DONE ); wxPostEvent( wnd->GetEventHandler(), event );
三、其他一些琐碎的知识点分享
1>. 取出 wxListCtrl 选中行中的某列元素的文本
unsigned long index = resList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED ); //获取当前选中的行的下标 wxListItem item; item.SetMask(wxLIST_MASK_TEXT); item.SetId(index); item.SetColumn(1); //取出选中行的第二列中的文本, 注: 下标以0开始 resList->GetItem(item); return item.GetText();
2>. 两个 Windows API
①. ShellExecute
ShellExecute 的功能通常被用来运行一个外部的程序, 或者打开一个文件/文件夹等操作, 详细的作用以及用法见百科: ShellExecute
一个通过 ShellExecute 调用与该类型文件相关联的应用程序进行打开的示例:
if( ShellExecute( NULL, _T("open"), getListSelectedPtah(), "", "", 1 ) <= (HINSTANCE)32 ) //返回值小于或等于32时表示失败 wxMessageBox( _T("未找到有效的打开方式!"), _T("打开失败"), wxOK|wxICON_INFORMATION );
②. WinExec 也可以用来运行指定程序, 例如将某个完整路径的文件在文件夹中显示:
WinExec( "explorer /select, " + getListSelectedPtah(), SW_NORMAL );
返回值大于 32 时表示成功。
3>. 文件查找的核心函数, 对文件/文件夹的操作
①. wxWidgets 提供的用来遍历目录的类 wxDir
[译] wxWidgets - wxDir
②. 使用 wxDir 遍历目录的示例
[代码分享] wxWidgets - wxDir 遍历文件
[代码分享] wxWidgets 非递归方式遍历文件
③. wxWidgets 提供的一部分对文件/文件夹操作函数
[译] wxWidgets - File functions - 文件/文件夹函数
凌晨三点了, 就先写到这, 其实内心还有很多这次学习到的知识想要与大家分享, 最近几天笔者会继续整理下本次项目中用到一些知识点较为系统的发表在博客里, 有努力就会有收获, 经过这十多天的努力, 这个小软件终于完成了!
软件名称: KumquatRoot2 - 橘根文件搜索
软件类型: 磁盘辅助
运行平台: Windows
开发语言: C++
图形库: wxWidgets
开发环境: Microsoft Visual C++ 6.0
软件介绍: 按指定规则查找磁盘中的文件。无需安装, 绿色零配置, 支持匹配文件内容、支持正则表达式。
软件截图:
开源协议: GNU GPL v3 ( http://www.gnu.org/licenses/gpl-3.0.html )
项目下载: https://github.com/mrwid/KumquatRoot2
软件下载: http://files.cnblogs.com/mr-wid/KumquatRoot2.zip
使用介绍:
首先通过 "浏览(B).." 按钮选择需要搜索的目录, 然后配置相关的查找条件:
1. "匹配文件名称", 按照文件名进行匹配, 在输入框中输入文件大致名称或正则表达式, 点击 "启用" 复选框后开启该搜索条件;
2. "匹配文件内容", 按文件内容进行匹配, 在输入框中输入文件内容关键词或正则表达式, 点击 "启用" 复选框后开启该搜索条件;
3. "选择匹配模式", 当选择 "普通模糊匹配" 时表示常规的文件查找, 输入框中的内容作为查找关键词; "正则表达式匹配" 表示将输入框中的内容作为正则表达式对文件/文件名进行匹配;
4. "按扩展名过滤", 输入框中输入以空格隔开的文件扩展名, 可以带扩展名的前的点'.'号, 也不可以不带, "过滤以上扩展名"表示不对以上扩展名结尾的文件进行匹配; "仅匹配以上扩展名"表示仅查找以以上扩展名作为文件名结尾的文件。
5. "开始搜索(S)", 按照以上设置的条件对指定目录中的文件进行匹配。
在搜索结果栏中, 可用右键弹出右键菜单对搜索到的文件进行进一步的操作, 右键菜单中的选项包括:
1. 打开文件(O) : 使用与该类型文件相关联的应用程序进行打开;
2. 打开文件所在目录(P) : 将该文件所在的文件夹显示出来, 并将该文件以高亮形式标注。
3. 另存为...(S) : 将文件保存到其他文件夹, 源文件不删除;
4. 删除文件(D) : 将该文件从磁盘中删除;
5. 关于 KumquatRoot2 : 对 KumquatRoot2 的相关介绍。
特别提示:
由于系统的权限设置, 在磁盘中存在一些普通应用程序无法访问的文件夹, 当 KumquatRoot2 尝试搜索到这个权限不足的文件夹时会提示无法访问并跳过, 点击 "Ok" 按钮关闭错误提示对话框。
当使用文件内容进行匹配时, 对于二进制文件, 程序将采取主动跳过的方案, 不会读取完整的内容进行匹配。
其他说明:
KumquatRoot2 是 KumquatRoot1.0 重构版, KumquatRoot1 由 Python 语言进行编写, (详细介绍: http://www.x86pro.com/kumquat/kumquatroot/ ) KumquatRoot2 在 上一版本的功能的基础上使用 C++ 语言进行全部重写, 大大减少了软件体积以及附加文件数量, KumquatRoot2仅有一个独立的 exe 可执行文件, 零配置, 完全绿色、开源。其整个项目托管在 github 上。
如果您对该软件有新的建议和意见, 欢迎留言, wid 会尽最大努力在以后的版本中对软件进行改进和提高。
再长的路, 一步步也能走完; 再短的路, 不迈开双脚也无法到达。
--------------------
wid, 2013.03.11