当前位置:   article > 正文

6.5 消息处理_messagemapfunctions

messagemapfunctions

6.5.1 使用消息映射宏

Windows统一用 WPARAM 和 LPARAM 两个参数来描述消息的附加信息,例如WM_CREATE消息的 LPARAM 参数是指向 CREATESTRUCT 结构的指针,WPARAM参数没有被使用;WM_LBUTTONDOWN 消息的 WPARAM 参数指定了各虚拟键的状态(UINT类型),LPARAM 参数指定了鼠标的坐标位置(POINT类型)。很明显,消息附加参数的类型并不是完全相同的,如果CWnd类也定义一种统一形式的成员来处理所有的消息,将会丧失消息映射的灵活性。

消息映射项AFX_MSGMAP_ENTRY的pfn成员记录了消息映射表中消息映射函数的地址,但它却无法反映出该消息处理函数的类型。试想,CWnd对象的WindowProc函数在调用消息映射表中的函数响应Windows消息时,它如何能够知道向这个函数传递什么参数呢,又如何能够知道该函数是否有返回值呢。所以,仅仅在消息映射表项中记录下消息处理函数的地址是不够的,还应该想办法记录下函数的类型,以便框架程序能够正确地调用它。消息映射项的nSig成员是为达到这个目的而被添加到AFX_MSGMAP_ENTRY结构中的,它的不同取值代表了消息处理函数不同的返回值、函数名和参数列表。

可以使用下面一组枚举类型的数据来表示不同的函数类型。

// _AFXMSG_.H文件。请创建一个这样的文件 
enum AfxSig // 函数签名标识
{
	AfxSig_end = 0,	// 结尾标识

	AfxSig_vv,	// void (void),比如,void OnPaint()函数
	AfxSig_vw,	// void (UINT),比如,void OnTimer(UINT nIDEvent)函数
	AfxSig_is,	// int	(LPTSTR),比如,BOOL OnCreate(LPCREATESTRUCT)函数
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

虽然要定义的数字签名远远超过3个,但仅仅是为了做试验,所以有这几个已经够了。可以认为数字签名中的v代表void,w代表UINT,i代表int,s代表指针。有了这些全局变量的声明,在初始化消息映射表时,就能够记录下消息处理函数的类型。比如,CWnd类中处理WM_TIMER消息的函数是:

void OnTimer(UINT nIDEvent); 
  • 1

相关的消息映射项就应该初始化为这个样子:

{ WM_TIMER, 0, 0, 0, AfxSig_vw, (AFX_PMSG)(AFX_PMSGW)(void (CWnd::*)(UINT))&OnTimer },
  • 1

请注意上面对OnTimer函数类型的转化顺序。在_AFXWIN.H文件中有对AFX_PMSGW宏的定义,应当把它添加到定义CWnd类的地方:

typedef void (CWnd::*AFX_PMSGW)(void); // 与AFX_PMSG宏相似,但这个宏仅用于CWnd的派生类 
  • 1

首先程序将OnTimer函数转化成“void (CWnd::*)(UINT)”类型,再转化成“void (CWnd::*)(void)”类型,最后转化成“void (CCmdTarget::*)(void)”类型。

当对应的窗口接收到WM_TIMER消息时,框架程序就会去调用映射项成员pfn指向的函数,即OnTimer函数。但是,在调用之前,框架程序必须把这个AFX_PMSG类型的函数转化成“void (CWnd::*)(UINT)”类型。为了使这一转化方便地进行,下面再定义一个名称为MessageMapFunctions的联合。

// _AFXIMPL.H文件 
union MessageMapFunctions
{
	AFX_PMSG pfn;

