那么现在大家都明白了,CDocManager::DoPromptFileName函数(注意它是一个虚函数)就是负责显示标准的文件打开和保存对话框的(可是有一个BOOL变量来决定显示哪一个对话框的)。
现在看起来,这好象对改变默认的对话框设置没什么用。你可能也没有考虑过在类CDocManager中的DoPromptFileName函数,或者不知道怎么改,来使用你自己的CDocManager类。那么你不用急,下面的代码就是告诉你怎么自定义CDocManager。
// CDocManager class declaration class CDocManagerEx : public CDocManager // Construction // Attributes // Operations // Overrides // Implementation
#include "stdafx.h" #ifdef _DEBUG static void AppendFilterSuffix(CString& filter, OPENFILENAME& ofn, CString strFilterExt, strFilterName; // add to filter ///////////////////////////////////////////////////////////////////////////// IMPLEMENT_DYNAMIC(CDocManagerEx, CDocManager) CDocManagerEx::CDocManagerEx() CDocManagerEx::~CDocManagerEx() BOOL CDocManagerEx::DoPromptFileName(CString& fileName, UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate) CString title; dlgFile.m_ofn.Flags |= lFlags; CString strFilter; // append the "*.*" all files filter dlgFile.m_ofn.lpstrFilter = strFilter; BOOL bResult = dlgFile.DoModal() == IDOK ? TRUE : FALSE; |
以上代码是从MFC原代码中完整的拷贝过来的,当中只有一行需要改:对话框的声明(当然,由于这是CFileDialog的子类,你可以有更多的修改权),而且对于标准对话框来说,还应该有预览功能。AppendFilterSuffix函数是从DoPromptFileName中被调用的,原代码可以从你的工程中获得。如果你想显示打开和保存对话框,你可以使用bOpenFileDialog参数,TRUE表示对话框是打开对话框,反之,亦然。
当然我们也可以使用我们自己定义的CDocManagerEx类来代替原来的CDocManager类,CWinApp是通过成员变量m_pDocManager来使用文档管理器,所以我们必须正确的初始化这个变量。有必要特别关注一下在CWinApp::AddDocTemplate中CWinApp创建对象的原代码,这其中有一个很普通的函数调用,我们把它给忽略了,那就是CWinApp::InitInstance函数。当m_pDocManager参数是NULL的时候,CWinApp::AddDocTemplate函数只是创建CDocManager对象,那么一旦m_pDocManager指针被正确的初始化,CWinApp::AddDocTemplate函数也就能够被正确的调用了。因此,这最后一步就是在调用CWinApp::AddDocTemplate之前就初始化成员变量m_pDocManager,以达到我们想要的样子。(当然你也不一定得调用CWinApp::AddDocTemplate,而是直接调用m_pDocManager->AddDocTemplate也行)
以下就是代码:
BOOL COurApp::InitInstance() #ifdef _AFXDLL // Change the registry key under which our settings are stored. LoadStdProfileSettings(); // Load standard INI file options (including MRU) // Register the application's document templates. Document templates CMultiDocTemplate* pDocTemplate; // Enable drag/drop open // Enable DDE Execute open // Parse command line for standard shell commands, DDE, file open // Dispatch commands specified on the command line // The main window has been initialized, so show and update it. return TRUE; |
一、在资源编辑器中建立一个菜单资源
新建一个菜单资源,比如把菜单的ID号为IDC_POPMENU。此菜单有一项两层,即有一个可弹出的菜单项,而此菜单项的弹出内容即为将要建立的弹出式菜单的内容。如右图,“可弹出项”下的菜单即为将要建立的弹出式菜单的内容。实际上,“可弹出项”这个名称在以后的操作中不会被用到,但VC++5不允许直接建立弹出式菜单,所以采用先建立“可弹出项”的方法。
至于每一个菜单项的消息映射,与一般的菜单相同。
二、使用CMenu类对象
CMenu类的成员函数较多,但建立弹出式菜单只需用到其中几个成员函数。
1、LoadMenu函数
原型:BOOL LoadMenu( UINT nIDResource );
其中nIDResource是菜单资源的ID号,这里用的是刚建立的IDC_POPMENU。
2、GetSubMenu函数
原型:CMenu* GetSubMenu( int nPos ) const;
此函数用于得到子菜单的指针。nPos为层数,0为第一层子菜单……以此类推。
由于我们需要的是“可弹出项”的第一层子菜单,因此用GetSubMenu(0)来得到第一层子菜单的类指针。
3、TrackPopupMenu函数
原型:BOOL TrackPopupMenu( UINT nFlags,int x,int y,CWnd* pWnd,LPCRECT lpRect = NULL );
其中:
nFlags为屏幕坐标属性和鼠标坐标属性
屏幕坐标属性:
TPM_CENTERALIGN 横向将菜单以x居中
TPM_LEFTALIGN 横向将菜单以x左对齐
TPM_RIGHTALIGN 横向将菜单以x右对齐
鼠标按键属性(只在响应WM_CONTEXTMENU消息时有效):
TPM_LEFTBUTTON 连续按? 右键不会连续弹出菜单,鼠标右键不可用于选定菜单项
TPM_RIGHTBUTTON 连续按鼠标右键会连续弹出菜单,鼠标右键可用于选定菜单项
x,y均为屏幕坐标
lpRect 菜单所占的区域。如果为NULL,当用户在菜单以外的区域按鼠标键时,菜单会消失
三、实例
1、当鼠标右键单击程序窗口的客户区时,程序会收到一条WM_CONTEXTMENU消息,此时是弹出菜单的最好时机
用ClassWizard中的“Add Windows Message Handler”功能添加对WM_CONTEXT消息的响应函数,函数中代码如下:
void CMyDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMenu menu; //定义CMenu类对象
menu.LoadMenu(IDC_POPMENU); //装入刚建立的菜单IDC_POPMENU menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,pWnd);
/*GetSubMenu(0)得到IDC_POPMENU的第一层子菜单,TrackPopupMenu将菜单弹出到(x,y)处。由于设置为TPM_LEFTALIGN,所以菜单以(x,y)为左上角。*/
}
2、在其他时候弹出菜单也可以,比如,可以响应WM_LBUTTONDOWN消息。这样,在鼠标左键单击时也能弹出菜单
用ClassWizard中的“Add Windows Message Handler”功能添加对WM_LBUTTONDOWN消息的响应函数,函数中代码如下:
void CMfc5Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CMenu menu; //定义CMenu类对象 menu.LoadMenu(IDC_POPMENU); //装入刚建立的菜单IDC_POPMENU ClientToScreen(&point); menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this);
/*GetSubMenu(0)得到IDC_POPMENU的第一层子菜单,TrackPopupMenu将菜单弹出到(x,y)处。由于设置为TPM_LEFTALIGN,所以菜单以(x,y)为左上角。*/
ScreenToClient(&point);
CDialog::OnLButtonDown(nFlags, point);
}
注意:在WM_LBUTTONDOWN消息中得到的point对象所存的坐标是相对于窗口客户区的,而TrackPopupMenu中的x,y需要是相对于屏幕的,所以需用ClientToScreen函数进行转换,但此消息响应函数要调用CDialog::OnLButtonDown(nFlags, point),故应该用ScreenToClient函数将point所存的坐标还原为相对于窗口客户区的。
谈谈MFC中的消息映射
引言:
众所周知,windows是基于消息驱动的,作好消息处理是WINDOWS编程的关键任务之一,用VC制作WINDOWS程式同样离不开消息的处理。虽然VC++6的类向导可以完成绝大部分工作,但不幸的是,它并不能完成所有的工作。这就要求我们对 VC中消息的处理有一个比较清淅的认识。只有这样才可能在必要的时候亲自动手完成一些复杂的消息映射处理。
在MFC中消息是通过一种所谓的消息映射机制来处理的。其实质是一张消息及其处理函数的一一对应表以及分析处理这张表的应用框架内部的一些程序代码.这样的好处是可以避免像早期的SDK编程一样需要罗列一大堆的CASE语句来处理各种消息.由于不同种类的消息其处理方法是不同的,所以我们有必要先弄清楚 WINDOWS消息的种类。
背景:
WINDOWS 消息的种类
WINDOWS中消息主要有以下三种类型:
1、标准的WINDOWS消息:这类消息是以WM_为前缀,不过WM_COMMAND例外。 例如: WM_MOVE、WM_QUIT等.
2、命令消息:命令消息以WM_COMMAND为消息名.在消息中含有命令的标志符ID,以区分具体的命令.由菜单,工具栏等命令接口对象产生.
3、控件通知消息:控件通知消息也是以WM_COMMAND为消息名.由编辑框,列表框,子窗口发送给父窗口的通知消息.在消息中包含控件通知码.以区分具体控件的通知消息.
其中标准的WINDOWS消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理.相对标准WINDOWS消息及控件通知消息而言,命令消息的处理对象范围就广得多.它不仅可以由窗口类处理,还可以由文档类,文档模板类及应用类所处理。
方法:
不同种类消息的映射方法。
在以上三种消息中,标准的WINDOWS消息映射是相当简单的。可直接通过类向导完成不同消息的映射处理,所以不在本文讨论之列。
凡是从CcmdTarget类派生的类都可以有消息映射.消息映射包括如下两方面的内容:
在类的定义文件中(.H)中加上一条宏调用:
DECLARE_MESSAGE_MAP()
通常这条语句中类定义的最后.
在类的实现文件(.CPP)中加上消息映射表:
BEGIN_MESSAGE_MAP(类名,父类名)
………..
消息映射入口项.
……….
END_MESSAGE_MAP( )
幸运的是除了某些类(如没有基类的类或直接从CobjectO类派生的类)外.其它许多类均可由类向导生成.尽管生成的类只是一个框架,需要我们补充内容.但消息映射表已经为我们加好了.只是入口项有待我们加入.
命令消息映射入口项是一个ON_COMMAND的宏.比如文件菜单下的"打开…"菜单(ID值为ID_FILE_OPEN)对应的消息映射入口项为:
ON_COMMAND(ID_FILE_NEW,OnFileOpen)
加入消息映射入口项之后需要完成消息处理函数.在类中消息处理函数都是类的成员函数,要响应一个消息,就必须定义一个该消息的处理函数.定义一个消息处理函数包括以下三方面的内容.
1.在类定义中加入消息处理函数的函数原型(函数声明)
2.在类的消息映射表中加入相应的消息映射入口项.
3.在类的实现中加入消息处理函数的函数体.
需要说明的是消息处理函数的原型一定要以afx_msg打头.比如:
afx_msg OnFileOpen();// 函数原型
作为约定.消息处理函数一般以On打头
但有时我们可能想用一个消息处理函数来处理一批消息。这时类向导就无能为力了。我们必须手工加入消息映射来完成这种工作。可用如下方法实现:
首先在处理该消息所在类的实现文件(亦即.CPP)中加入的消息映射入口:
...
BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
file://{{AFX_MSG_MAP(CMyApp)
...
file://}}AFX_MSG_MAP
ON_COMMAND_RANGE(ID_MYCMD_ONE, ID_MYCMD_TEN, OnDoSomething)
END_MESSAGE_MAP( )
...
粗体标志的语句是我们加入的语句(以后约定我们加入的语句均用粗体标志).其中我们使用了宏ON_COMMAND_RANGE来实现从命令消息ID_MYCMD_ONE到 ID_MYCMD_TEN都由OnDoSomthing一个消息函数处理.注意.ID_MYCMD_ONE到 ID_MYCMD_TEN的ID值一定要连续.且ID_MYCMD_ONE值一般较小.
完成上述工作之后我们还需要在该类的头文件(亦即.H)中加入消息处理函数的申明:
// Generated message-map functions
protected:
file://{{AFX_MSG(CMyApp)
...
file://}}AFX_MSG
afx_msg void OnDoSomething( UINT nID );
DECLARE_MESSAGE_MAP()
由于这不是VC类向导加入的函数申明,所以放在了//}}AFX_MSG之外.
注意这个消息处理函数有一个UINT类型参数.而处理单一命令的消息处理函数一般是没有参数(除更新用户接口对象状态命令消息处理函数).这个参数的主要作用是提供用户选择的命令的ID值.
最后要做的工作就是在该类的实现文件中实现该消息处理函数. 同样,有时我们也想使用一个消息处理函数处理一批更新用户接口对象状态命令消息.方法同上:
首先在.CPP文件中加入语句如下:
...
BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
file://{{AFX_MSG_MAP(CMyApp)
...
file://}}AFX_MSG_MAP
ON_UPDATE_COMMAND_UI_RANGE (ID_MYCMD_ONE, ID_MYCMD_TEN, OnUpdateSomething)
END_MESSAGE_MAP( )
...
在该类的头文件(亦即.H)中加入消息处理函数的申明:
// Generated message-map functions
protected:
file://{{AFX_MSG(CMyApp)
...
file://}}AFX_MSG
afx_msg void OnUpdateSomething( CcmdUI * pcmdui );
DECLARE_MESSAGE_MAP()
请各位注意了,仔细的读者已经注意到这里的消息处理函数并未像命令消息处理函数需要一个额外的UINT类型的参数.原因在于pcmdui中已包含了此信息.所以不再需要这个参数了.最后不要忘了完成函数体!
关于命令消息就讨论到这个地方.接下来讨论控件通知消息.
控件通知消息相对而言就复杂一点了.限于篇幅不能一一涉及.这里我们仅讨论 WM_NOTIFY消息的处理.
WM_NOTFY产生的原因如下。
在WINDOWS3.X中控件通知它们父窗口,如鼠标点击,控件背景绘制事件,通过发送一个消息到父窗口.简单的通知仅发送一个WM_COMMAND消息.包含一个通知码(比如BN_CLICKED)和一个在wParam中的控件ID及一个在lPraram中的控件句柄.因为wParam 和lParam均被使用.就没有方法传送其它的附加信息了.比如在BN_CLICKED 通知消息中.就没有办法发送关于当鼠标点击时光标的位置信息.在这种情况下就只能使用一些特殊的消息.包括:WM_CTLCOLOR,WM_VSCROLL, WM_HSCROLL等等.值得一提的是这些消息能被反射回发送它们的控件.就是所谓的消息反射.有兴趣的读者请参阅有关专著.
在WIN32中同样可以使用那些在WINDOWS3.1中使用的通知消息.不过不像过去通过增加特殊目的的消息来为新的通知发送附加的数据.而是使用一个叫 WM_NOTIFY的消息,它能以一个标准的风格传送大量的附加数据.
WM_NOTIFY消息包含一个存在wParam中的发送消息控件的ID和一个存在 lParam中的指向一个结构体的指针.这个结构可能是NMHDR结构体.也可能是第一个成员是NMHDR的更大的结构.因为NMHDR是第一个成员,所以指向这个结构的指针也可以指向NMHDR.
在许多情况下,这个指针将指向一个更大的结构,当你使用时必需转换它.只有很少的通知消息.比如通用通知消息(它的名字以NM_打头),工具提示控件的 TTN_SHOW和TTN_POP实际上在使用NMHDR结构.
NMHDR结构包含了发送消息控件的句柄,ID及通知码(如TTN_SHOW),其格式如下:
Typedef sturct tagNMHDR{
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
对TTN_SHOW消息而言,code成员的值将设为TTN_SHOW.
类向导可以创建ON_NOTIFY消息映射入口并为你提供一个处理函数的框架.来处理 WM_NOTIFY类型的消息.ON_NOTIFY消息映射宏有如下语法.
ON_NOTIFY(wNotifyCode,id,memberFxn)
数意义如下:
wNotifyCode:要处理的通知消息通知码。比如:LVN_KEYDOWN.
Id:控件标识ID.
MemberFxn:处理此消息的成员函数.
此成员函数必需有如下的原形申明:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);
比如:假设你想成员函数OnKeydownList1处理ClistCtrl(标识ID=IDC_LIST1)的 LVN_KEYDOWN消息,你可以使用类向导添加如下的消息映射:
ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )
在上面的例子中,类向导提供如下函数:
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
// TODO: Add your control notification handler
// code here
*pResult = 0;
}
这时类向导提供了一个适当类型的指针.你既可以通过pNMHDR,也可以通过 pLVKeyDow来访问这个通知结构。
如前所述,有时我们可能需要为一组控件处理相同的WM_NOTIFY消息.这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY.当你使用 ON_NOTIFY_RANGE时,你需要指定控件的ID范围.其消息映射入口及函数原型如下:
ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
参数说明:
wNotifyCode:消息通知码.比如:LVN_KEYDOWN,
id: 第一控件的标识ID。
idLast:最后一个控件的标识ID。(标识值一定要连续)
memberFxn: 消息处理函数。
成员函数必须有如下原型申明:
afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );
其中id的表示发送通知消息的控件标识ID
结束语:
于目前介绍MFC消息映射的资料甚少.而这部分内容对编程又相当重要.本文简要地介绍了MFC中的几种重要的消息映射处理.但基于篇幅有限没能作更全面更深入的探讨.