原文地址:http://www.cnblogs.com/nbsofer/archive/2013/07/09/3180973.html
昨天, 群里面有一个人问起: 要怎么让"文件打开对话框"居中显示, 有人说子类. 而我告诉他的方法是用钩子函数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 只读 CheckBox2.取得 ComboC 的坐标, 同上
cmb1 显示 文件过滤 的 ComboBox
stc2 cmb1 左边的标签
cmb2 显示当前驱动器或文件夹的 ComboBox
stc4 cmb2 左边的标签
cmb13 显示当前选择的文件的ComboBox(包含edt1)
edt1 显示当前文件的文本框(可能为NULL)
stc3 cmb13 左边的标签
lst1 显示当前驱动器或文件夹内容的列表框
stc1 lst1 左边的标签
IDOK 确定(OK)按钮
IDCANCEL 取消(Cancel)按钮
pshHelp 帮助命令按钮
这个是为了计算 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,0,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,0,left,top,width,height,SWP_NOZORDER);
示例项目(VC6.0): http://files.cnblogs.com/nbsofer/ofnhook.7z