	void (CWnd::*pfn_vv)(void);
	void (CWnd::*pfn_vw)(UINT);
	int (CWnd::*pfn_is)(LPTSTR);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

下面的代码演示了如何调用消息映射表中的函数OnTimer,其中lpEntry变量是查找到的指向类中AFX_MSGMAP_ENTRY对象的指针。

union MessageMapFunctions mmf; 
mmf.pfn = lpEntry->pfn; 
if(lpEntry->nSig == AfxSig_vw) 
{ (this->*mmf.pfn_vw)(wParam); } // 调用消息映射表中的函数 
  • 1
  • 2
  • 3
  • 4

CWnd类中为绝大部分Windows消息都安排了消息处理函数。做为示例,现在仅处理下面几个消息。

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); // WM_CREATE消息  
afx_msg void OnPaint(); // WM_PAINT消息  
afx_msg void OnClose(); // WM_CLOSE消息  
afx_msg void OnDestroy(); // WM_DESTROY消息  
afx_msg void OnNcDestroy(); // WM_NCDESTROY消息  
afx_msg void OnTimer(UINT nIDEvent); // WM_TIMER消息
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在CWnd类的实现文件中,这些消息处理函数的默认实现代码如下。

int CWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{ 
    return Default();
} 
void CWnd::OnPaint() 
{ 
    Default();
} 
void CWnd::OnClose() 
{ 
    Default();
} 
void CWnd::OnDestroy() 
{ 
    Default();
} 
void CWnd::OnNcDestroy() 
{ 
    CWinThread* pThread = AfxGetThread();  
    if(pThread != NULL)  
    { 
        if(pThread->m_pMainWnd == this)  
        { 
             if(pThread == AfxGetApp()) // 要退出消息循环?  
             { 
                 ::PostQuitMessage(0); 
             }  
             pThread->m_pMainWnd = NULL;  
        }  
    }  
    Default();  
    Detach();  // 给子类做清理工作的一个机会  
    PostNcDestroy(); 
} 
void CWnd::OnTimer(UINT nIDEvent) 
{ 
    Default();
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

请注意,只有在类的消息映射表中添加成员函数与特定消息的关联之后,消息到达时框架程序才会调用它们。上面这些消息处理函数除了OnNcDestroy函数做一些额外的工作外,其他函数均是直接调用DefWindowProc函数做默认处理,所以CWnd类的消息映射表中应该有这么一项(说明CWnd类要处理WM_NCDESTROY消息)。

{ WM_NCDESTROY, 0, 0, 0, AfxSig_vv, (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(void))&OnNcDestroy }, 
  • 1

为了方便向消息映射表中添加消息映射项,再在_AFXMSG_.H文件中为各类使用的消息映射项定义几个消息映射宏。

#define ON_WM_CREATE() \  
    { WM_CREATE, 0, 0, 0, AfxSig_is, \  
         (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(LPCREATESTRUCT))&OnCreate },
#define ON_WM_PAINT() \  
    { WM_PAINT, 0, 0, 0, AfxSig_vv, \  
         (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(HDC))&OnPaint }, 
#define ON_WM_CLOSE() \  
    { WM_CLOSE, 0, 0, 0, AfxSig_vv, \  
         (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(void))&OnClose }, 
#define ON_WM_DESTROY() \  
    { WM_DESTROY, 0, 0, 0, AfxSig_vv, \  
         (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(void))&OnDestroy }, 
#define ON_WM_NCDESTROY() \  
    { WM_NCDESTROY, 0, 0, 0, AfxSig_vv, \  
         (AFX_PMSG)(AFX_PMSGW)(int (CWnd::*)(void))&OnNcDestroy }, 
#define ON_WM_TIMER() \  
    { WM_TIMER, 0, 0, 0, AfxSig_vw, \ 
         (AFX_PMSG)(AFX_PMSGW)(void (CWnd::*)(UINT))&OnTimer },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

对消息映射宏的定义大大简化了用户使用消息映射的过程。比如,CWnd类要处理WM_NCDESTROY消息,以便在窗口完全销毁前做一些清理工作,CWnd的消息映射表就应该如下这样编写。

// 初始化消息映射表 // WINCORE.CPP文件 
BEGIN_MESSAGE_MAP(CWnd, CCmdTarget) 
ON_WM_NCDESTROY() 
END_MESSAGE_MAP() 
  • 1
  • 2
  • 3
  • 4

现在,各窗口的消息都被发送到了对应CWnd对象的WindowProc函数,而每个要处理消息的类也都拥有了自己的消息映射表,剩下的事情是WindowProc函数如何将接收到的消息交给映射表中记录的具体的消息处理函数,这就是下一小节要解决的问题。

6.5.2 消息的分发机制

根据处理函数和处理过程的不同,框架程序主要处理3类消息:
(1)Windows消息,前缀以“WM_”打头,WM_COMMAND例外。这是通常见到的WM_CREATE、WM_PAINT等消息。对于这类消息我们安排一个名称为OnWndMsg的虚函数来处理。
(2)命令消息,是子窗口控件或菜单送给父窗口的WM_COMMAND消息。虽然现在还没有讲述子窗口控件,但菜单总用过吧。这一类消息用名为OnCommand的虚函数来处理。
(3)通知消息,是通用控件送给父窗口的WM_NOFITY消息。这个消息以后再讨论,这里仅安排一个什么也不做的OnNotify虚函数响应它。

处理这3类消息的函数定义如下:

class CWnd : public CCmdTarget 
{
    …… // 其他成员 
protected:  
    virtual BOOL OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult);  
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);  
    virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); 
}; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

