Visual C++ MFC/ATL开发-提高篇

MFC简单打印方法
通过MFC来完成打印作业有很多的方法,应用场合也有所不同。我们通常是利用视图框架在MFC基础之上按照OnPreparePrinting() à OnBeginPrinting() ? à OnPreprareDC() à OnPrinting () à OnEndPrinting()的顺序来编程的。对于我们使用单文档或者多文档的视图框架时这无疑是一种很好的方式。但是,在基于对话框的应用程序中,或者在很多打印要求不高的情况下(如只是简单的打印中间计算结果、报告程序异常等),我们就没有必要再绕上面的那么一个大弯子,可以直接利用MFC封装的打印对话框CPrintDialog来执行简单的打印任务。当然,如果你不怕烦,这种方式也可以执行很复杂的打印作业。

  下面做一个简单的例子程序。利用Visual C++ 6.0 的AppWizard生成一个对话框应用程序框架。然后,利用资源编辑器添加一个Edit控件,属性设置为:选择Multiline、Want Return ,根据你的兴趣选择滚动条有关的项,绑定成员变量m_strPrintString。使“确定(OK)”按钮不接受程序的“Enter”键消息,Caption改为“打印(Print)”。此键按下的处理函数如下:

if (!UpdateData(TRUE))

{

AfxMessageBox("数据交换有误!");

}

if (m_strPrintString==_T(""))

{

AfxMessageBox("请输入需要打印的文字?quot;);

return;

}

CPrintDialog dlg(FALSE,

PD_NOPAGENUMS|PD_NOSELECTION,

this);

if(dlg.DoModal() == IDOK)

{

CDC dc;

dc.Attach(dlg.GetPrinterDC());
//把打印设备环境附加到DC对象

DOCINFO di; 

di.cbSize = sizeof(DOCINFO);

di.lpszDocName = "SimplePrintDoc";

di.lpszOutput = NULL;

di.lpszDatatype = NULL;

di.fwType = 0;

dc.StartDoc(&di); 
//通知打印机驱动程序执行一新的打印任务

dc.StartPage();//通知打印机驱动程序打印新页

dc.SetMapMode(MM_HIENGLISH);//设置当前影射模式为:单位0.001英寸

//X方向向右增加,Y方向向上增加

CRect rectPrint(0, 0,

dc.GetDeviceCaps(HORZRES),
//返回设备的.以毫米为单位的物理显示宽度 

dc.GetDeviceCaps(VERTRES));
//返回设备的.以毫米为单位的物理显示高度

dc.DPtoLP(&rectPrint);//设备物理单位转化为逻辑单位

dc.SetWindowOrg(0, -rectPrint.bottom);//设置原点

CFont font;

VERIFY(font.CreatePointFont(120, 

"Arial", 

&dc));
//为DC创建字体

CFont* def_font = dc.SelectObject(&font);//保存现在的字体

dc.SetTextAlign(TA_TOP|TA_LEFT);

CString s = m_strPrintString; 
//要打印的字符串(注意有长度的限制)


s += "\n"; //必须增加一个换行符号(因为后面while循环的要求)

CString ss;

int index;

CSize size;

int x = 300;

int y = 9000;
//注意原点位置和坐标增加的方向

size = dc.GetTextExtent("00", 2);//计算使用当前字体输出时文本所占大小、宽 度

while((index = s.Find("\n")) != -1)

{

ss = s.Left(index);

if(ss.Find("\r") != -1)
//输入的字符串有回车符

ss = s.Left(index-1);

s = s.Mid(index+1);
//取剩余的字符串 // AfxMessageBox("A"+ss+"A");

dc.TextOut(x, y, ss);//打印到缓冲区

y -= size.cy;

}


dc.SelectObject(def_font); 
//恢复原来的字体

font.DeleteObject(); 

dc.EndPage();
 //通知打印机驱动程序页结束

dc.EndDoc();//通知打印机驱动程序打印完毕

DeleteDC(dc.Detach()); 

}

  程序代码很简单,一看就明白,我想应该适合大多数的简单打印场合吧。另外,这个例子对于初学Windows编程的朋友来说,无疑也是一个理解Windows设备无关特性的好实例。

MFC文件浏览程序中改变文件读写对话框样式

在我告诉大家怎么样修改MFC浏览程序中文件读写对话框的缺省设置之前呢,我们应该先知道MFC是怎么样显示文件打开和文件保存对话框的。如果你选择了文件菜单中的打开,那么就会有一个消息发送给CWinApp::OnFileOpen,通过一个成员变量m_pDocManager(一个指向CDocManager 对象的指针)来调用CDocManager::OnFileOpen,那么函数就会调用CDocManager的虚函数DoPromptFileName,成功后再调用CWinApp::OpenDocumentFile函数,这个文件对话框就是在DoPromptFileName的虚函数中显示的。当我们打开的是保存对话框时,文件保存(或另存为)命令消息就会发给CDocument::OnFileSave (or CDocument::OnFileSaveAs),在这2种情况下,CDocument::DoSave函数都会被调用。最后,如果文件名是空的,那么CDocument::DoSave就会调用CWinApp::DoPromptFileName,使得成员变量m_pDocManager有效,并且调用CDocManager::DoPromptFileName来显示保存对话框。 
  那么现在大家都明白了,CDocManager::DoPromptFileName函数(注意它是一个虚函数)就是负责显示标准的文件打开和保存对话框的(可是有一个BOOL变量来决定显示哪一个对话框的)。
  现在看起来,这好象对改变默认的对话框设置没什么用。你可能也没有考虑过在类CDocManager中的DoPromptFileName函数,或者不知道怎么改,来使用你自己的CDocManager类。那么你不用急,下面的代码就是告诉你怎么自定义CDocManager。
// CDocManager class declaration
// 
class CDocManagerEx : public CDocManager
{
  DECLARE_DYNAMIC(CDocManagerEx) 
// Construction
public:
  CDocManagerEx(); 
// Attributes
public: 
// Operations
public: 
// Overrides
  // helper for standard commdlg dialogs
  virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,
      DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate); 
// Implementation
public:
  virtual ~CDocManagerEx();
}; 

