第四部分:消息映射
  应用程序放在窗口中的任何用户界面对象都具有两种可控制的特性:1) 它的外观,2) 它响应事件的行为。在上一讲中,你已经学习了CStatic控制和如何使用样式属性来定制用户界面对象的外观。这些概念可用于MFC中的所有不同控制类。
  在本讲中,我们将介绍CButton控制,以理解消息映射和简单的事件处理。然后还要介绍使用CScrollBar控制的稍微复杂点的例子。
  理解消息映射
  在第二讲中,MFC程序不包括主要函数或时间循环。所有的事件处理都是作为CWinApp的一部分在后台处理的。因为它们是隐藏的,所以我们需要一种方法来告诉不可见的时间循环通告我们应用程序所感兴趣的事件。这需要一种叫做消息映射的机制。消息映射识别感兴趣的事件然后调用函数来响应这些事件。
  例如,如果你要编写一个程序,当用户按下标有“退出”的按钮时要退出应用程序。在程序中,你编写代码来建立按钮:你指示按钮应如何动作。然后,为其父窗口建立用户单击按钮时的消息映射,它试图要传递消息给其父窗口。为了建立父窗口的消息,你要建立截取消息映射的机制,并且使用按钮的消息。当一指定的按钮事件发生时,消息映射会请求MFC调用一指定的函数。在这种情况下,单击退出按钮就是所感兴趣的事件。然后你把退出应用程序的代码放到指定的函数中。
  其它的工作就由MFC来做了。当程序执行时,用户单击“退出”按钮时,按钮就会自己加亮。然后MFC自动调用相应的函数,并且程序会终止。只使用很少的几行代码你就响应了用户事件。
  CButton类
  在上一讲中所讨论的CStatic控制是唯一不响应用户时间的控制。Windows中所有的其它控制都可响应用户事件。第一,当用户处理它们时,它们会自动更新其外观(例如,当用户单击按钮时,按钮会自己加亮以给用户一个反馈)。第二,每个不同的控制都要发送信息给你的代码以使程序能响应用户的需要。例如,当单击按钮时,按钮就会发送一个命令消息。如果你编写代码接收消息,则你的代码就能响应用户事件。
  为了理解这个过程,我们从CButton控制开始。下面的代码说明了建立按钮的过程:
  // button1.cpp
  #include 
  #define IDB_BUTTON 100
  // Declare the application class
  class CButtonApp : public CWinApp
  {
  public:
  virtual BOOL InitInstance();
  };
  // Create an instance of the application class
  CButtonApp ButtonApp; 
  // Declare the main window class
  class CButtonWindow : public CFrameWnd
  { 
  CButton *button;
  public:
  CButtonWindow();
  };
  // The InitInstance function is called once
  // when the application first executes
  BOOL CButtonApp::InitInstance()
  {
  m_pMainWnd = new CButtonWindow();
  m_pMainWnd->ShowWindow(m_nCmdShow);
  m_pMainWnd->UpdateWindow();
  return TRUE;
  }
  // The constructor for the window class
  CButtonWindow::CButtonWindow()
  { 
  CRect r;
  // Create the window itself
  Create(NULL, 
  "CButton Tests", 
  WS_OVERLAPPEDWINDOW,
  CRect(0,0,200,200));
  // Get the size of the client rectangle
  GetClientRect(&r);
  r.InflateRect(-20,-20);
  // Create a button
  button = new CButton();
  button->Create("Push me",
  WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  r,
  this,
  IDB_BUTTON);
  }
  上面的代码与前面介绍的代码几乎相同。CButton类的Create函数共有5个参数。前四个与CStatic的相同。第五个参数为按钮的资源ID。资源ID是用来标识消息映射中按钮的唯一整数值。常数值IDB_BUTTON已经在程序的顶部做了定义。“IDB_”是任选的,只是该常量ID是用来表示按钮的。它的值为100,因为100以内的值都为系统所保留。你可以使用任何大于99的值。
  CButton类所允许的样式属性与CStatic类的是不同的。定义了11个不同的“BS”(“Button Style”)常量。完整的“BS”常量列表可在用Search命令查找CButton,并选择“button style”。这里我们要用的是BS_PUSHBUTTON样式,它表示我们要一正常的的按钮方式来显示该按钮。我们还使用了两个熟悉的“WS”属性: WS_CHILD和WS_VISIBLE。我们将在后面介绍其它一些样式。
  当你运行代码时,会注意到按钮响应了用户事件。既它加亮了。除此之外它没有做任何事情,因为我们还没有教它怎样去做。我们需要编写消息映射来使按钮做一些感兴趣的事情。
  建立消息映射
  下面的代码包含有消息映射,也包含有新的处理单击按钮的函数(当用户单击按钮时会响一下喇叭)。它只是前面代码的一个简单的扩充:
  // button2.cpp
  #include 
  #define IDB_BUTTON 100
  // Declare the application class
  class CButtonApp : public CWinApp
  {
  public:
  virtual BOOL InitInstance();
  };
  // Create an instance of the application class
  CButtonApp ButtonApp; 
  // Declare the main window class
  class CButtonWindow : public CFrameWnd
  { 
  CButton *button;
  public:
  CButtonWindow();
  afx_msg void HandleButton();
  DECLARE_MESSAGE_MAP() 
  };
  // The message handler function
  void CButtonWindow::HandleButton()
  {
  MessageBeep(-1);
  }
  // The message map
  BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
  ON_BN_CLICKED(IDB_BUTTON, HandleButton)
  END_MESSAGE_MAP()
  // The InitInstance function is called once
  // when the application first executes
  BOOL CButtonApp::InitInstance()
  {
  m_pMainWnd = new CButtonWindow();
  m_pMainWnd->ShowWindow(m_nCmdShow);
  m_pMainWnd->UpdateWindow();
  return TRUE;
  }
  // The constructor for the window class
  CButtonWindow::CButtonWindow()
  { 
  CRect r;
  // Create the window itself
  Create(NULL, 
  "CButton Tests", 
  WS_OVERLAPPEDWINDOW,
  CRect(0,0,200,200));
  // Get the size of the client rectangle
  GetClientRect(&r);
  r.InflateRect(-20,-20);
  // Create a button
  button = new CButton();
  button->Create("Push me",
  WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  r,
  this,
  IDB_BUTTON);
  }
  主要修改了三个方面: 
  CButtonWindow的类说明现在包含了一个新的成员函数和一个新的表示消息映射的宏。HandleButton函数是正常的C++函数,它通过afx_msg标签确定为消息处理函数。该函数需要一些特殊的约束,例如,它必须是void型并且它不能接收任何参数。DECLARE_MESSAGE_MAP宏建立了消息映射。函数和宏都必须是public型的。 
  HandleButton函数作为成员函数以同样的方式来建立。在该函数中,我们调用了Windows API中的MessageBeep函数。 
  用宏来建立消息映射。在代码中,你可以看见BEGIN_MESSAGE_MAP宏接收两各参数。第一个指定了使用消息映射的类的名称。第二个是基类。然后是ON_BN_CLICKED宏,接受两个参数控制的ID和该ID发送命令消息时所调用的函数。最后,消息映射用END_MESSAGE_MAP来结束。 
  当用户单击按钮时,它向其包含该按钮的父窗口发送了一个包含其ID的命令消息。那是按钮的缺省行为,这就是该代码工作的原因。按钮向其父窗口发送消息,是因为它是子窗口。父窗口截取该消息并用消息映射来确定所要调用的函数。MFC来安排,只要指定的消息一出现,相应的函数就会被调用。
  ON_BN_CLICKED消息是CButton发送的唯一感兴趣的消息。它等同于CWnd中的ON_COMMAND消息,只是一个更简单方便的同义词而已。
  改变大小的消息
  在上面的代码中,由于有了消息映射,从CFrameWnd继承来的应用程序窗口认出按钮有按钮产生的单击消息并响应之。加入消息映射的ON_BN_CLICKED宏指定了按钮的ID和窗口在接收到来自按钮的命令消息时应调用的函数。因为只要用户单击了按钮,按钮就会自动把其ID发送父窗口,这样才能允许代码正确地处理按钮事件。
  作为该应用程序的主窗口的框架窗口自己也有传递消息的能力。大约有100不同的消息可用,它们都是从CWnd类继承来的。从MFC帮助文件中浏览CWnd类的成员函数,你就会看到所有的这些消息。查看所有以“On”开头的成员函数。
  你可能已经注意到了,至今为止所有的代码都不能很好地处理尺寸变化。当窗口变化大小时,窗口的框架会做相应的调整,但是窗口中调的内容仍原处不动。可以通过处理尺寸变化的事件来更好的处理这一问题。任何窗口发送的消息之一就是变尺寸消息。该消息是当改变形状时发出的。我们可以使用该消息来控制框架中子窗口的大小,如下所示: 
  // button3.cpp
  #include 
  #define IDB_BUTTON 100
  // Declare the application class
  class CButtonApp : public CWinApp
  {
  public:
  virtual BOOL InitInstance();
  };
  // Create an instance of the application class
  CButtonApp ButtonApp; 
  // Declare the main window class
  class CButtonWindow : public CFrameWnd
  { 
  CButton *button;
  public:
  CButtonWindow();
  afx_msg void HandleButton();
  afx_msg void OnSize(UINT, int, int);
  DECLARE_MESSAGE_MAP() 
  };
  // A message handler function
  void CButtonWindow::HandleButton()
  {
  MessageBeep(-1);
  }
  // A message handler function
  void CButtonWindow::OnSize(UINT nType, int cx,
  int cy)
  {
  CRect r;
  GetClientRect(&r);
  r.InflateRect(-20,-20);
  button->MoveWindow(r);
  }
  // The message map
  BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
  ON_BN_CLICKED(IDB_BUTTON, HandleButton)
  ON_WM_SIZE()
  END_MESSAGE_MAP()
  // The InitInstance function is called once
  // when the application first executes
  BOOL CButtonApp::InitInstance()
  {
  m_pMainWnd = new CButtonWindow();
  m_pMainWnd->ShowWindow(m_nCmdShow);
  m_pMainWnd->UpdateWindow();
  return TRUE;
  }
  // The constructor for the window class
  CButtonWindow::CButtonWindow()
  { 
  CRect r;
  // Create the window itself
  Create(NULL, 
  "CButton Tests", 
  WS_OVERLAPPEDWINDOW,
  CRect(0,0,200,200));
  // Get the size of the client rectangle
  GetClientRect(&r);
  r.InflateRect(-20,-20);
  // Create a button
  button = new CButton();
  button->Create("Push me",
  WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  r,
  this,
  IDB_BUTTON);
  }
  为了理解上面的代码,从窗口的消息映射开始。你会发现入口ON_WM_SIZE。该入口表示消息映射是对来自CButtonWindow对象的变尺寸消息发生响应。变尺寸消息是当用户改变窗口的大小时产生的。该消息来自窗口本身,而不是作为ON_COMMAND消息由按钮向其父窗口发送的。这是因为窗口框架不是子窗口。
  要注意的是消息映射中的ON_WM_SIZE入口没有参数。你在MFC文档中CWnd类,消息映射中的ON_WM_SIZE入口总是调用OnSize函数,并且该函数必须接收三个参数。OnSize函数必须是消息映射所属类的成员函数,并且该函数必须用afx_msg来说明(正如上面在CButtonWindow的定义中所见到的一样)。