为了将CWnd对象接受到的消息传递给上述3个虚函数,应当如下改写WindowProc的实现代码:

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{ 
     LRESULT lResult;  
     if(!OnWndMsg(message, wParam, lParam, &lResult))  
         lResult = DefWindowProc(message, wParam, lParam);  
     return lResult; 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

OnWndMsg函数的返回值说明了此消息有没有被处理。如果没有处理WindowProc发过来的消息,OnWndMsg返回FALSE,WindowProc函数则调用CWnd类的成员函数DefWindowProc做默认处理。最后一个参数pResult用于返回消息处理的结果。

OnWndMsg函数会进而将接受到的消息分发给OnCommand和OnNotify函数。现在先写下这两个函数的实现代码:

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam) 
{ 
    return FALSE;
} 
BOOL CWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
{ 
    return FALSE; 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这节我们重点谈论OnWndMsg函数的实现过程,所以让处理命令消息和通知消息的函数仅返回FALSE即可。

假如用户从CWnd类派生了自己的窗口类CMyWnd,然后把要处理的消息写入CMyWnd类的消息映射表中。CWnd::OnWndMsg函数接收到CMyWnd类感兴趣的消息以后如何处理呢?它调用GetMessageMap虚函数得到自己派生类(CMyWnd类)的消息映射表的地址,然后遍历此表中所有的消息映射项,查找CMyWnd类为当前消息提供的消息处理函数,最后调用它。

要想遍历消息映射表查找处理指定消息的消息映射项,用一个简单的循环即可。下面的AfxFindMessageEntry函数完成此功能。

// 声明函数的代码在_AFXWIN.H文件中(CWnd类下面),实现代码在WINCORE.CPP文件中 
const AFX_MSGMAP_ENTRY* AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* lpEntry, 
						UINT nMsg, UINT nCode, UINT nID)
{
	while(lpEntry->nSig != AfxSig_end)
	{
		if(lpEntry->nMessage == nMsg && lpEntry->nCode == nCode &&
				(nID >= lpEntry->nID && nID <= lpEntry->nLastID))
			return lpEntry;
		lpEntry++;
	}
	return NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

此函数的第一个参数是消息映射表的地址,后面几个参数指明了要查找的消息映射项。查找成功函数返回消息映射项的地址。有了这个地址,就可以调用用户提供的消息处理函数了。具体实现代码如下面的OnWndMsg函数所示。

BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
	LRESULT lResult = 0;

	// 将命令消息和通知消息交给指定的函数处理
	if(message == WM_COMMAND)
	{
		if(OnCommand(wParam, lParam))
		{
			lResult = 1;
			goto LReturnTrue;
		}
		return FALSE;
	}
	if(message == WM_NOTIFY)
	{
		NMHDR* pHeader = (NMHDR*)lParam;
		if(pHeader->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
			goto LReturnTrue;
		return FALSE;
	}

	// 在各类的消息映射表中查找合适的消息处理函数,找到的话就调用它
	const AFX_MSGMAP* pMessageMap;
	const AFX_MSGMAP_ENTRY* lpEntry;
	for(pMessageMap = GetMessageMap(); pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
	{
		ASSERT(pMessageMap != pMessageMap->pBaseMap);
		if((lpEntry = AfxFindMessageEntry(pMessageMap->pEntries, message, 0, 0)) != NULL)
				goto LDispatch;
	}
	return FALSE;

LDispatch:
	union MessageMapFunctions mmf;
	mmf.pfn = lpEntry->pfn;
	switch(lpEntry->nSig)
	{
	default:
		return FALSE;
	case AfxSig_vw:
		(this->*mmf.pfn_vw)(wParam);
		break;
	case AfxSig_vv:
		(this->*mmf.pfn_vv)();
		break;
	case AfxSig_is:
		(this->*mmf.pfn_is)((LPTSTR)lParam);
		break;	
	}

LReturnTrue:
	if(pResult != NULL)
		*pResult = lResult;
	return TRUE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

OnWndMsg函数为所有的Windows消息查找消息处理函数,如果找到就调用它们。但是它不处理命令消息(WM_COMMAND)和通知消息(WM_NOTIFY)。事实上,这两个消息最终会被传给CCmdTarget类,由这个类在自己的派生类中查找合适的消息处理函数。这也是CCmdTarget类居于消息处理顶层的原因。为了使CWinThread及其派生类有机会响应命令消息和通知消息,也要让CWinThread类从CCmdTarget类继承,而不从CObject类继承。

6.5.3 消息映射应用举例

到此,框架程序已经有能力创建并管理窗口了。做一个实时显示电脑内存的使用情况的窗口。

这个例子主要用到了GlobalMemoryStatus函数。这个函数能够取得当前系统内物理内存和虚拟内存的使用情况,其原形如下:

void GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer); 
  • 1

其参数是指向MEMORYSTATUS结构的指针,GlobalMemoryStatus会将当前的内存使用信息返回到这个结构中:

typedef struct _MEMORYSTATUS {
    DWORD dwLength; // 本结构的长度。不用你在调用GlobalMemoryStatus之前设置
    DWORD dwMemoryLoad; // 已用内存的百分比
    SIZE_T dwTotalPhys; // 物理内存总量 
    SIZE_T dwAvailPhys; // 可用物理内存
    SIZE_T dwTotalPageFile; // 交换文件总的大小 
    SIZE_T dwAvailPageFile; // 交互文件中空闲部分大小
    SIZE_T dwTotalVirtual; // 用户可用的地址空间
    SIZE_T dwAvailVirtual; // 当前空闲的地址空间 
} MEMORYSTATUS, *LPMEMORYSTATUS;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

MEMORYSTATUS结构反映了调用发生时内存的状态,所以它能够实时监测内存。

06Meminfo实例的实现原理很简单,在处理WM_CREATE消息时安装一个间隔为0.5s的定时器,然后在WM_TIMER消息到来时调用GlobalMemoryStatus函数获取内存使用信息并更新客户区显示。

原理虽然简单,但目的是介绍框架程序是怎样工作的,所以应该将更多的注意力放在CWnd类处理消息的方式上。下面具体讲述程序的编写过程。

创建一个名为06Meminfo的空Win32 Application工程,更换VC++使用的默认运行期库,使它支持多线程(见3.1.5小节)。为了使用自己设计的框架程序,必须把COMMON目录下的.CPP文件全部添加到工程中,然后再从CWinApp类继承自己的应用程序类,从CWnd类继承自己的窗口类。

具体的程序代码在Meminfo.h和Meminfo.cpp两个文件中。在工程中通过菜单命令“File/New…”新建它们,文件内容如下。

//Meminfo.h文件
#include "_afxwin.h"

class CMyApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};

class CMainWindow : public CWnd
{
public:
	CMainWindow();
protected:
	char m_szText[1024];	// 客户区文本缓冲区
	RECT m_rcInfo;		// 文本所在方框的大小

protected:
	virtual void PostNcDestroy();
	afx_msg BOOL OnCreate(LPCREATESTRUCT);
	afx_msg void OnPaint();
	afx_msg void OnTimer(UINT nIDEvent);
	DECLARE_MESSAGE_MAP()
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
// Meminfo.cpp文件

#include "Meminfo.h"
#include "resource.h"

#define IDT_TIMER	101

CMyApp theApp;

BOOL CMyApp::InitInstance()
{
	m_pMainWnd = new CMainWindow;
	::ShowWindow(*m_pMainWnd, m_nCmdShow);
	::UpdateWindow(*m_pMainWnd);

	return TRUE;
}

CMainWindow::CMainWindow()
{
	m_szText[0] = '\0';

	LPCTSTR lpszClassName = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW, 
		::LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_3DFACE+1), AfxGetApp()->LoadIcon(IDI_MAIN));

	CreateEx(WS_EX_CLIENTEDGE, lpszClassName, 
		"内存使用监视器", WS_OVERLAPPEDWINDOW, 
		CW_USEDEFAULT, CW_USEDEFAULT, 300, 230, NULL, NULL);
}

