子类化GetOpenFileName/GetSaveFileName, 以及钩子函数OFNHookProc的使用的简要说明
昨天, 群里面有一个人问起: 要怎么让"文件打开对话框"居中显示, 有人说子类. 而我告诉他的方法是用钩子函数OFNHookProc, 不知道这是不是所谓的子类?
相信看了我今天这篇文章以后, 要解决居中显示的问题就是小菜一碟啦~
这个东西也并不是我今天才用, 很久以前做的串口调试助手(Com Monitor)上面也用到了这个功能.
下面来看一张被挂钩了的GetOpenFileName的效果(来自QQ影音):
可以看到, "打开"对话框的右上角被QQ影音添加了一个按钮, 用来管理常用文件夹, 这个按键放在这里是最适合不过了~
下面看看我将要说明的代码实现的功能:
同样可以看到, 我在文件类型下面, 增设了一栏, 叫做"Test".
不过她是怎么实现的,下面简单介绍.
来看看GetOpenFileName(为简单书写,以后不再写出GetSaveFileName)的参数OPENFILENAME中的某些成员:
- typedef struct tagOFN {
- ...
- DWORD Flags;
- ...
- LPARAM lCustData;
- LPOFNHOOKPROC lpfnHook;
- LPCTSTR lpTemplateName;
- ...
- } OPENFILENAME, *LPOPENFILENAME;
DWORD Flags:
要实现钩子效果,必须使能 OFN_EXPLORER + OFN_ENABLEHOOK
要像我上面那样做的话, 还可以加上一个 OFN_ENABLETEMPLATE 标志, 该标志将程序中的一个对话框模板作为"打开"的一个子窗口.
LPARAM lCustData:
这个是传递给OFNHookProc钩子过程的初始化参数,钩子函数收到的初始化对话框消息就是通常的 WM_INITDIALOG, 其中的消息参数 LPARAM 就是
lCustData, 此时可用来进一步初始化对话框, 通过你的自定义初始化参数.
LPCTSTR lpTemplateName:
这个是所谓的对话框模板, 其实就是一个对话框资源而已, "打开"将其作为其窗体的部分显示出来,用 MAKEINTRESOURCE 转换成 LPCTSTR.
LPOFNHOOKPROC lpfnHook:
这里需要设定一个函数指针, 就是我们的子类化窗口消息处理过程.
下面来看看OFNHookProc:
原型:UINT_PTR CALLBACK OFNHookProc(HWND hdlg,UINT uiMsg,WPARAM wParam,LPARAM lParam);
这看起来和常规的消息过程一模一样, 确实是这样, 不过需要注意的地方是:
hdlg 是我们的子对话框的句柄, 并不是"打开"对话框的句柄, 我加的那些控件, 就是我的子窗口, 而我又作为"打开"的子窗口!
其实, 我们要处理的消息并不多, 比如, 我处理了 WM_SIZE,WM_INITDIALOG,WM_NOTIFY, ...
下面又来说说这几个消息的处理:
WM_INITDIALOG:
参数:
1.这个消息是"打开"在初始化的时候发送的, 这时候我们需要做的就是初始化我们自己的对话框数据, 比如我上面在ComboBox中添加了几条字符串
2.还有一个初始化参数, 来自 lParam, 通过 lCustData 传送而来, 通过强制转换转换成我们需要的类型
3.由于我们的模板也是作为子窗口的, 所以, 这里可以在函数域定义一个全局的父窗口句柄变量, 并在这里初始化, 以便后续使用
返回:
WM_INITDIALOG 需要返回 0 以表示我们的初始化过程初始化成功, 不然函数调用失败!
WM_NOTIFY:
参数:来自控件的WM_NOTIFY消息可能会很多, 不过, 我们只需要处理其中需要处理的
1.CDN_FILEOK
这个消息来自于当我们点击"打开"按钮时, 这时候, 我们可以判断用户的选择是否合理并返回一个值告诉父窗口.
当你认为用户的选择合理时, 可以返回 0 来关闭对话框, 如果返回 非0, 对话框将不会被关闭!
2.CDN_SHAREVIOLATION
对此消息不熟, 不多作介绍( 如果哪位熟悉, 请帮忙补充到 评论 栏, 感谢 )
返回:由于我们处理的对话框消息, 所以, 消息的返回不能靠简单地return解决, 而是使用函数:SetWindowLong/SetWindowLongPtr
1.SetWindowLong(..., DWL_MSGRESULT, ...);
2.SetDlgMsgResult(...);
上面两种方法设置的是真正的返回值, 而对话框过程的返回值:
0 - 对话框在你处理该消息后继续处理当前消息
1 - 对话框不再处理该消息
至于要怎么设置返回, 可以参看我提供的代码
WM_SIZE:
参数:见MSDN, 不多说, 因为用不到
处理过程:
我所有的控件对齐都是在这里处理, 说起简单也简单, 说起复杂, 还是有点! (要是有个图就能轻易说明问题啦~)
首先看一下窗口继承情况:
第1层:"打开"对话框
/ \
第2层:原有的子窗口控件 + 我们的对话框模板
\
第3层: 我们的对话框模板的子窗口控件
正是因为如此, 所以处理控件坐标才会变得那么复杂(不过是通用的, 可以写成一个宏或函数来搞定, 如果控件较多的话)
还有一点:对话框模板被放到"打开"上面的坐标, 我没有找到资料明确说明, 我用GetWindowRect取得的很不规则(当然,还是矩形),也就是说:模板左边距不为零, ...
好吧, 说说我上面(严格)对齐ComboBox的过程(上面的(文件过滤)叫ComboA, 下面的(我的)叫ComboB), 上面显示文件名的叫 ComboC:
1.取得 ComboA 相对于"打开"的坐标,同时能获得长宽: 通过 GetWindowRect
要怎么取得 ComboA 的句柄? 毕竟 ComboA 又不是我们的对话框, 我们没办法知道其标识符ID. 不过还好, M$ 在它的
头文件里面告诉我们了它们的标识符ID(头文件是dlgs.h, 被包含于Windows.h), 其中有如下:
chx1 只读 CheckBox
cmb1 显示 文件过滤 的 ComboBox
stc2 cmb1 左边的标签
cmb2 显示当前驱动器或文件夹的 ComboBox
stc4 cmb2 左边的标签
cmb13 显示当前选择的文件的ComboBox(包含edt1)
edt1 显示当前文件的文本框(可能为NULL)
stc3 cmb13 左边的标签
lst1 显示当前驱动器或文件夹内容的列表框
stc1 lst1 左边的标签
IDOK 确定(OK)按钮
IDCANCEL 取消(Cancel)按钮
pshHelp 帮助命令按钮
2.取得 ComboC 的坐标, 同上
这个是为了计算 ComboA 与 ComboC 的齐顶的间距, 来调整ComboB 和 ComboA 的间距
3.取得对话框模板(作为子窗口)相对于"打开"的坐标: 通过 GetWindowRect
注意对话框模板的父窗口是GetParent(hdlg)哦!
4.设定 ComboB 的坐标, 通过 SetWindowPos/MoveWindow
注意了, 如果只是简单地根据 ComboA 来设定我们的 ComboB 那就错了, 因为 ComboB 的父窗口是对话框模板!
如果对话框模板在"打开"上的坐标是(0,0)的话, 那么我们就可以轻松地设定为相同的坐标, 但如果不是的话, 就麻烦一点了.
我们需要计算相对坐标, 其实也不复杂, 只是看起来有点而已.
下面贴出我的对齐代码, 不再详述了:
- HWND hCboCurFlt = GetDlgItem(hParent, cmb1); //过滤列表ComboBox
- HWND hStaticFlt = GetDlgItem(hParent,stc2); //过滤列表左侧的Static
- HWND hCboFile = GetDlgItem(hParent,cmb13); //当前选择的文件ComboBox
- RECT rcCboFlt,rcStaFlt,rcDlg,rcFile;
- int left,top,width,height;
- //这个矩形是"过滤ComboBox"和"过滤提示Static控件",我们根据它们来对齐控件
- GetWindowRect(hCboCurFlt, &rcCboFlt);
- GetWindowRect(hStaticFlt,&rcStaFlt);
- //这个是"文件名(Edit)"-当前选择的, 用来计算它和过滤的间距,以调整我的控件和过滤Combo的间距
- GetWindowRect(hCboFile,&rcFile);
- //说实话,我并不清楚对话框模板的位置是怎样的,反正左边距不是0,不清楚
- //所以,调整窗口的时候要相对移动坐标
- GetWindowRect(hdlg,&rcDlg);
- //设定坐标, 注意, 这里的坐标不是相对于我们的对话框模板的,而是"打开/关闭",好好理解下
- //什么时候能画个图示意下就好了
- left = rcCboFlt.left-rcDlg.left;
- top = rcCboFlt.top-rcDlg.top+(rcCboFlt.top-rcFile.top);
- width = rcCboFlt.right-rcCboFlt.left;
- height = rcCboFlt.bottom-rcCboFlt.top;
- SetWindowPos(hCombo,,left,top,width,height,SWP_NOZORDER);
- left = rcStaFlt.left-rcDlg.left;
- top = rcStaFlt.top-rcDlg.top+(rcCboFlt.top-rcFile.top);
- width = rcStaFlt.right-rcStaFlt.left;
- height = rcStaFlt.bottom-rcStaFlt.top;
- SetWindowPos(hStatic,,left,top,width,height,SWP_NOZORDER);
说到这里已经差不多啦, 关于OFNHookProc的简单介绍就到这里了.
示例源代码:
- #if _WIN32_WINNT<0x0502
- #undef _WIN32_WINNT
- #define _WIN32_WINNT 0x0502
- #endif
- #include <Windows.h>
- #include <WindowsX.h>
- #include "resource.h"
- UINT_PTR __stdcall OFNHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam);
- BOOL get_file_name(char* title, char* filter,char* buffer)
- {
- OPENFILENAME ofn = {};
- *buffer = ;
- ofn.lStructSize = sizeof(ofn);
- ofn.hInstance = GetModuleHandle(NULL);
- //允许 缩放窗口+资源管理器风格+文件必须存在+隐藏只读文件+使能钩子+使能模板
- ofn.Flags = OFN_ENABLESIZING|OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY|OFN_ENABLEHOOK|OFN_ENABLETEMPLATE;
- ofn.lpstrFilter = filter;
- ofn.lpstrFile = &buffer[];
- ofn.nMaxFile = MAX_PATH;
- ofn.lpstrTitle = title;
- ofn.lpfnHook = OFNHookProc;
- ofn.lpTemplateName = MAKEINTRESOURCE(IDD_DLG_TEMPLATE);
- return (BOOL)GetOpenFileName(&ofn);
- }
- /************************************************************************
- 函数:OFNHookProc@16
- 功能:GetOpenFileName/GetSaveFileName的子类/钩子处理过程
- 参数: hdlg - 该对话框模板句柄, 注意:不是"打开/保存"对话框的句柄
- uiMsg,wParam,lParam - 同常规Windows消息参数
- 返回: 由于是对话框消息,所以返回值应使用SetWindowLong(...,DWL_MSGRESULT,...)来返回
- 分以下3种情况返回:
- 1.钩子函数返回0:默认对话框函数继续处理该消息
- 2.钩子函数返回非0:默认对话框函数忽略该消息不再处理
- 3.(例外)对于CDN_SHAREVIOLATION 和 CDN_FILEOK(点击"打开/保存"时触发) 通知消
- 息时,钩子过程应该明确返回一个非0值以指示钩子过程已经使用SetWindowLong(...,DWL_MSGRESULT,...);
- 设置了一个非零的该消息的返回值, 默认对话框函数不再继续处理
- *************************************************************************/
- UINT_PTR CALLBACK OFNHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
- {
- static HWND hParent; //对话框模板的父窗口句柄
- static HWND hCombo; //我增加的ComboBox的句柄
- static HWND hStatic; //我增加的Static控件的句柄
- if(uiMsg == WM_NOTIFY){
- LPOFNOTIFY pofn = (LPOFNOTIFY)lParam;
- if(pofn->hdr.code == CDN_FILEOK){
- //返回0-关闭对话框并返回
- //返回!0-禁止关闭对话框
- int iSel = ComboBox_GetCurSel(hCombo);
- if(iSel != ){//未选择"测试字符串二"时
- MessageBox(hParent,"你必须选择\"测试字符串二\"才能离开!",NULL,MB_ICONEXCLAMATION);
- SetWindowLong(hdlg,DWL_MSGRESULT,);
- //或使用:SetDlgMsgResult(hdlg,1);
- return ;
- }
- return ;
- }
- }else if(uiMsg == WM_SIZE){
- HWND hCboCurFlt = GetDlgItem(hParent, cmb1); //过虑列表ComboBox
- HWND hStaticFlt = GetDlgItem(hParent,stc2); //过虑列表左侧的Static
- HWND hCboFile = GetDlgItem(hParent,cmb13); //当前选择的文件ComboBox
- RECT rcCboFlt,rcStaFlt,rcDlg,rcFile;
- int left,top,width,height;
- //这个矩形是"过虑ComboBox"和"过虑提示Static控件",我们根据它们来对齐控件
- GetWindowRect(hCboCurFlt, &rcCboFlt);
- GetWindowRect(hStaticFlt,&rcStaFlt);
- //这个是"文件名(Edit)"-当前选择的, 用来计算它和过虑的间距,以调整我的控件和过虑Combo的间距
- GetWindowRect(hCboFile,&rcFile);
- //说实话,我并不清楚对话框模板的位置是怎样的,反正左边距不是0,不清楚
- //所以,调整窗口的时候要相对移动坐标
- GetWindowRect(hdlg,&rcDlg);
- //设定坐标, 注意, 这里的坐标不是相对于我们的对话框模板的,而是"打开/关闭",好好理解下
- //什么时候能画个图示意下就好了
- left = rcCboFlt.left-rcDlg.left;
- top = rcCboFlt.top-rcDlg.top+(rcCboFlt.top-rcFile.top);
- width = rcCboFlt.right-rcCboFlt.left;
- height = rcCboFlt.bottom-rcCboFlt.top;
- SetWindowPos(hCombo,,left,top,width,height,SWP_NOZORDER);
- left = rcStaFlt.left-rcDlg.left;
- top = rcStaFlt.top-rcDlg.top+(rcCboFlt.top-rcFile.top);
- width = rcStaFlt.right-rcStaFlt.left;
- height = rcStaFlt.bottom-rcStaFlt.top;
- SetWindowPos(hStatic,,left,top,width,height,SWP_NOZORDER);
- return ;
- }else if(uiMsg == WM_INITDIALOG){
- HFONT hFontDialog = NULL;
- hParent = GetParent(hdlg);
- //初始化我们自己的句柄,方便下次使用
- hCombo = GetDlgItem(hdlg, IDC_COMBO_TEST);
- hStatic = GetDlgItem(hdlg,IDC_STATIC_TEST);
- //初始化我们的控件的数据
- ComboBox_AddString(hCombo, "测试字符串一");
- ComboBox_AddString(hCombo, "测试字符串二");
- ComboBox_AddString(hCombo, "女孩不哭,哈哈!");
- ComboBox_SetCurSel(hCombo,);
- //一般为了表现效果一致,需要设置一下字体,比如说VC6的默认字体
- //System就相当的丑陋,如果不修改的话,受不了...
- //当然,最好是采用"打开"对话框的统一字体
- //如果你当前也使用VC6,可以注释掉看看,呃.........
- hFontDialog = (HFONT)SendMessage(hParent,WM_GETFONT,,);
- SendMessage(hCombo,WM_SETFONT,(WPARAM)hFontDialog,MAKELPARAM(TRUE,));
- SendMessage(hStatic,WM_SETFONT,(WPARAM)hFontDialog,MAKELPARAM(TRUE,));
- return ;
- }
- UNREFERENCED_PARAMETER(wParam);
- return ;
- }
- int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
- {
- char buffer[MAX_PATH];
- if(get_file_name("选择一个文件","所有文件(*.*)\x00*.*\x00",buffer)){
- MessageBox(NULL,buffer,"你选择了文件",MB_ICONINFORMATION);
- }else{
- MessageBox(NULL,"你没有选择文件或遇到了错误!",NULL,MB_ICONEXCLAMATION);
- }
- return ;
- }
示例项目(VC6.0):http://files.cnblogs.com/nbsofer/ofnhook.7z
女孩不哭 @ 2013-07-09 21:59:02 @ http://www.cnblogs.com/nbsofer
子类化GetOpenFileName/GetSaveFileName, 以及钩子函数OFNHookProc的使用的简要说明的更多相关文章
- Win32中安全的子类化(翻译)
关于子类化的话题虽然有些旧,但它至今仍然不失为一种开发Windows的强有力技术,在MFC的内核.甚至.NET的内核中都离不开它,希望本连载能对Windows开发的爱好者有所帮助. 原文标题:Safe ...
- 【QT】子类化QObject+moveToThread实现多线程
往期链接: <QThread源码浅析> <子类化QThread实现多线程> 从往期<QThread源码浅析>可知,在Qt4.4之前,run 是纯虚函数,必须子类化Q ...
- 深入理解MFC子类化
子类化,通俗来讲就是用自己的窗口处理函数来处理特定消息,并将自己其他消息还给标准(默认)窗口处理函数.在SDK中,通过SetWindowLong来指定一个自定义窗口处理函数:SetWindowLong ...
- 眼见为实(2):介绍Windows的窗口、消息、子类化和超类化
眼见为实(2):介绍Windows的窗口.消息.子类化和超类化 这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词.为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和 ...
- Ring3下无驱动移除winlogon.exe进程ctrl+alt+del,win+u,win+l三个系统热键,非屏蔽热键(子类化SAS 窗口)
随手而作,纯粹技术研究,没什么实际意义. 打开xuetr,正常情况下.winlogon.exe注册了三个热键.ctrl+alt+del,win+u,win+l三个. 这三个键用SetWindowsHo ...
- C++ 中超类化和子类化常用API
在windows平台上,使用C++实现子类化和超类化常用的API并不多,由于这些API函数的详解和使用方法,网上一大把.本文仅作为笔记,简单的记录一下. 子类化:SetWindowLong,GetWi ...
- C++ 中超类化和子类化
超类化和子类化没有具体的代码,其实是一种编程技巧,在MFC和WTL中可以有不同的实现方法. 窗口子类化: 原理就是改变一个已创建窗口类的窗口过程函数.通过截获已创建窗口的消息,从而实现监视或修改已创建 ...
- 窗口 超类化 子类化 HOOK
body { font-family: Bitstream Vera Sans Mono; font-size: 11pt; line-height: 1.5; } html, body { colo ...
- 利用钩子函数来捕捉键盘响应的windows应用程序
一:引言: 你也许一直对金山词霸的屏幕抓词的实现原理感到困惑,你也许希望将你的键盘,鼠标的活动适时的记录下来,甚至你想知道木马在windows操作系统是怎样进行木马dll的加载的…..其实这些都是用到 ...
随机推荐
- NSLayoutConstraint的简单应用
UIView *topView = [[UIView alloc] init]; topView.backgroundColor = [UIColor redColor]; [self.view ad ...
- Windows xcopy
1.考虑下面的需求,把aaa目录下面的111.txt 拷贝到 bbb,如下:echo onxcopy .\aaa\111.txt .\bbb\ /ypause2.注意这里表示路径要用右斜杠,因为左斜杠 ...
- WIN7系统开题提示loli.vbs 操作超时怎么办
这个是魔兽争霸的一个病毒,但是该病毒没有任何危害性,只是作为检测进入房间的地图是否含有作弊脚本,主动提供了清除工具 搜索loli,删除所有bat和exe,vbs文件 如果魔兽争霸3安装目录存在 ...
- Centos下安装gcc
虚拟机安装了一个CentOS7,发现没有gcc,通过以下命令安装: yum install gcc yum install gcc-c++
- Spring MVC 实现文件的上传和下载
前些天一位江苏经贸的学弟跟我留言问了我这样一个问题:“用什么技术来实现一般网页上文件的上传和下载?是框架还是Java中的IO流”.我回复他说:“使用Spring MVC框架可以做到这一点,因为Spri ...
- IDEA引入外部jar包的方法
在做发短信的功能(阿里大于)的时候,我只是吧jar包拷贝到了项目的external library. 拷贝进来之后我用junit写了test方法,可以完美发送短信到手机,但是我这是个web项目,然后我 ...
- TP - 001
- cmder、cmd、python 中文乱码问题
cmd下echo中文没有问题,但是进入python模式后就中文乱码,cmder更是echo也乱码 其实是要配置默认code page, cmd默认是ansi的编码,中文自然乱码 CMD chcp 65 ...
- 算法笔记_220:猜算式(Java)
目录 1 问题描述 2 解决方案 1 问题描述 看下面的算式: □□ x □□ = □□ x □□□ 它表示:两个两位数相乘等于一个两位数乘以一个 三位数. 如果没有限定条件,这样的例子很多. 但 ...
- MySQL中 PK NN UQ BIN UN ZF AI 的意思
PK Belongs to primary key作为主键 NN Not Null非空 UQ Unique index不能重复 BIN Is binary column存放二进制数据的列 ...