请参考Visual C++ Programmer's Guide (Visual C++ 程序员手册)来了解这里对此调度结构更进一步之描述。特别地,请注意窗口消息,控件窗口通知以及命令之间的显著区别。这里假设你对相关问题已很熟悉并希望做进一步的了解。
a.命令传递和调度。
从MFC1.0之功能开发到MFC2.0之结构化视窗,产生了WM_COMMAND 消息机制。它冗余地提供了菜单命令之通知快捷键和对话框控件的通知。
MFC1.0中实现了,在CWnd导出类中的一个命令处理函数(例如,OnFileNew)能被调用以响应特定之WM_COMMAND命令。由此可以窥见一个被称为消息映射的数据结构,以及一个节省空间的命令传递机制之创建。
MFC1.0还提供了额外的手段来从命令中分离控件通知。命令一般表示为一个16位之ID,通常被称为命令ID(CommandID)。命令通常从一个CFrameWnd窗口出发(比如,选择了菜单或使用了快捷键),然后被传递到许多其它窗口。
MFC1.0中命令传递被毫无察觉地实现于多文档界面(MDI)中,一个MDI框架窗口调度所有命令到其当前活跃之子窗口。这一功能在MFC2.0中被一般化以及扩展了。它允许命令被一个范围更广的对象来处理(而不仅仅是窗口对象);它提供了一个更正式的并且可扩展的结构来传递消息,扩展命令目标传递,使之不仅用于处理命令也用于更新UI对象。(像菜单项和工具条按钮)。这样就可以反映出当前之可用命令。
CommandID可参考Visual C++程序员手册中关于命令传递和其绑定处理的相关内容。
技术点20(TN20)有关ID命名的信息。我们使用“ID_”作为一般命令ID之前缀。命令ID大于0x8000。
如果有一个STRINGTABLE资源有和命令ID相同的ID值那么在消息栏中或是状态条中就可以显示此命令之描述字符串。在你程序之资源中,一个命令ID可能出现在下面几处:
在一个具有相同ID之用作提示信息的字符串资源中;
在能够产生同样命令的菜单项中,而这此菜单项是由各种菜单资源创建的;
更高级的,由一个对话框按键产生的GOSUB命令。
在你的程序之源代码中,命令ID可能出现在以下几处:
在你的RESOURCE.H(或其它主符号头文件)中定义程序特定之命令ID;
或,在一个用于创建工具条之ID列表处;
或,一个ON_COMMAND宏中;
或,一个ON_UPDATE_COMMAND_UI宏中。
当下,在MFC中需要命令ID>=0x8000的唯一原因即是对话框中GOSUB命令的要求。
GOSUB命令,即在对话框中使用命令之构架。命令之架构包括,在框架窗口,菜单项,工具条按键,对话框面板按键以及其它控制面板和用户界面等元素之中传递命令,并与它们协调工作。最终,使这些界面元素按需求更新,并将命令或控件ID传递到命令之主目的地(通常是主框架窗口)。主命令目的地由于某些原因或许会把命令或控件通知传递给其它命令目的地。
对话框(模态或非模态的)或可获利于命令架构的某些特性。实际上,只要你把对话框控件之控件ID写进恰当的命令ID中,你就会发现这一点。当然,对对话框的支持并非自行完成的,因此需要你写一点额外的代码。注意,为了让以上这些能完好之工作,你的命令ID应当>=0x8000。
要让众多对话框能够将某一命令传递至同一框架中,同有之命令应当>=0x8000;而那些在某个特定的对话框中的非同有之IDC_们应当<=0x7FFF。
首先,你或将一通用按键放置在某一通常之模态对话框中,并将此按键之IDC值设为某恰当之命令ID。而后,当用户选择此按键时,此对话框之拥有者(通常为主框架窗口),就会像得到其它命令一样得到该控件命令。
这就被称为GOSUB命令,原因是它通常被用来唤起其它对话框。(一个作为第一个对话框的GOSUB)。
另外,你还可以在对话框中调用CWnd::UpdateDialogControls()函数,并将主框架窗口的地址传给它,此函数会针对你框架窗口是否有命令处理方法来使能/禁用你的对话框控件。此函数会在程序空闲时间被你控制面板(Control bar)自动调用,但对于一般对话框而言,如果你需要此功能,就必须手动调用它。
请注意ON_UDATE_COMMAND_UI被用于维持任何时候所有程序之菜单项之使能(或选中)状态,是一个十分浪费计算时间的问题。一个常用之技术是仅当用户选择POPUP时,(即菜单弹出时)来使能(选中)菜单项。
在MFC2.0中,CFrameWnd的实现处理了WM_INITMENUPOPUP消息,并且使用命令传递架构借助于ON_UPDATE_COMMAND_UI消息之处理来决定菜单状态。CFrameWnd也处理WM_ENTERIDLE消息来在状态条(或称消息条)对当前菜单项进行描述。
应用程序中使用的菜单结构,其由Visual C++ 创建,在WM_INITMENUPOPUP消息发出时,它容纳了潜在可用的命令列表。ON_UPDATE_COMMAND_UI消息处理函数可以修改菜单之状态或者文字。对于更高级的用法(如File MRU最近使用文件列表或OLE动态弹出菜单),实际的做法是在菜单被下拉显示前修改菜单之结构。
同样地当程序进入空闲时间时会对ON_UPDATE_COMMAND_UI消息带来的对工具条(或其它控制面板)的调整要求做出处理。请参考类库手册和技术点31获取更多的关于控制面板的相关信息。
随处弹出菜单(Nested Popup Menus) 如果你正在使用随处弹出菜单结构,你会发现菜单的首个菜单项之ON_UPDATE_COMMAND_UI处理方法有两种不同的情况。第一种情况是为弹出窗口本身调用更新消息处理函数。由于这种弹出菜单没有ID号,并且要用弹出菜单之第一个菜单项的ID号来表示整个菜单。因此,这种调用是必须的。这种情况下,CCmdUI对象之m_pSubMenu成员值是非空的,并且其指向本菜单。第二种情况是作为下级菜单的弹出窗口之菜单项即将显示前。这种情况下菜单ID指向首个菜单项,并且CCmdUI对象之m_pSubMenu成员为空,这使得你可以直接从菜单项来使能弹出菜单。而你需要做的就是写一些菜单响应代码;
例如,一个随处弹出菜单有如下结构:
File > New > Sheet(ID_NEW_SHEET)
> Chart(ID_NEW_CHART)
ID_NEW_SHEET和ID_NEW_CHART两个命令可以分别地被使能/禁用。New所在之弹出菜单应当是可用的。只要上述两命令之一是可能的,命令ID_NEW_SHEET的处理函数看起来会像下面这样(它是弹出菜单第一个命令):
CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
if(pCmdUI->m_pSubMenu!=NULL)
{//根据Sheet和Chart来使能整个弹出菜单
BOOL bEnable=m_bCanCreateSheet||m_bCanCreateChart;
//这里CCmdUI::Enable是或逻辑,因此我们必须完成
//一些可能要完成的工作。
pCmdUI->m_pMenu->EnableMenuItem(
pCmdUI->m_nIndex,
MF_BYPOSITION|(bEnable?MF_ENBLED:(MF_DISABLED|MFGRAYED))
);
return;
}
//否则只考虑NewSheet命令
pCmdUI->Enable(m_bCanCreateSheet);
}
ID_NEW_CHART的命令处理是一个一般的命令更新处理函数。它看起来就像下面这样;
void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bCanCreateChart);
}
(译者注:请注意上面两个函数里传递过来的pCmdUI的内容)
ON_COMMAND和ON_BN_CLICKED
上面二者之消息映射宏是一样的。
MFC的命令和控件通知的路由机制只使用命令ID来决定其路由去向。控件通知中如果其值为零(BN_CLICKED)则被看作为命令。高级提示:实际上,所有的控件通知消息遍历命令处理链条。因此,技术上有如下可能性,你可写一个控件通知处理函数,比如说EN_CHANGE,而把它放在你的文档类中。这并不是一种被推荐的做法,因为实际中需要这种特性的程序很少。ClassWizard不支持这种做法,并且使用这种用法会产生破碎代码。
除去按钮控件的自动禁用
假设你在一个对话面板中放了一个按钮控件,或者是在一个对话框类中你自行调用了CWnd::UpdateDialogControls函数,你会发现该按钮如果没有ON_COMMAND或ON_UPDATE_COMMAND_UI等处理映射函数,就会被框架自动禁用。而在很多情况下,你不想要映射函数但仍然希望按钮是可用的。最简单的方法就添加一个哑函数并且什么也不做(用ClassWizard很容易实现)。
窗口消息传递
以下讲述一些MFC类的高级课题,以及窗口消息是如何传递的,还有一些对之有影响的课题。更深入的讨论请参考类库手册有关公开API的细节。还请查阅MFC类库的源代码以获取更多实现细节。特别地,请阅读技术点17中关于窗口清除的讨论;对于所有CWnd导出类来说,这是一个很重要的主题。
CWnd公开的成员函数CWnd::OnChildNotify为子窗口(或者说是控件)提供了一个强大的可扩展的结构来实现钩子功能;或者说让流到其父窗口(或者说拥有者)的消息命令和控件通知有机会流到子窗口。
如果子窗口(控件)是一个CWnd的C++对象,作为虚函数的OnChildNotify将会首先被调用,其调用参数是来自原始消息,即一个MSG结构。子窗口可以对消息置之不理,吞食,或者为父窗口修改这条消息(少见)。缺省的,CWnd实现是处理下一条消息,并通过OnChildNotify钩子来让子窗口首先触及这条消息。看下面这些消息,WM_MEASUREITEM和WM_DRAWITEM(自绘消息);WM_COMPAREITEM和WM_DELETEITEM(自绘消息);还有WM_HSCROLL和WM_VSCROLL,WM_CTLCOLOR,WM_PARENTNOTIFY。
你会发现OnChildNotify钩子被用作将父绘消息变为自绘消息。除了OnChildNotify钩子以外,滚动消息还有更进一步的传递行为。请仔细阅读下面关于滚动条和WM_HSCROLL&WM_VSCROLL消息源的细节。
CFrameWnd
CFrameWnd类提供了大多命令之传递以及用户界面更新的实现。它主要被用作应用程序之主窗口(CWinApp::m_pMainWnd),但也适用于所有框架窗口。主框架窗口是指带有菜单条,状态条(或消息提示行)的窗口。请将如上讨论和命令传递以及WM_INITMENUPOPUP消息相互联系考虑。(译者注:见前,“在MFC2.0中,CFrameWnd的实现处理...”)
CFrameWnd类管理活跃视图,下面的消息被传递到当前视图:所有的命令消息(活跃视图最先得到它们);WM_HSCROLL和WM_VSCROLL消息(来自相连滚动条);还有已经被转换成对CView::OnActiveView虚函数之调用的消息WM_ACTIVATE(对MDI来说是WM_MDIACTIVATE).
关于CMDIFrameWnd和CMDIChildWnd
两种MDI框架窗口都由CFrameWnd继承而来,因此都可以进行同样的命令传递和用户界面刷新。对于一个典型的MDI应用程序来说,只有主框架窗口(就是说CMDFrameWnd对象)拥有菜单条和状态条,因此其也就是命令传递实现的主要去处。一般的传递策略是当前活跃的MDI子窗口首先得到命令。
缺省的PreTranslateMessage实现是,首先处理来自MDI子窗口的加速键表,其次是MDI框架窗口的,最后是标准的MDI的系统命令加速键(它通常由TranslateMDISysAccel函数处理)。
关于滚动条
当处理滚动条消息的时候(指WM_HSCROLL/OnHScroll和WM_VSCROLL/OnVScroll),你应当知道不能依赖滚动条来自何处,这不仅仅是一个普通的窗口问题;因为滚动消息可能来自一个真实的滚动条控件,或者来自WE_HSCROLL/WS_VSCROLL滚动条,而这并非一个滚动条控件。MFC对此加以了扩展,滚动条控件可以是窗口的子控件或者是窗口自有的(实际上,所谓的窗口和滚动条之间的父与子关系可以是任何关系);这点对于在切分窗口中共享滚动条是特别重要的。请参考技术点29以获得更多的关于CSplitterWnd的细节。作为旁注,要是有两个CWnd的导出类,而且在创建时指定的滚动条风格是相互冲突的,则其不会反映到窗口上去。作为参数传递到创建例程中时,WS_HSCROLL和WS_VSCROLL可以被单独设置,并且窗口创建后不能更改。当然,你不应当在视窗创建后再直接地调试或设定视窗之WS_H/VSCROLL风格。对于CMDIFrameWnd来说,你传到Create和LoadFrame中的滚动条风格,一般用于创建MDICLIENT。如果你期望有一个滚动条样式的MDICLIENT(像资源管理器),确信在创建CMDIFrameWnd时为其同时设定了滚动条的两种风格(WS_HSCROLL/WS_VSCROLL)。对于CSplitterWnd,滚动条风格显现在分割区域中特定共享之滚动条上。对于静态切分窗口,你通常不用指定滚动条风格,对于动态切分窗口,你需要根据你窗口的切分方向来对滚动条风格进行设定。那就是说,如果你分割行,请设定WS_HSROLL;如果你分割列,请设定WS_VSROLL。