// CMainWindow类的消息映射表
BEGIN_MESSAGE_MAP(CMainWindow, CWnd)
ON_WM_CREATE()
ON_WM_PAINT()
ON_WM_TIMER()
END_MESSAGE_MAP()

BOOL CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	// 设置显示文本所在方框的大小
	::GetClientRect(m_hWnd, &m_rcInfo);
	m_rcInfo.left = 30;
	m_rcInfo.top = 20;
	m_rcInfo.right = m_rcInfo.right - 30;
	m_rcInfo.bottom = m_rcInfo.bottom - 30;

	// 安装定时器
	::SetTimer(m_hWnd, IDT_TIMER, 500, NULL);

	// 将窗口提到最顶层
	::SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, 0, 0,
			SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE);

	return TRUE;
}

void CMainWindow::OnTimer(UINT nIDEvent)
{
	if(nIDEvent == IDT_TIMER)
	{
		char szBuff[128];
		MEMORYSTATUS ms;
		
		// 取得内存状态信息
		::GlobalMemoryStatus(&ms);


		// 将取得的信息放入缓冲区m_szText中
	
		m_szText[0] = '\0';
		
		wsprintf(szBuff, "\n  物理内存总量:  %-5d MB", ms.dwTotalPhys/(1024*1024));
		strcat(m_szText, szBuff);
		wsprintf(szBuff, "\n  可用物理内存:  %-5d MB", ms.dwAvailPhys/(1024*1024));
		strcat(m_szText, szBuff);
		
		wsprintf(szBuff, "\n\n  虚拟内存总量:  %-5d MB", ms.dwTotalVirtual/(1024*1024));
		strcat(m_szText, szBuff);
		wsprintf(szBuff, "\n  可用虚拟内存:  %-5d MB", ms.dwAvailVirtual/(1024*1024));
		strcat(m_szText, szBuff);
		
		wsprintf(szBuff, "\n\n  内存使用率:     %d%%", ms.dwMemoryLoad);
		strcat(m_szText, szBuff);
		
		// 无效显示文本的区域,以迫使系统发送WM_PAINT消息,更新显示信息
		::InvalidateRect(m_hWnd, &m_rcInfo, TRUE);
	}	
}

