1. 命令与命令分散处理:
1) 命令在MFC中就是指各种基于WM_COMMAND消息(也包括UPDATE_COMMAND_UI等);
2) 可以触发命令消息的元素有很多,并且可以分布在不同类中(不同地点,不同组件),例如菜单项、工具栏按钮等;
3) 虽然命令触发的元素分散在各个地方,但是Windows只允许主框架(CFrameWnd)来接受命令,这就意味着貌似只有主框架能处理这些命令,但实际并不是这样的!!
!!可以看到在文档/视图结构中几乎任何地方(任何类对象)都可以处理不同的命令消息,比如只有主框架的菜单项才会产生的命令消息却能在CView甚至文档甚至CApp中处理!!
4) 这就是MFC的命令分散化处理的设计,这样做的好处就是可以让命令处理放在最合适的对象中进行,避免把所有的处理程序都堆在框架类里,这样既不符合逻辑也不美观;
2. 命令传送机制:
1) 为了达到命令分散处理的目的,MFC在文档/视图结构的底层根植了命令传送机制,它可以将所有发送给框架的命令分发(传送)给其它各个对象中!
2) MFC使用CCmdTarget的虚函数OnCmdMsg进行命令传送,即Command Message的缩写:
i. 几乎所有的Windows元素,包括窗口都属于命令对象,即CmdTarget,因为窗口等元素都会是命令的产生者或者接受者,同样CWnd等大多数Windows元素都是直接或间接派生与CCmdTarget;
ii. 看一下最重要的主框架窗口的CFrameWnd::OnCmdMsg的默认实现:
BOOL CFrameWnd::OnCmdMsg(Cmd)
{
CView* pView = GetActiveView();
if (pView && pView->OnCmdMsg(Cmd)) return TRUE;
if (CWnd::OnCmdMsg(Cmd)) return TRUE;
CWinApp* pApp = AfxGetApp();
if (pApp && pApp->OnCmdMsg(Cmd)) return TRUE;
return FALSE;
}
!这是一段不规范的伪代码,只是为了演示其命令传递的过程;
!!其中Cmd就是主框架接收到的命令;
iii. 从过程可以看出,CFrameWnd会把接受到的一条命令先传递给CView,如果CView中有接受该命令的消息映射那么就将这条命令交给CView来处理,处理完成则返回TRUE退出并从消息队列中删除该命令;如果CView中没有接受该命令的消息映射,那么就将命令交给自己处理,但是如果主框架自己也没有接受该命令的消息映射就只能传给应用程序本身了,如果谁都没有则会以失败退出,并将该命令转交给::DefWindowProc来处理了;
!!注意,是先交给“活动视图”处理的,因为一个程序可能有好多视图,但是命令只会先传给当前正处在活动状态的视图,即当前具有输入焦点的视图(代码中使用GetActiveView来获取活动视图);
!!最最重要的一点千万不要忘了,那就是千万不要让多个对象来处理同一种命令,可以看到,一旦有对象接受一条命令那么处理完之后CFrameWnd的OnCmdMsg就立即返回了,之后的就不会再处理下去了,所以千万不要傻乎乎地让对个对象来处理同一种命令;
3) 其实活动视图也会继续传送命令,在CView自己的OnCmdMsg中会调用CDocument的OnCmdMsg,而CDocument的OnCmdMsg里会继续调用文档模板CDocTemplate的OnCmdMsg,这里看一下完整的命令传送体系图:
4) 分配命令处理的习惯:
i. 将菜单的New、Open、Exit交给CWinApp处理(OnFileNew、OnFileOpen、OnAppExit);
ii. 菜单的Save、Save As交给CDocument处理(OnFileSave、OnFileSaveAs);
iii. 显示/隐藏工具栏、工具栏/状态栏命令交给CFrameWnd处理;
iv. 其它大多数命令交由文档视图处理;
!!只有命令消息才遵循命令传递机制,其它窗口命令,如WM_PAINT、WM_CHAR等还是只能交由接受消息的窗口处理,窗口消息不能传递;
!!鼠标、键盘输入的处理传给视图,也交由视图处理,其它大多数窗口消息交由框架窗口处理,毕竟窗口消息就是窗口才接受的消息,因此都由窗口来处理咯!
!!!一定要记住!!文档对象(CDocument)和应用程序对象(CWinApp)从不接受“非”命令消息!!
3. 预定义命令ID和命令处理程序:
1) MFC为一些非常常用的命令项提供了预定义的ID和默认实现的命令处理函数,这些ID和处理函数都是在基类就已经实现了的,因此在派生的时候并不会看到它们,但是可以直接调用;
2) 虽然MFC提供了这些预定义命令ID和处理程序的实现,但并不是所有的也都提供了消息映射,像OnFileNew和OnFileOpen等MFC就提供了默认的命令ID(ID_FILE_NEW和ID_FILE_OPEN)也提供了默认的函数实现,但是就是偏偏没有提供默认的消息映射ON_COMMAND(ID_FILE_NEW, OnFileNew),也就是说你想使用默认的处理函数还必须得手工添加ON_COMMAND消息映射,这又是为什么?干脆全部消息映射也帮你添加好不就行了,为什么还要那么麻烦呢?还给你留一些命令就是不添加预定义的消息映射,非得让你手动添加才能使用呢?
!!答案是显而易见的,因为一部分命令的处理很少甚至是几乎不使用默认的实现,都要添加自己的功能,但是对于这些命令用户又想使用MFC预定的命令ID,此时用户就能有选择的将这些ID映射到自己的处理函数上去,如果为这些ID在基类中也提供映射的话,那么在派生类中就不能再将该ID映射到其它处理函数上去了,因为一个ID只能映射一个处理函数!!
3) 但像其它一些命令,如ID_APP_EXIT等,这些命令都已经达成一定的国际共识了,就只让CWinApp的OnAppExit来处理了,并且OnAppExit也不需要怎么添加或修改,那像这些命令的映射MFC就直接在基类给出默认的定义了;
4) 下面给出一些常用的预定义命令ID及其默认处理程序和是否需要手动添加消息映射:这里只列举一些最常用的
各项对应的属性分别是:命令ID 菜单项 默认处理程序 是否需要手动添加消息映射(Y/N)
File菜单:
ID_FILE_NEW New CWinApp::OnFileNew Y
ID_FILE_OPEN Open CWinApp::OnFileOpen Y
ID_FILE_SAVE Save CDocument::OnFileSave N
ID_FILE_SAVE_AS Save As CDocument::OnFileSaveAs N
ID_APP_EXIT Exit CWinApp::OnAppExit N
Edit菜单:只有预定义命令ID,MFC没有为Edit菜单定义任何默认处理程序,ID都是ID_EDIT_开头,有CLEAR、COPY、CUT等常用的
View菜单:
ID_VIEW_TOOLBAR Toolbar CFrameWnd::OnBarCheck N
ID_VIEW_STATUS_BAR Status Bar CFrameWnd::OnBarCheck N
!!以上两个是是否要显示工具栏和状态栏的命令
!!其实你完全没必要使用MFC提供的各种预定义,所有都可以用自己的实现来代替,可以定义自己的命令ID、自己的命令处理函数,也可以用自定义的命令处理函数来处理MFC预定义ID的命令;