一、开发自己的Win32类库
MFC是微软对Win32代码进行封装的一套庞大的类库,如果直接讲解,由于不了解其内部结构和开发思想,学习起来将是非常难于理解的。为了了解MFC是如何封装的,今天我们先来开发一个我们自己的Win32类库。
所谓类库,是指一套可以重用的代码,是为了方便以后的开发,将大量固定的、重复的、有规律的代码包装起来,供以后开发时直接调用,而不用再次重写这部分代码;这样就可以将我们的主要精力投入到真正需要花费时间的业务及其逻辑上面,而不再去关心和编写那些千篇一律的程序结构的代码了。
在前面的课程中,我们发现WinMain工程中就有许多固定模式的代码,我们现在就来将其转换成我们自己的类库,我们称之为Win32Lib。
WinMain工程的源代码如下:
#include <windows.h>
#include <stdio.h>
//声明窗口回调函数
LRESULT CALLBACK WinProc(
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息ID
WPARAM wParam, // 第1个消息参数
LPARAM lParam // 第2个消息参数
);
//程序入口
int WINAPI WinMain(
HINSTANCE hInstance, // 当前实例句柄
HINSTANCE hPrevInstance, // 前一实例句柄
LPSTR lpCmdLine, // 命令行参数
int nCmdShow // 窗口显示方式
)
{
//1. 注册窗口
WNDCLASS wndcls; //定义并填充窗口类
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndcls.hInstance = hInstance;
wndcls.lpfnWndProc = WinProc; //重点:指定窗口消息的处理函数
wndcls.lpszClassName = "Itjob2010";
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls); //注册窗口
//2. 创建窗口
HWND hWnd;
hWnd = CreateWindow(
wndcls.lpszClassName, //窗口类名称
"一个简单的Win32程序", //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格,定义为普通型
0, //窗口位置的x坐标
0, //窗口位置的y坐标
600, //窗口的宽度
400, //窗口的高度
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //应用程序实例句柄
NULL); //窗口创建数据指针
//3. 显示窗口
ShowWindow(hWnd, SW_SHOWNORMAL);
//4. 更新窗口
UpdateWindow(hWnd);
//5. 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //把虚键消息翻译成字符消息(WM_CHAR),
//再把WM_CHAR消息放到消息队列中去
DispatchMessage(&msg); //指示操作系统把这条消息发送到窗口
//过程WinProc进行处理
}
return 0;
}
//窗口回调函数,由操作系统调用,程序员
//不要调用,但程序员需要编写其实现代码
LRESULT CALLBACK WinProc(
HWND hwnd, // 窗口句柄
UINT uMsg, // 消息ID
WPARAM wParam, // 第1个消息参数
LPARAM lParam // 第2个消息参数
)
{
switch (uMsg)
{
case WM_CHAR:
char szChar[20];
sprintf(szChar, "你按下了%c键", (char)wParam);
MessageBox(hwnd, szChar, "WM_CHAR", 0);
break;
case WM_LBUTTONDOWN:
HDC hdc;
hdc = GetDC(hwnd);
TextOut(hdc, 0, 50, "计算机编程语言培训", strlen("计算机编程语言培训"));
ReleaseDC(hwnd, hdc);
break;
case WM_PAINT:
HDC hDC;
PAINTSTRUCT ps;
hDC = BeginPaint(hwnd, &ps);
TextOut(hDC, 0, 0, "Hello, World!", strlen("Hello, World!"));
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
在上述代码中,我们发现,主函数WinMain()中的代码在编写任何一个Win32工程时几乎都是上述固定的步骤,即注册窗口类,创建窗口,显示窗口,更新窗口,消息循环。
窗口回调函数WinProc()中的代码虽然各有不同,但也有共同的规律,即都是一个case语句对应一段处理某个消息的代码。
首先我们将这些switch-case语句中的代码转换成函数的形式。为了统一,我们使这些函数都具有一样的函数原型。即:
LRESULT OnChar(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT OnLButtonDown (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT OnPaint (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT OnDestroy (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
转换后的部分代码如下:
switch (uMsg)
{
case WM_CHAR:
OnChar(hWnd, wMsg, wParam, lParam);
break;
case WM_LBUTTONDOWN:
OnLButtonDown (hWnd, wMsg, wParam, lParam);
break;
case WM_PAINT:
OnPaint (hWnd, wMsg, wParam, lParam);
break;
case WM_DESTROY:
OnDestroy (hWnd, wMsg, wParam, lParam);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
转换后的代码就更加具有规律性了,如下表所示:
消息(code) | 消息响应函数(Fxn) |
WM_CHAR | OnChar(hWnd, wMsg, wParam, lParam) |
WM_LBUTTONDOWN | OnLButtonDown (hWnd, wMsg, wParam, lParam) |
WM_PAINT | OnPaint (hWnd, wMsg, wParam, lParam) |
WM_DESTROY | OnDestroy (hWnd, wMsg, wParam, lParam) |
从前面C/C++的课程中,我们知道,具有相同原型的一系列函数,可以使用typedef语句将他们定义成统一的函数指针形式,如下:
typedef LRESULT (*FXN)( HWND, UINT, WPARAM, LPARAM);
再定义一个返回元素个数的宏,后面的代码会用到:
#define dim(x) (sizeof(x) / sizeof(x[0]))
然后定义一个结构,用于表示上述的表格中的2类数据:
struct tagMESSAGEMAP {
UINT Code; //消息
FXN Fxn; //响应函数
};
再用该结构类型定义一个消息映射数组MessageMaps,并赋初值如下:
tagMESSAGEMAP MessageMaps [] = {
WM_CHAR, OnChar,
WM_LBUTTONDOWN, OnLButtonDown,
WM_PAINT, OnPaint,
WM_DESTROY, OnDestroy,
};
接下来,我们将switch-case语句改造成for循环语句,改造完成后的WinProc函数如下:
//主窗口回调函数
LRESULT CALLBACK WinProc(
HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
// 如果当前消息是我们关心的、定义在数组中的消息,则处理之
for (int i = 0; i < dim(MessageMaps); i++)
{
if (wMsg == MessageMaps [i].Code)
{
FXN iFxn = MessageMaps [i].Fxn;
LRESULT lResult = iFxn (hWnd, wMsg, wParam, lParam);
if (lResult == 0)
return 0;
}
}
// 否则,将消息交给系统去处理
return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
经过上面的改造之后,我们以后再要添加新的消息和消息响应函数,就只需要在数组MessageMaps[]中添加相应的消息代码,定义相应的消息响应函数及其实现代码就可以了。这样就将我们的精力真正转移到了我们所关心的业务上面来了,而再也不用去关心程序的结构了。
改造后的全部代码如下:
#include <windows.h>
#include <stdio.h>
//返回元素的个数
#define dim(x)(sizeof(x) / sizeof(x[0]))
//定义函数指针
typedef LRESULT(*FXN)(HWND, UINT, WPARAM, LPARAM);
//消息映射结构
struct tagMESSAGEMAP
{
UINT Code; //消息
FXN Fxn; //响应函数
};
//主窗口回调函数
LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);
//声明消息响应函数
LRESULT OnChar(HWND, UINT, WPARAM, LPARAM);
LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM);
LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM);
LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM);
LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM);
//消息映射数组
tagMESSAGEMAP MessageMaps[] = {
WM_CHAR, OnChar,
WM_LBUTTONDOWN, OnLButtonDown,
WM_PAINT, OnPaint,
WM_DESTROY, OnDestroy,
WM_TIMER, OnTimer,
};
//入口函数
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow) // show state
{
WNDCLASS wndcls;
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon = LoadIcon(NULL, IDI_ERROR);
wndcls.hInstance = hInstance;
wndcls.lpfnWndProc = WinProc;
wndcls.lpszClassName = "ItJob2010";
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
HWND hWnd;
hWnd = ::CreateWindow(wndcls.lpszClassName, "培训中心",
WS_OVERLAPPEDWINDOW,
0, 0, 600, 400, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
::SetTimer(hWnd, 123, 1000, NULL);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
//主窗口回调函数
LRESULT CALLBACK WinProc(
HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
// 如果当前消息是我们关心的、定义在数组中的消息,则处理之
for (int i = 0; i < dim(MessageMaps); i++)
{
if (wMsg == MessageMaps[i].Code)
{
FXN iFxn = MessageMaps[i].Fxn;
LRESULT lResult = iFxn(hWnd, wMsg, wParam, lParam);
if (lResult == 0)
return 0;
}
}
// 否则,将消息交给系统去处理
return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
//消息响应函数: 字符按下
LRESULT OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
char szChar[20];
sprintf(szChar, "char is %c", (char)wParam);
MessageBox(hWnd, szChar, "OnChar", 0);
return 0;
}
//消息响应函数: 鼠标左键按下
LRESULT OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
hdc = GetDC(hWnd);
TextOut(hdc, 0, 50, "计算机编程语言培训", strlen("计算机编程语言培训"));
ReleaseDC(hWnd, hdc);
return 0;
}
//消息响应函数:重绘窗口
LRESULT OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
GetClientRect(hWnd, &rc);
int iR = min(rc.right - rc.left, rc.bottom - rc.top) / 2;
iR = iR * 4 / 5;
POINT pt;
pt.x = (rc.right + rc.left) / 2;
pt.y = (rc.bottom + rc.top) / 2;
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
::Ellipse(hdc, pt.x - iR, pt.y - iR, pt.x + iR, pt.y + iR);
MoveToEx(hdc, pt.x, pt.y,(LPPOINT)NULL);
LineTo(hdc, pt.x + iR, pt.y);
static char stime[] = "23:59:59";
SYSTEMTIME tm;
::GetLocalTime(&tm);
sprintf(stime, "%.2d:%.2d:%.2d", tm.wHour, tm.wMinute, tm.wSecond);
::TextOut(hdc, 10, 10,(LPCSTR)stime, strlen(stime));
EndPaint(hWnd, &ps);
return 0;
}
//消息响应函数: 销毁窗口
LRESULT OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
PostQuitMessage(0);
return 0;
}
//消息响应函数: 定时器
LRESULT OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
::GetClientRect(hWnd, &rc);
::InvalidateRect(hWnd, &rc, TRUE);
return 0;
}
其中,红色部分是我新添加的一个定时器消息及其响应函数。其中用到了一些后面课程中将要讲解的绘图函数,算是提前给大家一个预习,能理解最好,不能理解也没关系,后面会讲的。
这里先简单看一下定时器消息WM_TIMER。WM_TIMER消息是Windows系统消息,使用SetTimer函数设置一个定时器,使用KillTimer销毁一个定时器。其原型分别为:
UINT SetTimer(
HWND hWnd, // handle of window for timer messages
UINT nIDEvent, // timer identifier
UINT uElapse, // time-out value
TIMERPROC lpTimerFunc // address of timer procedure
);
BOOL KillTimer(
HWND hWnd, // handle of window that installed timer
UINT uIDEvent // timer identifier
);
用法非常简单,详见MSDN。
二、将Win32Lib封装成类(Win32Class)
前面的WinMain工程和Win32Lib工程,都还是全局函数的形式,现在我们来将Win32Lib封装成class,为向MFC过渡埋下伏笔。
Win32Lib工程中,我们仅仅是改造了WinProc()这个窗口处理函数,对WinMain()主函数并没有改造。仔细分析WinMain()函数,发现其本质就做了2件事情:一是创建和显示窗口,一是进行消息循环以响应窗口消息。因此,我们的封装思想也是基于这2点的。将其封装成2个类:CMyWnd类,和CMyApp类。CMyWnd类专门负责和窗口创建相关的工作,CMyApp类专门负责初始化、程序的运行(消息循环)等应用性的工作。
将Win32Lib工程复制一份,将目录改名为Win32Class,我们将在此基础上封装我们的Win32Class类。打开Win32Class工程,添加一个新类CMyWnd。
完成后的CMyWnd类的定义如下:
class CMyWnd
{
public:
HINSTANCE m_hInstance;
HWND m_hWnd;
public:
CMyWnd();
virtual ~CMyWnd(){};
BOOL Create();
BOOL ShowWindow();
BOOL UpdateWindow();
//主窗口回调函数
static LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);
//声明消息响应函数
static LRESULT OnChar(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM);
};
用同样的方法添加类CMyApp,其定义如下:
class CMyApp
{
public:
CMyWnd* m_pMainWnd; //主窗口
public:
CMyApp();
virtual ~CMyApp(){};
BOOL InitInstance(); //初始化主窗口
BOOL Run(); //消息循环
};
完成后的Win32Lib.h内容如下:
// Win32Lib.h: interface for the CMyWnd class.
//
#if !defined(AFX_WIN32LIB_H__510E6511_F3DB_4E80_B512__INCLUDED _)
#define AFX_WIN32LIB_H__510E6511_F3DB_4E80_B512__INCLUDED _
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
//返回元素的个数
#define dim(x)(sizeof(x) / sizeof(x[0]))
//定义函数指针
typedef LRESULT(*FXN)(HWND, UINT, WPARAM, LPARAM);
//消息映射结构
struct tagMESSAGEMAP
{
UINT Code; //消息
FXN Fxn; //响应函数
};
class CMyWnd
{
public:
HINSTANCE m_hInstance;
HWND m_hWnd;
public:
BOOL UpdateWindow();
BOOL ShowWindow();
BOOL Create();
CMyWnd();
virtual ~CMyWnd(){};
//主窗口回调函数
static LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);
//声明消息响应函数
static LRESULT OnChar(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM);
static LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM);
};
//消息映射数组
tagMESSAGEMAP MessageMaps[] = {
WM_CHAR, CMyWnd::OnChar,
WM_LBUTTONDOWN, CMyWnd::OnLButtonDown,
WM_PAINT, CMyWnd::OnPaint,
WM_DESTROY, CMyWnd::OnDestroy,
WM_TIMER, CMyWnd::OnTimer,
};
class CMyApp
{
public:
CMyWnd* m_pMainWnd;
public:
CMyApp();
virtual ~CMyApp(){};
BOOL InitInstance();
BOOL Run();
};
#endif // !defined(AFX_WIN32LIB_H__510E6511_F3DB_4E80_B512__INCLUDED _)
完成后的Win32Lib.cpp内容如下:
#include <windows.h>
#include <stdio.h>
#include "Win32Class.h"
CMyApp theApp;
//入口函数
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow) // show state
{
return 0;
}
//
// CMyWnd Class
//
CMyWnd::CMyWnd()
{
m_hWnd = NULL;
m_hInstance = NULL;
}
BOOL CMyWnd::Create()
{
WNDCLASS wndcls;
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon = LoadIcon(NULL, IDI_ERROR);
wndcls.hInstance = m_hInstance;
wndcls.lpfnWndProc = WinProc;
wndcls.lpszClassName = "ItJob2010";
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
m_hWnd = ::CreateWindow(wndcls.lpszClassName, "培训中心",
WS_OVERLAPPEDWINDOW,
0, 0, 600, 400, NULL, NULL, m_hInstance, NULL);
::SetTimer(m_hWnd, 123, 1000, NULL);
if (m_hWnd == NULL)
return FALSE;
else
return TRUE;
}
BOOL CMyWnd::ShowWindow()
{
return ::ShowWindow(m_hWnd, SW_SHOWNORMAL);
}
BOOL CMyWnd::UpdateWindow()
{
return ::UpdateWindow(m_hWnd);
}
//主窗口回调函数
LRESULT CALLBACK CMyWnd::WinProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
// 如果当前消息是我们关心的、定义在数组中的消息,则处理之
for (int i = 0; i < dim(MessageMaps); i++)
{
if (wMsg == MessageMaps[i].Code)
{
FXN iFxn = MessageMaps[i].Fxn;
LRESULT lResult = iFxn(hWnd, wMsg, wParam, lParam);
if (lResult == 0)
return 0;
}
}
// 否则,将消息交给系统去处理
return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
//消息响应函数: 字符按下
LRESULT CMyWnd::OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
char szChar[20];
sprintf(szChar, "char is %c", (char)wParam);
MessageBox(hWnd, szChar, "OnChar", 0);
return 0;
}
//消息响应函数: 鼠标左键按下
LRESULT CMyWnd::OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
hdc = GetDC(hWnd);
TextOut(hdc, 0, 50, "计算机编程语言培训", strlen("计算机编程语言培训"));
ReleaseDC(hWnd, hdc);
return 0;
}
//消息响应函数:重绘窗口
LRESULT CMyWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
//画一个圆
RECT rc;
GetClientRect(hWnd, &rc);
int iR = min(rc.right - rc.left, rc.bottom - rc.top) / 2;
iR = iR * 4 / 5;
POINT pt;
pt.x = (rc.right + rc.left) / 2;
pt.y = (rc.bottom + rc.top) / 2;
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
::Ellipse(hdc, pt.x - iR, pt.y - iR, pt.x + iR, pt.y + iR);
MoveToEx(hdc, pt.x, pt.y,(LPPOINT)NULL);
LineTo(hdc, pt.x + iR, pt.y);
//显示时间
static char stime[] = "23:59:59";
SYSTEMTIME tm;
::GetLocalTime(&tm);
sprintf(stime, "%.2d:%.2d:%.2d", tm.wHour, tm.wMinute, tm.wSecond);
::TextOut(hdc, 10, 10,(LPCSTR)stime, strlen(stime));
EndPaint(hWnd, &ps);
return 0;
}
//消息响应函数: 销毁窗口
LRESULT CMyWnd::OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
PostQuitMessage(0);
return 0;
}
//消息响应函数: 定时器
LRESULT CMyWnd::OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
::GetClientRect(hWnd, &rc);
::InvalidateRect(hWnd, &rc, TRUE);
return 0;
}
//
// CMyApp Class
//
CMyApp::CMyApp()
{
m_pMainWnd = NULL;
if (InitInstance())
Run();
}
BOOL CMyApp::Run()
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return TRUE;
}
BOOL CMyApp::InitInstance()
{
m_pMainWnd = new CMyWnd();
m_pMainWnd->Create();
m_pMainWnd->ShowWindow();
return m_pMainWnd->UpdateWindow();
}
三、优化Win32Class,将不常用的代码移到.h文件中,将常用的代码移到.cpp文件中去。
其中使用MY_MESSAGE_DECLARE和MY_MESSAGE_MAP这2个宏将原本在Win32Class.h文件中的声明消息响应函数和定义消息响应函数数组的代码也移到了Win32Class.cpp文件中。这样,.h文件中都是一些结构性和固定模式的不需要经常改动的内容了,而.cpp文件中的内容就全是需要经常改动、和应用相关的内容,如要再添加新的消息及其响应代码,只需要照葫芦画瓢即可,真正实现了将主要精力放在业务上而不是程序的框架上的目标。
其实,上面2个宏的实现思想就是MFC中消息映射的核心思想,通过实现我们自己的类库的过程,将原本复杂晦涩、难于理解的MFC消息映射的概念轻松的揭示给了大家。这为我们后面学习MFC打下了良好的基础。
头文件Win32Class.h的内容如下:
// Win32Lib.h: interface for the CMyWnd class.
//
#if !defined(AFX_WIN32LIB_H__510E6511_F3DB_4E80_B512__INCLUDED_)
#define AFX_WIN32LIB_H__510E6511_F3DB_4E80_B512__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
//返回元素的个数
#define dim(x)(sizeof(x) / sizeof(x[0]))
//定义函数指针
typedef LRESULT(*FXN)(HWND, UINT, WPARAM, LPARAM);
//消息映射结构
struct tagMESSAGEMAP
{
UINT Code; //消息
FXN Fxn; //响应函数
};
class CMyWnd
{
public:
HINSTANCE m_hInstance;
HWND m_hWnd;
public:
BOOL Create();
BOOL ShowWindow();
BOOL UpdateWindow();
CMyWnd();
virtual ~CMyWnd(){};
//主窗口回调函数
static LRESULT CALLBACK WinProc(HWND, UINT, WPARAM, LPARAM);
//声明消息响应函数
MY_MESSAGE_DECLARE
};
//消息映射数组
MY_MESSAGE_MAP
class CMyApp
{
public:
CMyWnd* m_pMainWnd;
BOOL InitInstance();
BOOL Run();
CMyApp();
virtual ~CMyApp(){};
};
//入口函数
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow) // show state
{
return 0;
}
//
// CMyWnd Class
//
CMyWnd::CMyWnd()
{
m_hWnd = NULL;
m_hInstance = NULL;
}
BOOL CMyWnd::Create()
{
WNDCLASS wndcls;
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndcls.hIcon = LoadIcon(NULL, IDI_ERROR);
wndcls.hInstance = m_hInstance;
wndcls.lpfnWndProc = WinProc;
wndcls.lpszClassName = "ItJob2010";
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
m_hWnd = ::CreateWindow(wndcls.lpszClassName, "培训中心",
WS_OVERLAPPEDWINDOW,
0, 0, 600, 400, NULL, NULL, m_hInstance, NULL);
::SetTimer(m_hWnd, 123, 1000, NULL);
if (m_hWnd == NULL)
return FALSE;
else
return TRUE;
}
BOOL CMyWnd::ShowWindow()
{
return ::ShowWindow(m_hWnd, SW_SHOWNORMAL);
}
BOOL CMyWnd::UpdateWindow()
{
return ::UpdateWindow(m_hWnd);
}
//主窗口回调函数
LRESULT CALLBACK CMyWnd::WinProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
// 如果当前消息是我们关心的、定义在数组中的消息,则处理之
for (int i = 0; i < dim(MessageMaps); i++)
{
if (wMsg == MessageMaps[i].Code)
{
FXN iFxn = MessageMaps[i].Fxn;
LRESULT lResult = iFxn(hWnd, wMsg, wParam, lParam);
if (lResult == 0)
return 0;
}
}
// 否则,将消息交给系统去处理
return DefWindowProc(hWnd, wMsg, wParam, lParam);
}
//
// CMyApp Class
//
CMyApp::CMyApp()
{
m_pMainWnd = NULL;
if (InitInstance())
Run();
}
BOOL CMyApp::Run()
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return TRUE;
}
BOOL CMyApp::InitInstance()
{
m_pMainWnd = new CMyWnd();
m_pMainWnd->Create();
m_pMainWnd->ShowWindow();
return m_pMainWnd->UpdateWindow();
}
#endif // !defined(AFX_WIN32LIB_H__510E6511_F3DB_4E80_B512__INCLUDED_)
实现文件Win32Class.cpp的内容如下:
#include <windows.h>
#include <stdio.h>
//声明消息响应函数
#define MY_MESSAGE_DECLARE \
static LRESULT OnChar(HWND, UINT, WPARAM, LPARAM); \
static LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM); \
static LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM); \
static LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM); \
static LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM); \
//消息映射数组
#define MY_MESSAGE_MAP \
tagMESSAGEMAP MessageMaps[] = { \
WM_CHAR, CMyWnd::OnChar, \
WM_LBUTTONDOWN, CMyWnd::OnLButtonDown, \
WM_PAINT, CMyWnd::OnPaint, \
WM_DESTROY, CMyWnd::OnDestroy, \
WM_TIMER, CMyWnd::OnTimer, \
}; \
#include "Win32Class.h"
CMyApp theApp;
//消息响应函数: 字符按下
LRESULT CMyWnd::OnChar(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
char szChar[20];
sprintf(szChar, "char is %c", (char)wParam);
MessageBox(hWnd, szChar, "OnChar", 0);
return 0;
}
//消息响应函数: 鼠标左键按下
LRESULT CMyWnd::OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
hdc = GetDC(hWnd);
TextOut(hdc, 0, 50, "计算机编程语言培训", strlen("计算机编程语言培训"));
ReleaseDC(hWnd, hdc);
return 0;
}
//消息响应函数:重绘窗口
LRESULT CMyWnd::OnPaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
//画一个圆
RECT rc;
GetClientRect(hWnd, &rc);
int iR = min(rc.right - rc.left, rc.bottom - rc.top) / 2;
iR = iR * 4 / 5;
POINT pt;
pt.x = (rc.right + rc.left) / 2;
pt.y = (rc.bottom + rc.top) / 2;
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint(hWnd, &ps);
::Ellipse(hdc, pt.x - iR, pt.y - iR, pt.x + iR, pt.y + iR);
MoveToEx(hdc, pt.x, pt.y,(LPPOINT)NULL);
LineTo(hdc, pt.x + iR, pt.y);
//显示时间
static char stime[] = "23:59:59";
SYSTEMTIME tm;
::GetLocalTime(&tm);
sprintf(stime, "%.2d:%.2d:%.2d", tm.wHour, tm.wMinute, tm.wSecond);
::TextOut(hdc, 10, 10,(LPCSTR)stime, strlen(stime));
EndPaint(hWnd, &ps);
return 0;
}
//消息响应函数: 销毁窗口
LRESULT CMyWnd::OnDestroy(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
PostQuitMessage(0);
return 0;
}
//消息响应函数: 定时器
LRESULT CMyWnd::OnTimer(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
RECT rc;
::GetClientRect(hWnd, &rc);
::InvalidateRect(hWnd, &rc, TRUE);
return 0;
}
四、将Win32Class.h文件从工程中移除,实现对WinMain等函数的隐藏。
将Win32Class.h文件移到windows.h所在的同一目录下,然后将#include "Win32Class.h"的引号改成尖括号#include <Win32Class.h>,工程中就看不到WinMain()等函数了。这就和MFC的情况类似了。
然后再添加OnClose和OnCreate消息响应的代码,发现非常简单了。不过,现在的简单是因为我们前面做了大量的封装工作。
此时的Win32Class已经和MFC非常的接近了,这为理解MFC的结构打下了良好的基础。
最终完成的代码见工程“4Win32Class”。
作业:自己动手实现从“WinMain”到“4Win32Class”的全部过程,复习讲课内容,深入理解类库的概念。