// DocManager.cpp : implementation file
// 
#include "stdafx.h"
#include "PreviewFileDlg.h"
#include "DocManager.h" // the header with the class declaration 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif 
static void AppendFilterSuffix(CString& filter, OPENFILENAME& ofn,
  CDocTemplate* pTemplate, CString* pstrDefaultExt)
{
  ASSERT_VALID(pTemplate);
  ASSERT_KINDOF(CDocTemplate, pTemplate); 
  CString strFilterExt, strFilterName;
  if (pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt) &&
  !strFilterExt.IsEmpty() &&
  pTemplate->GetDocString(strFilterName, CDocTemplate::filterName) &&
  !strFilterName.IsEmpty())
  {
    // a file based document template - add to filter list
#ifndef _MAC
    ASSERT(strFilterExt[0] == '.');
#endif
    if (pstrDefaultExt != NULL)
    {
      // set the default extension
#ifndef _MAC
      *pstrDefaultExt = ((LPCTSTR)strFilterExt) + 1; // skip the '.'
#else
      *pstrDefaultExt = strFilterExt;
#endif
      ofn.lpstrDefExt = (LPTSTR)(LPCTSTR)(*pstrDefaultExt);
      ofn.nFilterIndex = ofn.nMaxCustFilter + 1; // 1 based number
    } 
    // add to filter
    filter += strFilterName;
    ASSERT(!filter.IsEmpty()); // must have a file type name
    filter += (TCHAR)'\0'; // next string please
#ifndef _MAC
    filter += (TCHAR)'*';
#endif
    filter += strFilterExt;
    filter += (TCHAR)'\0'; // next string please
    ofn.nMaxCustFilter++;
  }

