前言
使用VS2005/8/10的空项目来创建窗口,可以作为Dome来研究,应用在使用MFC的过程中自己窗口。
MFC是微软对Win32代码进行封装的一套庞大的类库,如果直接讲解,由于不了解其内部结构和开发思想,学习起来将是非常难于理解的。为了了解MFC是如何封装的,今天我们先来开发一个我们自己的Win32类库。
所谓类库,是指一套可以重用的代码,是为了方便以后的开发,将大量固定的、重复的、有规律的代码包装起来,供以后开发时直接调用,而不用再次重写这部分代码;这样就可以将我们的主要精力投入到真正需要花费时间的业务及其逻辑上面,而不再去关心和编写那些千篇一律的程序结构的代码了。
实例
在VS2005/8/10中创建一个空项目,新建一个cpp文件。直接拷贝下面的代码到cpp文件中,运行就行了。
#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 = TEXT("ItJob2010");
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
HWND hWnd;
hWnd = ::CreateWindow(wndcls.lpszClassName, TEXT("培训中心"),
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,LPTSTR(szChar), TEXT("OnChar"), 0);
return 0;
}
//消息响应函数: 鼠标左键按下
LRESULT OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
hdc = GetDC(hWnd);
TextOut(hdc, 0, 50, TEXT("计算机编程语言培训"), 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,(LPTSTR)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;
}
补充改进
主函数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 = TEXT("ItJob2010");
wndcls.lpszMenuName = NULL;
wndcls.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndcls);
HWND hWnd;
hWnd = ::CreateWindow(wndcls.lpszClassName, TEXT("培训中心"),
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, (LPTSTR)szChar, TEXT("OnChar"), 0);
return 0;
}
//消息响应函数: 鼠标左键按下
LRESULT OnLButtonDown(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
hdc = GetDC(hWnd);
TextOut(hdc, 0, 50, TEXT("计算机编程语言培训"), 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,(LPTSTR)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;
}