void CMainWindow::OnPaint()
{
	PAINTSTRUCT ps;
	HDC hdc = ::BeginPaint(m_hWnd, &ps);

	// 设置背景为透明模式
	::SetBkMode(hdc, TRANSPARENT);

	// 创建字体
	HFONT hFont = ::CreateFont(12, 0, 0, 0, FW_HEAVY, 0, 0, 0, ANSI_CHARSET, \
		OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
		VARIABLE_PITCH | FF_SWISS, "MS Sans Serif" );
	// 创建画刷
	HBRUSH hBrush = ::CreateSolidBrush(RGB(0xa0, 0xa0, 0xa0));
	// 将它们选入到设备环境中
	HFONT hOldFont = (HFONT)::SelectObject(hdc, hFont);
	HBRUSH hOldBrush = (HBRUSH)::SelectObject(hdc, hBrush);

	// 设置文本颜色
	::SetTextColor(hdc, RGB(0x32, 0x32, 0xfa));
	// 画一个圆角矩形
	::RoundRect(hdc, m_rcInfo.left, m_rcInfo.top, m_rcInfo.right, m_rcInfo.bottom, 5, 5);
	
	// 绘制文本
	::DrawText(hdc, m_szText, strlen(m_szText), &m_rcInfo, 0);
	
	// 清除资源
	::DeleteObject(::SelectObject(hdc, hOldFont));
	::DeleteObject(::SelectObject(hdc, hOldBrush));
	::EndPaint(m_hWnd, &ps);
}

void CMainWindow::PostNcDestroy()
{
	delete this;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

在这里插入图片描述
程序很简单,仅处理WM_CREATE、WM_PAINT和WM_TIMER三个消息。这次不必再使用长长的switch/case结构了,直接在消息映射表中添加相关消息映射项即可处理它们。运行程序,自己的类库框架开始工作了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/690687
推荐阅读
相关标签
  

闽ICP备14008679号