📄 01_9_13_10.asp.html
字号:
(1)nIDFirst和nIDLast </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
参与分配父窗口客户区的子窗口的id范围。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
每个WM_CHILD风格的窗口都有个id,这是在窗口创建过程中指定的。函数CWnd::Create()的第六个参数就是这个id。api函数CreateWindow和 CreateWindowEx里的那个HMENU类型的参数,当窗口的风格里有WM_CHILD时,它不是指的菜单句柄,而是该窗口的id。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
nIDFirst和nIDLast参数指明了:如果一个子窗口的id值大于等于nIDFirst并且小于等于nIDLast,在这个函数中才会给这个子窗口发送 WM_SIZEPARENT消息,这个子窗口才能参与父窗口客户区的分配。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
(2)nIDLeftOver </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
前面说过,有一个特定的子窗口,它不响应WM_SIZEPARENT消息。只有当其它的子窗口都分配完了,它才来捡取父窗口客户区里剩下的那块。 nIDLeftOver正是这个子窗口的id。它也必须大于等于nIDFirst并且小于等于nIDLast。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
(3)lpRectClient </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
这是一个指向RECT结构数据的指针。这个RECT结构里存放的正是父窗口客户区的初始可用区域。随着在该函数里依次给各个子窗口发送 WM_SIZEPARENT消息,每个响应这个消息的子窗口都会切去自己所占据的部分。最后剩下的部分,就是id为nIDLeftOver的子窗口将要占据的区域了。这个参数可以为NULL,这时初始的可用区域就是整个父窗口客户区。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
(4)nFlag和lpRectParam </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
这两个参数放在一起讲比较好。nFlag是该函数的功能标志,它可以有三个值:reposDefault,reposQuery 和reposExtra。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
当nFlag等于reposDefault时,RepositionBars函数的功能是这样的:依次给id介于nIDFirst和nIDLast之间并且不等于nIDLeftOver的子窗口发送WM_SIZEPARENT消息,每个响应这个消息的子窗口从lpRectClient所指的结构里切去自己所占据的部分,并且将自己的大小和位置调整到自己所占据的区域的大小,最后RepositionBars函数还将id为nIDLeftOver的子窗口的大小和位置调整到被其他子窗口切剩的可用区域内,使这个子窗口正好完全覆盖最后的可用区域。这种情况下lpRectParam不用,可以为NULL。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
当nFlag等于reposQuery 时,RepositionBars函数的功能是这样的:依次给id介于nIDFirst和nIDLast之间并且不等于nIDLeftOver的子窗口发送WM_SIZEPARENT消息,每个响应这个消息的子窗口从lpRectClient所指的结构里切去自己所占据的部分,但是他们并不调整自己的大小和位置,最后RepositionBars函数并不调整将id为nIDLeftOver的子窗口的大小和位置,而是根据bStretch的值来做动作:如果bStretch为TRUE,那么 RepositionBars函数把最后剩下的可用区域拷贝到lpRectParam指向的RECT结构里;如果bStretch为FALSE,那么RepositionBars函数把所有其他子窗口占用掉的可用区域的高和宽(要所有的子窗口都紧排在一起,形成一个大的矩形,这个值才有意义)拷贝到lpRectParam指向的RECT结构的bottom 和right成员里,其top和left成员被置零。使用这个nFlag值来调用RepositionBars的目的不是要重排子窗口,而是要看看,假如重排子窗口的话,这些子窗口将占去多大一块,最后剩下的可用区域在什么位置等等信息。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
当nFlag等于reposExtra时,该函数的功能和nFlag等于reposDefault时差不多,有点小小的区别。此时需要用到lpRectParam。前面说过,当 nFlag等于reposDefault时,RepositionBars函数将在最后把id为nIDLeftOver的子窗口的大小和位置调整到被其他子窗口切剩的可用区域内,使这个子窗口正好完全覆盖最后的可用区域。而当nFlag等于reposExtra时,RepositionBars在调整id为nIDLeftOver的子窗口的大小和位置前,还要用 lpRectParam来对最后剩下的可用区域做修正。假设lpRect指向的是最后的可用区域,那么这个修正是这样进行的: </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
lpRect->top+=lpRectParam->top;</td></tr></table><table width=100% ><tr><td class=a14>
lprect->left+=lpRectParam->left;</td></tr></table><table width=100% ><tr><td class=a14>
lpRect->right-=lpRectParam->right;</td></tr></table><table width=100% ><tr><td class=a14>
lpRect->bottom-=lpRectParam->bottom;</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
通过这样的修正,可以使最后剩下的可用区域不被id为nIDLeftOver的子窗口占满,而是空出一些地方来留作他用。 </td></tr></table><table width=100% ><tr><td class=a14>
(5)bStretch </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
这个参数上面已经提到一点它的作用。它主要是提供给各个响应WM_SIZEPARENT消息的子窗口用的,子窗口例如工具栏,状态条等在决定自己将从父窗口客户区的可用空间里划走多少时,这个参数也是个判断的依据。详细可以参阅工具栏和状态条响应WM_SIZEPARENT的函数OnSizeParent()。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
2。消息WM_SIZEPARENT </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
这是个mfc自定义的消息。在msdn里的TN024这篇技术文章里有关于这个消息的说明。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
该消息的两个参数中wParam不用,lParam是指向一个AFX_SIZEPARENTPARAMS结构变量的指针,这个结构变量是在RepositionBars函数里定义的: </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
AFX_SIZEPARENTPARAMS layout; </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
AFX_SIZEPARENTPARAMS结构定义如下: </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
struct AFX_SIZEPARENTPARAMS</td></tr></table><table width=100% ><tr><td class=a14>
{</td></tr></table><table width=100% ><tr><td class=a14>
HDWP hDWP; </td></tr></table><table width=100% ><tr><td class=a14>
RECT rect; </td></tr></table><table width=100% ><tr><td class=a14>
SIZE sizeTotal; </td></tr></table><table width=100% ><tr><td class=a14>
BOOL bStretch; </td></tr></table><table width=100% ><tr><td class=a14>
};</td></tr></table><table width=100% ><tr><td class=a14>
这个结构变量的成员是在RepositionBars函数里填写的:它的bStretch成员就是RepositionBars的参数bStretch,它的sizeTotal成员的两个成员cx和cy都被设置为零,它的rect成员就是从RepositionBars的参数lpRectClient里拷贝过来的,就是父窗口客户区的初始可用区域嘛。每个响应这个消息的子窗口都必须修改rect成员的值,以便切去自己所占据的部分。 </td></tr></table><table width=100% ><tr><td class=a14>
成员hDWP是什么?这得知道三个api函数:BebinDeferWindowPos(),DeferWindowPos()和EndDeferWindowPos()。这三个api函数是用来成批设置窗口的位置和尺寸的。BebinDeferWindowPos()先通知windows分配一个将用来存贮窗口的位置和尺寸信息的结构,它不是返回这个结构的指针,而是返回代表这个结构的句柄,句柄的类型是HDWP。然后每个需要重新设置位置和尺寸的窗口都要调用DeferWindowPos()函数(该函数需要那个HDWP 类型的句柄为参数),以便往那个结构里填写各自的窗口位置和大小信息。最后,在某个合适的时候调用EndDeferWindowPos(),windows就会根据那个结构里的信息把有关的窗口的位置和大小一次性设置好。比起针对每个窗口分别用SetWindowPos()等函数逐个设置来说,这种方法速度快。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
好了,在RepositionBars函数里正是调用了BebinDeferWindowPos(),获得一个HDWP类型的句柄,这个句柄就被填写到了上面那个结构变量 layout的成员hDWP里。然后RepositionBars函数给每个符合条件的子窗口发送WM_SIZEPARENT消息。在每个响应WM_SIZEPARENT消息的子窗口里,要调用DeferWindowPos()来设置位置和尺寸信息。当所有的子窗口都响应完毕WM_SIZEPARENT消息后,RepositionBars函数再调用 EndDeferWindowPos()函数,这一来,除了那个id为nIDLeftOver的子窗口外,所有的子窗口都一次性排好了位置了。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
至于该结构的sizeTotal成员的意义,它累计每个子窗口所占据掉的可用区域的长宽尺寸和。每个子窗口在响应WM_SIZEPARENT消息时一般都要把自己所占据的区域的高和宽分别累加到sizeTotal结构的cy和cx成员里。这有什么意义呢?当每个子窗口所占据的区域都是挨在一起的时候,这个 sizeTotal结构就有意义了,主框架窗口可以使nFlag等于reposQuery,使bStretch等于FALSE来调用RepositionBars函数,RepositionBars函数会把 sizeTotal结构的两个成员值拷贝到lpRectParam参数里返回给主框架类(前面也提到过),这样主框架类就知道它的客户区内的子窗口占去了客户区内多大的一块空间。如果你的主框架窗口没有利用这个信息,那么响应WM_SIZEPARENT消息的子窗口就可以不理睬sizeTotal成员。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
ID的分配 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
可以看到,每个子窗口都有个id,同一个父窗口的子窗口的id不能重复。mfc的一些现成的控件子窗口都有预定义的id: </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
id名 id值 意义</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_TOOLBAR 0xE800 // 主窗口的工具栏的id</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_STATUS_BAR 0xE801 // 状态栏的id</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_PREVIEW_BAR 0xE802 // PrintPreview Dialog Bar</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_RESIZE_BAR 0xE803 // OLE in-place resize bar</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_REBAR 0xE804 // COMCTL32 "rebar" Bar</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_DIALOGBAR 0xE805 // CDialogBar</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
还有象单文档程序的视图窗口,多文档程序的那个MDIClient窗口,分隔条窗口,他们的id值介于下面两个id值之间: </td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_PANE_FIRST 0xE900 //</td></tr></table><table width=100% ><tr><td class=a14>
AFX_IDW_PANE_LAST 0xE9FF</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
你要给你自己的子窗口分配id的话,别和上面的重复了。一般如果用IDE的菜单view/resource symbols项来加入自己的id的话,是不会重复的。有关id,还可以看看msdn里的TN020文章,那是专讲id的。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
实例分析 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
1。CFrameWnd类是如何调用RepositionBars函数的 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
前面介绍了RepositionBars的各个参数和意义,现在看看CFrameWnd类是如何调用这个函数的,从中可以学习RepositionBars函数的使用方法。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
CFrameWnd类及其派生类生成的窗口的客户区内可以有工具栏,状态条和视图窗口等子窗口。当父窗口的尺寸发生变化时,这些子窗口的各自的位置和大小比例关系保持不变,这就需要父窗口一旦在它自己的尺寸发生变化时就调用RepositionBars函数。CFrameWnd类是集中在函数 RecalcLayout里调用RepositionBars函数的。该类保证了在窗口尺寸发生变化时函数RecalcLayout都被调用,从而RepositionBars函数也能被及时调用,确保了各个子窗口都能及时调整自己的位置和大小。 </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
RecalcLayout是个虚函数。该函数的功能就是在主框架的客户区内提供一个初始的可用区域,并把这个区域放在一个CRect类型的变量里。该函数大致是这样的: </td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
void CFrameWnd::RecalcLayout(BOOL bNotify)</td></tr></table><table width=100% ><tr><td class=a14>
{</td></tr></table><table width=100% ><tr><td class=a14>
if (m_bInRecalcLayout)</td></tr></table><table width=100% ><tr><td class=a14>
return;//这大概是在防止该函数重入</td></tr></table><table width=100% ><tr><td class=a14>
m_bInRecalcLayout = TRUE;</td></tr></table><table width=100% ><tr><td class=a14>
....</td></tr></table><table width=100% ><tr><td class=a14>
....</td></tr></table><table width=100% ><tr><td class=a14>
....</td></tr></table><table width=100% ><tr><td class=a14>
....</td></tr></table><table width=100% ><tr><td class=a14>
if (GetStyle() & FWS_SNAPTOBARS)</td></tr></table><table width=100% ><tr><td class=a14>
{</td></tr></table><table width=100% ><tr><td class=a14>
CRect rect(0, 0, 32767, 32767);</td></tr></table><table width=100% ><tr><td class=a14>
RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery,</td></tr></table><table width=100% ><tr><td class=a14>
&rect, &rect, FALSE);</td></tr></table><table width=100% ><tr><td class=a14>
RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra,</td></tr></table><table width=100% ><tr><td class=a14>
&m_rectBorder, &rect, TRUE);</td></tr></table><table width=100% ><tr><td class=a14>
CalcWindowRect(&rect);</td></tr></table><table width=100% ><tr><td class=a14>
SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),</td></tr></table><table width=100% ><tr><td class=a14>
SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);</td></tr></table><table width=100% ><tr><td class=a14>
}</td></tr></table><table width=100% ><tr><td class=a14>
else</td></tr></table><table width=100% ><tr><td class=a14>
RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra, &m_rectBorder);</td></tr></table><table width=100% ><tr><td class=a14>
m_bInRecalcLayout = FALSE;</td></tr></table><table width=100% ><tr><td class=a14>
} 可以看出,mfc认为这个函数是不能重入的。在编制自己的RecalcLayout()函数时也得用同样的方法来防止重入。</td></tr></table><table width=100% ><tr><td class=a14>
后面的if语句检查框架窗口是否具有风格FWS_SNAPTOBARS,这个风格用在什么时候呢?我是这样认为的:通常都是在主框架窗口的尺寸改变</td></tr></table><table width=100% ><tr><td class=a14>
时,子窗口在响应WM_SIZEPARENT消息时调整自己的尺寸以便跟上框架窗口的尺寸变化。有这样的情况:父窗口的客户区内的子窗口的数目是动态变</td></tr></table><table width=100% ><tr><td class=a14>
化的,而且这些子窗口互相不能重叠,他们的尺寸由于某种原因不好改变。那么当子窗口的数目发生增减时,如不调整父窗口自己的尺寸,就会导</td></tr></table><table width=100% ><tr><td class=a14>
致客户区留下空白或新增加的子窗口没有多余空间安排。FWS_SNAPTOBARS风格就是用在这种情况下,使父窗口能调整自己的大小以便容纳子窗口。</td></tr></table><table width=100% ><tr><td class=a14>
看这个分支里的语句,似乎是这样的。</td></tr></table><table width=100% ><tr><td class=a14>
一般都不会有FWS_SNAPTOBARS风格的,所以一般是执行else分支。在这个分支里简单地调用RepositionBars去重排所有的子窗口,它的参数</td></tr></table><table width=100% ><tr><td class=a14>
lpRectClient 使用默认的NULL值,意思就是初始可用区域是父窗口的整个客户区。</td></tr></table><table width=100% ><tr><td class=a14>
可以在自己的派生类里编写自己的RecalcLayout函数,以便用自己的方法调用RepositionBars函数。要注意的是在CFrameWnd类的窗口刚被创建</td></tr></table><table width=100% ><tr><td class=a14>
时RecalcLayout函数也被调用,此时可能某些用户自己加的子窗口还未被创建出来,所以在这个函数内如果要引用某个用户自己加的子窗口的句柄</td></tr></table><table width=100% ><tr><td class=a14>
的话必须先用::IsWindow()函数判断一下该窗口句柄是否可用。否则的话就会出现非法操作了。</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
</td></tr></table><table width=100% ><tr><td class=a14>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -