主题:MFC仍旧是精品五
如果你查看MFC文档,就会发现CWnd中有近100名为“On...”的函数。CWnd::OnSize是其中之一。所有这些函数都在消息映射中有形如ON_WM_对应的标签。例如,ON_WM_SIZE对应OnSize。ON_WM_入口不接收任何参数,如ON_BN_CLICKED一样。参数是假设的并自动传递给相应的如OnSize的“On...”函数。
重复一遍,因为它很重要: OnSize函数总是与消息映射中的ON_WM_SIZE入口想对应。你必须命名处理函数OnSize, 并且它必须接收三个参数。不同的函数的参数会有所不同。
上面的代码中在OnSize函数自身的内部,有三行代码修改了按钮在窗口中的尺寸。你可以在该函数中输入任何你想要的代码。
调用GetClientRect是为了恢复窗口用户区域的新尺寸。该矩形会被缩小,并调用按钮的MoveWindow函数。MoveWindow是从CWnd继承来的,改变尺寸和移动子窗口是在一步完成的。
当你执行上面改变窗口大小的程序时,你就会发现按钮自己能正确地改变大小。在代码中,变尺寸事件他国消息映射中的OnSize函数而产生一调用,它调用MoveWindow函数来改变按钮的大小。
窗口消息
查看MFC文档,你可以看到主窗口处理的各种各样的CWnd消息。有些与我们上面介绍的类似。例如,ON_WM_MOVE消息是当用户移动窗口时发送的消息,ON_WM_PAINT消息是当窗口的任何部分需要重画时发出的。至今为止,我们的所有程序,重画工作都是自动完成的,因为是控制自己来负责其外观。如果你自己使用GDI命令来在用户区域中绘制,应用程序就应负责重画工作。因此ON_WM_PAINT就变得重要了。
还有一些发送给窗口的事件消息更深奥。例如,你可以使用ON_WM_TIMER消息与SetTimer函数来使接收预先设置的时间间隔。下面的代码给出了该过程。当你运行该代码时,程序会每隔1秒钟鸣笛一声。你可以用其它更有用的功能来代替鸣笛。
// button4.cpp
#include
#define IDB_BUTTON 100
#define IDT_TIMER1 200
// 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);
afx_msg void OnTimer(UINT);
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);
}
// A message handler function
void CButtonWindow::OnTimer(UINT id)
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
ON_BN_CLICKED(IDB_BUTTON, HandleButton)
ON_WM_SIZE()
ON_WM_TIMER()
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));
// Set up the timer
SetTimer(IDT_TIMER1, 1000, NULL); // 1000 ms.
// 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);
}
在上面的程序内部,我们建立了一个按钮,如前所示,改变尺寸的代码没有变动。在窗口的构造函数中,我们添加了SetTimer函数的调用。该函数接收三个参数:时钟的ID(可以同时使用多个时钟,每次时钟关闭时都会把ID传递给所调用的函数),时间以毫秒为单位。在这里,我们向函数传送了NULL,以使窗口消息映射自己自动发送函数。在消息映射中,我们已经通知了ON_WM_TIMER消息,它会自动调用OnTimer函数来传递已经关闭了的时钟的ID。
当程序运行时,它每隔1毫秒鸣笛一声。每次时钟的时间增量流逝,窗口都会发送消息给自己。消息映射选择消息给OnTimer函数,它鸣笛。你可以在此放置更有用的代码。
滚动条控制
Windows用两种不同的方式来处理滚动条。一些控制,如编辑控制和列表控制,可以带有滚动条。在这种情况下,滚动条会被自动处理,不不要额外的代码来处理。
滚动条也可以作为单独的元件来使用。当这样使用时,滚动条就拥有独立的权力。你可以参见MFC参考手册中有关CScrollBar的有关章节。滚动条控制的建立与前面介绍的静态标签和按钮的一样。它有四个成员函数允许你设置和获取滚动条的位置和范围。
下面的代码演示了建立水平滚动条的过程和其消息映射:
// sb1.cpp
#include
#define IDM_SCROLLBAR 100
const int MAX_RANGE=100;
const int MIN_RANGE=0;
// Declare the application class
class CScrollBarApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CScrollBarApp ScrollBarApp;
// Declare the main window class
class CScrollBarWindow : public CFrameWnd
{
CScrollBar *sb;
public:
CScrollBarWindow();
afx_msg void OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar);
DECLARE_MESSAGE_MAP()
};
// The message handler function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CScrollBarWindow, CFrameWnd)
ON_WM_HSCROLL()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CScrollBarApp::InitInstance()
{
m_pMainWnd = new CScrollBarWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CScrollBarWindow::CScrollBarWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CScrollBar Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
// Create a scroll bar
sb = new CScrollBar();
sb->Create(WS_CHILD|WS_VISIBLE|SBS_HORZ,
CRect(10,10,r.Width()-10,30),
this,
IDM_SCROLLBAR);
sb->SetScrollRange(MIN_RANGE,MAX_RANGE,TRUE);
}
Windows会区分水平和垂直滚动条,同时还支持CScrollBar中一称为尺寸盒的控制。尺寸盒是一个小方块。它处于水平和垂直滚动条的交叉处,呀鼠标拖动它会自动改变窗口的大小。在后面的代码中你看到如何用Create函数的SBS_HORZ样式来建立一水平滚动条。在建立了滚动条之后,马上用SetScrollRange中的MIN_RANGE和MAX_RANGE龙个常数给出了滚动条的范围0~100(它们定义在程序的顶部)。
事件处理函数OnHScroll来自CWnd类。我们使用该函数是因为该代码建立了水平滚动条。对于垂直滚动条应使用OnVScroll。在代码中,消息映射与滚动函数相联系,并使滚动条在用户操作时发出鸣笛声。当你运行该程序时,你可以单击箭头、拖动滚动条上的小方块等等。每次操作都会出现鸣笛声,但是滚动条上的小方块实际上不会移动,因为我们还没有把它与实际的代码相关联。
每次滚动条调用OnHScroll时,你的代码都要确定用户的操作。在OnHScroll函数内部,你可以检验传递给处理函数的第一参数,如下所示。如果你与上面的代码一起使用,滚动条的小方块就会移动到用户操作的位置处。
// The message handling function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
int pos;
pos = sb->GetScrollPos();
switch ( nSBCode )
{
case SB_LINEUP:
pos -= 1;
break;
case SB_LINEDOWN:
pos += 1;
break;
case SB_PAGEUP:
pos -= 10;
break;
case SB_PAGEDOWN:
pos += 10;
break;
case SB_TOP:
pos = MIN_RANGE;
break;
case SB_BOTTOM:
pos = MAX_RANGE;
break;
case SB_THUMBPOSITION:
pos = nPos;
break;
default:
return;
}
if ( pos < MIN_RANGE )
pos = MIN_RANGE;
else if ( pos > MAX_RANGE )
pos = MAX_RANGE;
sb->SetScrollPos( pos, TRUE );
}
SB_LINEUP和SB_LINEDOWN的不同常数值在CWnd::OnHScroll函数文档中有介绍。上面的代码首先使用GetScrollPos函数来恢复滚动条的当前位置。然后使用开关语句来确定用户对滚动条的操作。SB_LINEUP 和SB_LINEDOWN常数值意味着垂直方向,但也可用于水平方向表示左右移动。SB_PAGEUP和SB_PAGEDOWN是用在用户单击滚动条时。SB_TOP和SB_BOTTOM用于当用户移动滚动条小方块到滚动条的顶部和底部。SB_THUMBPOSITION用于当用户拖动小方块到指定位置时。代码会自动调整位置,然后确保它在设置其新位置时仍然在范围内。一旦设置了滚动条,小方块就会移动到适当的位置。
垂直滚动条的处理也是类似的,只是要用OnVScroll函数中的SBS_VERT样式。
理解消息映射
消息映射结构只能用于MFC。掌握它和如何在你的代码中应用它是很重要的。
可能纯C++使用者会对消息映射产生疑问: 为什么Microsoft不用虚拟函数来替代消息映射?虚拟函数是MFC中处理消息映射的标准C++方式,所以使用宏DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP可能有些怪异。
MFC使用消息映射来解决虚拟函数的基本问题。参见MFC帮助文件中的CWnd类。它包含200多个成员函数,所有的成员函数当不使用消息映射时都是虚拟的。现在来看一下所有CWnd类的子类。MFC中大约有近30个类是以CWnd为基类的。这包括所有的可见控制如按钮、静态标签和列表。现在想象一下,MFC使用虚拟函数,并且你建立一应用程序包含有20个控制。CWnd中的200个虚拟函数中的每个都需要自己的虚拟函数表,并且一个控制的每个例程都应有一组200个虚拟函数与之关联。则程序可能就有近4,000个虚拟函数表在内存中,这对内存有限的机器来说是个大问题。因为其中的大部分是不用的。
消息映射复制了虚拟函数表的操作,但是它是基于需要的基础之上的。当你在消息映射中建立一个入口时,你是在对系统说,“当你看见一个特殊的消息时,请调用指定的函数”。只有这些函数实际上被重载到消息映射中,着就节省了内存和CPU的负担。
当你用DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP说明消息映射时,系统会通过你的消息映射选择所有的消息。如果消息映射处理了给定的消息,则你的函数会被调用,卸车也就停留在此。但是,如果你的消息映射中不包含某个消息的入口,则系统会把该消息发送第二个BEGIN_MESSAGE_MAP指定的类。那个类可能会也可能不会处理它,如此重复。最后,如果没有消息映射处理一给定的消息,该消息会到由一缺省的处理函数来处理。
结论
本讲中所介绍的所有消息映射处理概念可适用于Windows NT中所有的控制和窗口。在大部分情况下,你可以使用ClassWizard来安装消息映射的入口,它将在后面的有关ClassWizard、AppWizard和资源编辑器一文中介绍。
重复一遍,因为它很重要: OnSize函数总是与消息映射中的ON_WM_SIZE入口想对应。你必须命名处理函数OnSize, 并且它必须接收三个参数。不同的函数的参数会有所不同。
上面的代码中在OnSize函数自身的内部,有三行代码修改了按钮在窗口中的尺寸。你可以在该函数中输入任何你想要的代码。
调用GetClientRect是为了恢复窗口用户区域的新尺寸。该矩形会被缩小,并调用按钮的MoveWindow函数。MoveWindow是从CWnd继承来的,改变尺寸和移动子窗口是在一步完成的。
当你执行上面改变窗口大小的程序时,你就会发现按钮自己能正确地改变大小。在代码中,变尺寸事件他国消息映射中的OnSize函数而产生一调用,它调用MoveWindow函数来改变按钮的大小。
窗口消息
查看MFC文档,你可以看到主窗口处理的各种各样的CWnd消息。有些与我们上面介绍的类似。例如,ON_WM_MOVE消息是当用户移动窗口时发送的消息,ON_WM_PAINT消息是当窗口的任何部分需要重画时发出的。至今为止,我们的所有程序,重画工作都是自动完成的,因为是控制自己来负责其外观。如果你自己使用GDI命令来在用户区域中绘制,应用程序就应负责重画工作。因此ON_WM_PAINT就变得重要了。
还有一些发送给窗口的事件消息更深奥。例如,你可以使用ON_WM_TIMER消息与SetTimer函数来使接收预先设置的时间间隔。下面的代码给出了该过程。当你运行该代码时,程序会每隔1秒钟鸣笛一声。你可以用其它更有用的功能来代替鸣笛。
// button4.cpp
#include
#define IDB_BUTTON 100
#define IDT_TIMER1 200
// 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);
afx_msg void OnTimer(UINT);
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);
}
// A message handler function
void CButtonWindow::OnTimer(UINT id)
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
ON_BN_CLICKED(IDB_BUTTON, HandleButton)
ON_WM_SIZE()
ON_WM_TIMER()
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));
// Set up the timer
SetTimer(IDT_TIMER1, 1000, NULL); // 1000 ms.
// 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);
}
在上面的程序内部,我们建立了一个按钮,如前所示,改变尺寸的代码没有变动。在窗口的构造函数中,我们添加了SetTimer函数的调用。该函数接收三个参数:时钟的ID(可以同时使用多个时钟,每次时钟关闭时都会把ID传递给所调用的函数),时间以毫秒为单位。在这里,我们向函数传送了NULL,以使窗口消息映射自己自动发送函数。在消息映射中,我们已经通知了ON_WM_TIMER消息,它会自动调用OnTimer函数来传递已经关闭了的时钟的ID。
当程序运行时,它每隔1毫秒鸣笛一声。每次时钟的时间增量流逝,窗口都会发送消息给自己。消息映射选择消息给OnTimer函数,它鸣笛。你可以在此放置更有用的代码。
滚动条控制
Windows用两种不同的方式来处理滚动条。一些控制,如编辑控制和列表控制,可以带有滚动条。在这种情况下,滚动条会被自动处理,不不要额外的代码来处理。
滚动条也可以作为单独的元件来使用。当这样使用时,滚动条就拥有独立的权力。你可以参见MFC参考手册中有关CScrollBar的有关章节。滚动条控制的建立与前面介绍的静态标签和按钮的一样。它有四个成员函数允许你设置和获取滚动条的位置和范围。
下面的代码演示了建立水平滚动条的过程和其消息映射:
// sb1.cpp
#include
#define IDM_SCROLLBAR 100
const int MAX_RANGE=100;
const int MIN_RANGE=0;
// Declare the application class
class CScrollBarApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
// Create an instance of the application class
CScrollBarApp ScrollBarApp;
// Declare the main window class
class CScrollBarWindow : public CFrameWnd
{
CScrollBar *sb;
public:
CScrollBarWindow();
afx_msg void OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar);
DECLARE_MESSAGE_MAP()
};
// The message handler function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CScrollBarWindow, CFrameWnd)
ON_WM_HSCROLL()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CScrollBarApp::InitInstance()
{
m_pMainWnd = new CScrollBarWindow();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// The constructor for the window class
CScrollBarWindow::CScrollBarWindow()
{
CRect r;
// Create the window itself
Create(NULL,
"CScrollBar Tests",
WS_OVERLAPPEDWINDOW,
CRect(0,0,200,200));
// Get the size of the client rectangle
GetClientRect(&r);
// Create a scroll bar
sb = new CScrollBar();
sb->Create(WS_CHILD|WS_VISIBLE|SBS_HORZ,
CRect(10,10,r.Width()-10,30),
this,
IDM_SCROLLBAR);
sb->SetScrollRange(MIN_RANGE,MAX_RANGE,TRUE);
}
Windows会区分水平和垂直滚动条,同时还支持CScrollBar中一称为尺寸盒的控制。尺寸盒是一个小方块。它处于水平和垂直滚动条的交叉处,呀鼠标拖动它会自动改变窗口的大小。在后面的代码中你看到如何用Create函数的SBS_HORZ样式来建立一水平滚动条。在建立了滚动条之后,马上用SetScrollRange中的MIN_RANGE和MAX_RANGE龙个常数给出了滚动条的范围0~100(它们定义在程序的顶部)。
事件处理函数OnHScroll来自CWnd类。我们使用该函数是因为该代码建立了水平滚动条。对于垂直滚动条应使用OnVScroll。在代码中,消息映射与滚动函数相联系,并使滚动条在用户操作时发出鸣笛声。当你运行该程序时,你可以单击箭头、拖动滚动条上的小方块等等。每次操作都会出现鸣笛声,但是滚动条上的小方块实际上不会移动,因为我们还没有把它与实际的代码相关联。
每次滚动条调用OnHScroll时,你的代码都要确定用户的操作。在OnHScroll函数内部,你可以检验传递给处理函数的第一参数,如下所示。如果你与上面的代码一起使用,滚动条的小方块就会移动到用户操作的位置处。
// The message handling function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
UINT nPos, CScrollBar* pScrollBar)
{
int pos;
pos = sb->GetScrollPos();
switch ( nSBCode )
{
case SB_LINEUP:
pos -= 1;
break;
case SB_LINEDOWN:
pos += 1;
break;
case SB_PAGEUP:
pos -= 10;
break;
case SB_PAGEDOWN:
pos += 10;
break;
case SB_TOP:
pos = MIN_RANGE;
break;
case SB_BOTTOM:
pos = MAX_RANGE;
break;
case SB_THUMBPOSITION:
pos = nPos;
break;
default:
return;
}
if ( pos < MIN_RANGE )
pos = MIN_RANGE;
else if ( pos > MAX_RANGE )
pos = MAX_RANGE;
sb->SetScrollPos( pos, TRUE );
}
SB_LINEUP和SB_LINEDOWN的不同常数值在CWnd::OnHScroll函数文档中有介绍。上面的代码首先使用GetScrollPos函数来恢复滚动条的当前位置。然后使用开关语句来确定用户对滚动条的操作。SB_LINEUP 和SB_LINEDOWN常数值意味着垂直方向,但也可用于水平方向表示左右移动。SB_PAGEUP和SB_PAGEDOWN是用在用户单击滚动条时。SB_TOP和SB_BOTTOM用于当用户移动滚动条小方块到滚动条的顶部和底部。SB_THUMBPOSITION用于当用户拖动小方块到指定位置时。代码会自动调整位置,然后确保它在设置其新位置时仍然在范围内。一旦设置了滚动条,小方块就会移动到适当的位置。
垂直滚动条的处理也是类似的,只是要用OnVScroll函数中的SBS_VERT样式。
理解消息映射
消息映射结构只能用于MFC。掌握它和如何在你的代码中应用它是很重要的。
可能纯C++使用者会对消息映射产生疑问: 为什么Microsoft不用虚拟函数来替代消息映射?虚拟函数是MFC中处理消息映射的标准C++方式,所以使用宏DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP可能有些怪异。
MFC使用消息映射来解决虚拟函数的基本问题。参见MFC帮助文件中的CWnd类。它包含200多个成员函数,所有的成员函数当不使用消息映射时都是虚拟的。现在来看一下所有CWnd类的子类。MFC中大约有近30个类是以CWnd为基类的。这包括所有的可见控制如按钮、静态标签和列表。现在想象一下,MFC使用虚拟函数,并且你建立一应用程序包含有20个控制。CWnd中的200个虚拟函数中的每个都需要自己的虚拟函数表,并且一个控制的每个例程都应有一组200个虚拟函数与之关联。则程序可能就有近4,000个虚拟函数表在内存中,这对内存有限的机器来说是个大问题。因为其中的大部分是不用的。
消息映射复制了虚拟函数表的操作,但是它是基于需要的基础之上的。当你在消息映射中建立一个入口时,你是在对系统说,“当你看见一个特殊的消息时,请调用指定的函数”。只有这些函数实际上被重载到消息映射中,着就节省了内存和CPU的负担。
当你用DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP说明消息映射时,系统会通过你的消息映射选择所有的消息。如果消息映射处理了给定的消息,则你的函数会被调用,卸车也就停留在此。但是,如果你的消息映射中不包含某个消息的入口,则系统会把该消息发送第二个BEGIN_MESSAGE_MAP指定的类。那个类可能会也可能不会处理它,如此重复。最后,如果没有消息映射处理一给定的消息,该消息会到由一缺省的处理函数来处理。
结论
本讲中所介绍的所有消息映射处理概念可适用于Windows NT中所有的控制和窗口。在大部分情况下,你可以使用ClassWizard来安装消息映射的入口,它将在后面的有关ClassWizard、AppWizard和资源编辑器一文中介绍。