/////////////////////////////////////////////////////////////////////////////
// CDocManagerEx 
IMPLEMENT_DYNAMIC(CDocManagerEx, CDocManager)
CDocManagerEx::CDocManagerEx()
{

CDocManagerEx::~CDocManagerEx()
{

BOOL CDocManagerEx::DoPromptFileName(CString& fileName, UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate)
{
  CPreviewFileDlg dlgFile(bOpenFileDialog); // this is the only modified line! 
  CString title;
  VERIFY(title.LoadString(nIDSTitle)); 
  dlgFile.m_ofn.Flags |= lFlags;
  CString strFilter;
  CString strDefault;
  if (pTemplate != NULL)
  {
    ASSERT_VALID(pTemplate);
    AppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate, &strDefault);
  }
  else
  {
    // do for all doc template
    POSITION pos = m_templateList.GetHeadPosition();
    BOOL bFirst = TRUE;
    while (pos != NULL)
    {
      CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);
      AppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate,
        bFirst ? &strDefault : NULL);
      bFirst = FALSE;
    }
  } 
  // append the "*.*" all files filter
  CString allFilter;
  VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER));
  strFilter += allFilter;
  strFilter += (TCHAR)'\0'; // next string please
#ifndef _MAC
  strFilter += _T("*.*");
#else
  strFilter += _T("****");
#endif
  strFilter += (TCHAR)'\0'; // last string
  dlgFile.m_ofn.nMaxCustFilter++; 
  dlgFile.m_ofn.lpstrFilter = strFilter;
#ifndef _MAC
  dlgFile.m_ofn.lpstrTitle = title;
#else
  dlgFile.m_ofn.lpstrPrompt = title;
#endif
  dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH); 
  BOOL bResult = dlgFile.DoModal() == IDOK ? TRUE : FALSE;
  fileName.ReleaseBuffer();
  return bResult;

  以上代码是从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()
{
  // Standard initialization
  // If you are not using these features and wish to reduce the size
  // of your final executable, you should remove from the following
  // the specific initialization routines you do not need. 
#ifdef _AFXDLL
  Enable3dControls();      // Call this when using MFC in a shared DLL
#else
  Enable3dControlsStatic();  // Call this when linking to MFC statically
#endif 
  // Change the registry key under which our settings are stored.
  // You should modify this string to be something appropriate
  // such as the name of your company or organization.
  SetRegistryKey(_T("Local AppWizard-Generated Applications")); 
  LoadStdProfileSettings(); // Load standard INI file options (including MRU)
  // Register the application's document templates. Document templates
  // serve as the connection between documents, frame windows and views. 
  CMultiDocTemplate* pDocTemplate;
  pDocTemplate = new CMultiDocTemplate(
    IDR_DIBTYPE,
    RUNTIME_CLASS(COurDoc),
    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
    RUNTIME_CLASS(COurView));
  ASSERT(m_pDocManager == NULL);
  m_pDocManager = new CDocManagerEx;
  m_pDocManager->AddDocTemplate(pDocTemplate); // or just AddDocTemplate(pDocTemplate);

  // create main MDI Frame window
  CMainFrame* pMainFrame = new CMainFrame;
  if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
    return FALSE;
  m_pMainWnd = pMainFrame; 
  // Enable drag/drop open
  m_pMainWnd->DragAcceptFiles(); 
  // Enable DDE Execute open
  EnableShellOpen();
  RegisterShellFileTypes(TRUE); 
  // Parse command line for standard shell commands, DDE, file open
  CCommandLineInfo cmdInfo;
  ParseCommandLine(cmdInfo); 
  // Dispatch commands specified on the command line
  if (!ProcessShellCommand(cmdInfo))
    return FALSE; 
  // The main window has been initialized, so show and update it.
  pMainFrame->ShowWindow(m_nCmdShow);
  pMainFrame->UpdateWindow(); 
  return TRUE;


VC++5的MFC中为程序添加弹出式菜单

弹出式菜单(PopMenu)大家都熟悉,在WIN98的桌面上单击鼠标右键弹出的菜单就是弹出式菜单。通常情况下,弹出式菜单在鼠标右键单击时弹出,当然,也可以根据需要随时弹出。 在VC++5的MFC中,管理菜单的类叫CMenu。下面我向大家介绍建立一个弹出式菜单的基本方法。 一、在资源编辑器中建立一个菜单资源 
 
  新建一个菜单资源,比如把菜单的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中的几种重要的消息映射处理.但基于篇幅有限没能作更全面更深入的探讨. 

你可能感兴趣的:(C++)