1. 一个MFC程序的基本框架由两个类构成:App类和Window类。通常情况下App类继承自CWinApp,主要负责应用程序相关部分;Window类继承自CFrameWnd,负责窗口显示,窗口逻辑部分。
Window类组合于App类,程序存在一个全局的App类对象。通常都在App::InitInstance中创建一个Window类对象,并保存于App::m_pMainWnd。
App中创建窗口的过程形如:
BOOL AnimPlayerApp::InitInstance()
{
CWinApp::InitInstance();
m_pMainWnd = new MainWindow();
m_pMainWnd->ShowWindow( m_nCmdShow );
m_pMainWnd->UpdateWindow();
return TRUE;
}
而对于一个Window类而言,就需要处理该窗口的逻辑,主要包括创建该窗口之上的子控件,以及最重要的消息映射。
一个GUI程序最重要的是什么?最重要的是控件的消息处理。Qt有signal and slot机制,Swing AWT有listener机制。MFC也不复杂,MFC处理消息用的是消息映射机制。事情就是这样的,这样一看,当你觉得你掌握了Qt里的signal and slot,掌握了Swing/AWT中的listener,就掌握了这两个GUI库的话,那么,当你掌握了MFC中的消息映射机制(不必去翻映射机制背后的原理,就好比不必去自己实现一个signal and slot一样),那么你就掌握了MFC。但是MFC为什么看上去还要复杂(虽然其设计很差,我们这里暂且只看功能)?因为MFC还实现了其他很多技术。你只需要知道,MFC是尽可能地帮程序员做事,如果你不愿意,你当然可以自己从头干起。
那么我们谈谈Window类中的消息处理。要使用MFC的消息映射,只需要在需要捕获消息的类定义中加上消息处理函数,以及放上一个MFC里的宏即可。消息处理函数和一般的函数是一样的,为了看上去完美(配合MFC),就加上一个afx_msg,但是事实上afx_msg什么也不是。再次强调,消息处理函数和一般的函数没什么两样。
Window类的基类,例如CFrameWnd,或者更上一层的CWnd类,已经定义了一些消息处理函数,例如OnPaint这个响应窗口绘制的函数。MFC这样做是因为它想对一些事件进行默认处理(起码我这样认为,另:事件亦即消息)。当你想处理一些MFC内部已经默认处理的消息时,你可以重载这些默认的消息处理函数,例如OnPaint。而另一些消息,例如你自己创建的子控件会产生的消息,则需要你新定义。但是无论如何,当你声明了一个消息处理函数后,你都需要在另一个实现地方做另一些宏处理,然后再真正定义函数体。声明消息映射形如:
/// 消息映射处理
private:
afx_msg void OnPaint();
/// 菜单消息处理
afx_msg void OnFileOpen();
afx_msg void OnFileExit();
DECLARE_MESSAGE_MAP()
注意最后一句话 DECLARE_MESSAGE_MAP(),你不必去翻这句宏的具体定义,你只需要知道,不加上这一句,你什么也做不了。
然后在我所谓的实现部分,加上形如这样的语句:
BEGIN_MESSAGE_MAP( MainWindow, CFrameWnd )
ON_WM_PAINT()
ON_COMMAND( ID__OPENIMG, OnFileOpen )
ON_COMMAND( ID__EXIT, OnFileExit )
END_MESSAGE_MAP()
对于很多MFC自己定义了的消息处理函数,你只需要做类似于ON_WM_PAINT()的声明即可,不需要指定具体的消息处理函数(例如第二句ON_COMMAND(ID_OPENIMG, OnFileOpen)),
MFC始终会调用它自己规定的函数,例如OnPaint。而另一些消息,则需要你写上消息处理的函数名。每一种消息都有自己的消息处理函数形式,因为每一个消息处理时需要得到的信息不一样,信息通常都是由参数传递,这就导致了每一个消息处理函数的参数,返回值不一样。每一个消息具体该定义如何的消息处理函数,这个就是翻阅文档的事情了。
最后,你只需要定义消息处理函数即可。还记的吗?消息处理函数和一般的类成员函数一样。消息处理函数大部分时候都是被MFC内部调用(派发消息时),当然你也可以自己调用。
如果你会Qt,当看了以上我做的分析后,是不是会同我一样,有一种心旷神怡的感觉?MFC中的消息处理函数,就如同Qt中定义的slot函数一样。MFC中的宏,就如同Qt中的slots, signals一样,我让你不要去管MFC中的宏,就如同不要去管Qt中slots之类是如何实现一样。
话说到此,真感觉MFC不过如此。MFC不见得实现了多么厉害的技术,它只是一堆糟糕的OO设计,糟糕的设计才导致了复杂。但是无论如何,当你掌握了核心技术,其他的,也还是一种查文档的事罢了。但是,MFC除了消息处理它还实现了其他东西,而这些东西,当你需要的时候,你同样会觉得他们简单无比。
接下来记录一些控件之类的使用经验。
2. 对于Window类,创建窗口上子控件之类的活动最好放在OnCreate里,要修改窗口样式,在PreCreateWindow里,要创建自身(调用Create),就在构造函数里。如果创建自身放在PreCreateWindow或者OnCreate里,会引发断点。
3.使用菜单:要使用菜单,可以先使用菜单资源编辑器编辑好菜单(包括菜单ID值),在创建窗口时(即调用CFrameWnd::Create之类的函数),传菜单资源句柄即可。当菜单项没有加消息处理时,菜单项默认情况下是灰色的。当加了消息处理后,会自动有效。要使即使没加消息处理的菜单项成为有效,可以置CFrameWnd:: m_bAutoMenuEnable 为FALSE。
菜单消息处理函数通常都是 void Window::OnFunc()的形式。
消息映射里:ON_COMMAND( 菜单项ID, 菜单消息处理函数 ) 即可。
4. 使用滚动条:创建滚动条同大部分子控件一样,先构造一个CScrollBar对象,然后调用Create函数,注意在指定滚动条风格时要加上WS_CHILD以及WS_VISIBLE风格。
处理滚动条消息,只需要重载OnHScroll或者OnVScroll。对应的消息映射宏是:
ON_WM_HSCROLL() 和 ON_WM_VSCROLL()
关于滚动条消息处理函数的实现,大部分时候需要手动设置滚动条thumb的位置。
5.使用按钮:同滚动条一样,先构造一个CButton对象,然后调用Create函数,指定风格时依然要指定WS_CHILD以及WS_VISIBLE。处理消息时需要手动指定 ON_BN_CLICKED( ID_BTN_START, OnBtnStart ) 即ON_BN_CLICKED( 按钮ID,按钮消息处理函数)
6.使用定时器Timer,随时调用SetTimer来启动,KillTimer来停止。重载OnTimer来处理消息,对应ON_WM_TIMER映射宏。