/*------------------------------------------------------------------------
011 编程达人win32 API每日一练
第11个例子GetMessage.c:消息循环
MSG结构
GetMessage函数
TranslateMessage函数:将虚拟键消息转换为字符消息
DispatchMessage函数: 分发一个消息给窗口程序
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
/*********************************回调函数************************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/*************************程序入口 *******************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin"); // 窗口类名
/***********************************1、设计窗口类*****************************/
WNDCLASS wndclass; //定义窗口类变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口风格
wndclass.lpfnWndProc = WndProc; //窗口处理函数---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
wndclass.lpszMenuName = NULL; //装载窗口菜单,此处程序没有
wndclass.lpszClassName = szAppName; // 窗口类名---程序名
/********************************2、注册窗口类********************************/
if (!RegisterClass(&wndclass)) //未成功注册
{
MessageBox(NULL, TEXT("注册失败!"),szAppName, MB_ICONEXCLAMATION);
return 0;
}
/*****************************3、创建窗口类**********************************/
HWND hwnd; //定义窗口句柄
hwnd = CreateWindow(szAppName, //已注册窗口类名
TEXT("我的第一个窗口程序!"),//窗体标题
WS_OVERLAPPEDWINDOW, //窗体风格
CW_USEDEFAULT, //窗体左上角x坐标
CW_USEDEFAULT, //窗体左上角y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //父窗口句柄,此处NULL
NULL, //窗口菜单句柄,此处NULL
hInstance,//应用程序句柄
NULL
);
/*******************************4、显示窗口和更新窗口***********************/
ShowWindow(hwnd, nCmdShow); //发送WM_SIZE和WM_SHOWWINDOW消息送入消息队列
UpdateWindow(hwnd); //发送WM_PAINT消息给窗口过程
/******************************5、消息循环*********************************/
MSG msg;//定义消息变量
/*1、调用GetMessage函数时,传入msg的地址,从窗口消息队列中获取消息,其中的消息结构的内容由操作系统自动填充。
2、这是windows程序的核心,消息循环处理过程,只有在收到WM_QUIT时才退出消息循环,结束程序。
*/
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将虚拟键消息转换为字符消息
DispatchMessage(&msg); //发送消息函数,先把msg发送给操作系统,
//然后由操作系统再调用Wndproc函数!
//Dispatch函数的内部实现大体流程
//push 消息参数进栈
//............
//call WndProc(...) //调用窗体回调函
//数,注意这里是个回调函数。
//................
//结束,返回
}
return msg.wParam;//msg.wParam 来自一条表示退出的消息,返回这个值给系统
}
/*****************************回调函数——消息处理过程***********************/
//窗口回调函数,只有声明和定义,直接返回!这说明此函数确实是由操作系统调用的!
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return 0; //未处理任何窗口消息
}
/*******************************************************************************
MSG结构:
typedef struct tagMSG {
HWND hwnd; //消息所指向的窗口的句柄
UINT message; //每条消息在头文件WINUSER.H中都定义了一个以WM为前缀的消息标识符,如按下鼠标左键WM_lbuttondown。
WPARAM wParam; //一个32为的“消息参数”,该参数的含义和取值取决于具体的消息
LPARAM lParam; //一个32位的消息参数,该参数的含义和取值同样取决于具体的消息
DWORD time; //消息进入消息队列的时间
POINT pt; //消息进入消息队列中时鼠标指针的位置坐标
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
*******************************************************************************
GetMessage函数:从调用线程的消息队列里取得一个消息并将其放于指定的结构。
BOOL WINAPI GetMessageA(
_Out_ LPMSG lpMsg, //指向MSG结构的指针,该结构从线程的消息队列接收消息信息
_In_opt_ HWND hWnd,//想获取哪个窗口的消息,为NULL 时是获取所有窗口的消息
_In_ UINT wMsgFilterMin,//获取消息的 ID 编号最小值,如果小于这个值就不获取
_In_ UINT wMsgFilterMax);//获取消息的 ID 编号最大值,如果大于这个值就不获取
如果wMsgFilterMin和wMsgFilterMax都为零,则GetMessage返回所有可用消息(即,不执行范围过滤)。
__in:输入参数;__out:输出参数;__in_opt:可选的输入参数,返回后不会改变其值。
*******************************************************************************
TranslateMessage函数:将虚拟键消息转换为字符消息。字符消息将发布到调用线程的消息队列中,以在线程下次调用GetMessage或PeekMessage函数时读取。
BOOL TranslateMessage(
const MSG *lpMsg //指向MSG结构的指针,该结构包含通过使用GetMessage或
//PeekMessage函数从调用线程的消息队列中检索到的消息信息。
);
*******************************************************************************
DispatchMessage函数:将消息调度到窗口过程。它通常用于调度由GetMessage函数检索的消息。
LRESULT DispatchMessage(
const MSG *lpMsg //指向包含消息的结构的指针。
);
*/
总结
消息循环是Windows程序的核心,主程序中使用一个while循环结构不间断的从窗口消息队列中获取消息,GetMessage函数获取到WM_QUIT标记后返回0,退出消息循环。如果获取的是其它消息,则交给TranslateMessage函数和DispatchMessage函数处理。如果从窗口消息队列中获取的是键盘按键消息WM_KEYDOWN,则通过TranslateMessage函数将键盘消息的虚拟键码翻译字符并转换成WM_CHAR消息后分发给操作系统,再次送入消息队列。如果获取的是非WM_KEYDOWN消息,则直接交给DispatchMessage函数分发给Windows操作系统,Windows操作系统再回调窗口过程处理消息。
【注】此时,由于窗口过程尚未处理任何消息,屏幕仍无法显示窗口。
2.2.5 第12练:窗口过程
/*------------------------------------------------------------------------
012 编程达人win32 API每日一练
第12个例子WndProc.c:处理消息机制 ---窗口过程
WndProc 函数
WM_CREATE消息
DefWindowProc函数
注意:关闭窗口后,程序仍然没有退出。
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
/*******************************回调函数**************************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/*************************程序入口 ******************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin"); // 窗口类名
/***********************************1、设计窗口类*****************************/
WNDCLASS wndclass; //定义窗口类变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口风格
wndclass.lpfnWndProc = WndProc; //窗口处理函数---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
wndclass.lpszMenuName = NULL; //装载窗口菜单,此处程序没有
wndclass.lpszClassName = szAppName; // 窗口类名---程序的名字
/********************************2、注册窗口类*****************************/
if (!RegisterClass(&wndclass)) //未成功注册
{
MessageBox(NULL, TEXT("注册失败!"),szAppName, MB_ICONEXCLAMATION);
return 0;
}
/*****************************3、创建窗口************************************/
HWND hwnd;
hwnd = CreateWindow(szAppName, //已注册的窗口类名
TEXT("我的第一个窗口程序!"),//窗体标题
WS_OVERLAPPEDWINDOW, //窗体风格
CW_USEDEFAULT, //窗体左上角x坐标
CW_USEDEFAULT, //窗体左上角y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //父窗口句柄,此处NULL
NULL, //窗口菜单句柄,此处NULL
hInstance,//应用程序句柄
NULL
);
/*创建窗口完成(只存在于内存),系统会发送第一条消息WM_CREATE,注意此时,程序并未执行到消息循环处,操作系统绕过应用程序的消息队列,直接向应用程序发出。该消息在所有的消息之前,目的是为了在程序程序执行之前可以有机会进行一些初始化工作或装载动态链接库等操作。
A) CreateWindow 的第一个参数就是窗口类名,通过这个名字可以找到刚才注册的窗口类,然后再根据它来创建窗口。
B) 显示器上的坐标与数学中的不同,显示器的左上角是坐标原点,从原点向右是x轴,向下是y轴,都是正坐标,没有负数。
C) 参数 hInstance 是通过主函数 WinMain 传入的。
【注意】通过 CreateWindows() 函数创建窗口后,仅仅是为窗口分配了内存空间,获得了句柄,但窗口并没有显示出来,所以还需要调用 ShowWindow() 函数来显示窗口。
*/
/********************************4、显示窗口和更新窗口************************/
ShowWindow(hwnd, nCmdShow); //发送WM_SIZE和WM_SHOWWINDOW消息送入消息队列
UpdateWindow(hwnd); //发送WM_PAINT消息给窗口过程
/******************************5、消息循环***********************************/
MSG msg; //定义消息变量
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将虚拟键消息转换为字符消息
DispatchMessage(&msg); //分发消息函数
}
return msg.wParam;//msg.wParam 来自一条表示退出的消息,返回这个值给系统,从而退出
}
/************************回调函数——消息处理过程****************************/
//窗口回调函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{//创建窗口消息,此消息是一个应用程序发送的第一个消息,也是唯一的一次!
case WM_CREATE:
return 0;
//break; //也可以
default: //可以放在最后
//调用默认窗口过程以为应用程序未处理的任何窗口消息提供默认处理
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
//return DefWindowProc(hwnd, message, wParam, lParam); //默认窗口过程
}
/*************************************注意*************************************
WndProc 函数:
1:所有的消息都由操作系统负责管理,所有的消息先进入总的系统消息队列,再由系统消息队列分发给各个窗口消息队列!
2:窗口回调函数Wndproc是由操作系统调用的,所有的队列消息进入消息循环,由消息循环把消息分发给操作系统,再由操作系统调用窗口过程Wndproc函数,而Wndproc窗口过程根据message的不同而调用相应的处理过程!如果是非队列消息则由操作系统直接交给窗口过程。窗口过程即回调函数,在switch语句中使用message参数,其中各个消息由单独的case语句处理。
请注意,每种情况都会为每个消息返回一个特定的值。对于未处理的消息,窗口过程将调用操作系统默认的窗口过程DefWindowProc函数处理。
3:上面这个程序可以改造一下,再添加几个消息处理过程,比如鼠标左键按下WM_LBUTTONDOWN,鼠标左键抬起WM_LBUTTONUP定时器消息WM_TIMER,绘图消息WM_PAINT消息等等。我们将注意测试每个消息!
4:其实,WINDOWS程序也可以自己去创建函数!自己去自定义消息处理过程!比如,自己定义了一个函数,想在鼠标左键点击后去调用这个函数,那就可以在WM_LBUTTONDOWN这个消息处理过程中加上自己定义的函数调用就可以了!
5:上面这个程序是个非常简单的程序,但是他足以说明WINDOWS程序的消息机制!几乎所有的程序都包括上面这段代码。不管多么复杂的WINDOWS程序都必须包括上面这段代码,因为他是再简单不过的了,只是创建了一个窗口!
但是,再复杂的程序无非也就是在Wndproc函数中的switch多加几个case嘛!无非就是加上什么鼠标,键盘,定时器等等,但原里都是一样的!他的消息处理过程都是一样的!主要是把他的消息机制弄懂,其它的都非常easy! ******************************************************************************/
/*
WM_CREATE消息:当应用程序通过调用CreateWindowEx或CreateWindow函数请求创建窗口时发送。(在函数返回之前发送消息。)
在创建窗口之后,但在该窗口变为可见之前,新窗口的窗口过程会收到此消息。窗口通过其WindowProc函数接收此消息。
#define WM_CREATE 0x0001
指向CREATESTRUCT结构的指针,该结构包含有关正在创建的窗口的信息。
返回值类型:LRESULT
如果应用程序处理此消息,则应返回零以继续创建窗口。如果应用程序返回–1,则窗口将被销毁,并且CreateWindowEx或CreateWindow函数将返回NULL句柄。
*******************************************************************************
DefWindowProc函数:调用Windows操作系统默认的窗口过程来为应用程序没有处理的任何窗口消息提供转换的处理。该函数确保每一个消息得到处理。
绝大多少消息都是由DefWindowProc函数按照默认的方式处理。
LRESULT LRESULT DefWindowProcA(
HWND hWnd,//接收到消息的窗口过程的句柄。
UINT Msg,//消息。
WPARAM wParam,//附加消息信息。此参数的内容取决于Msg参数的值。
LPARAM lParam//附加消息信息。此参数的内容取决于Msg参数的值。
);
返回值类型:LRESULT。返回值是消息处理的结果,并取决于消息。
*/
运行结果:
图2-4 第一个窗口
总结
1.窗口过程是一个回调函数,Windows操作系统将消息参数传递给WndProc窗口过程处理消息。窗口过程内是一个switch结构,根据message消息ID使用case语句处理。本例case WM_CREATE:语句块中什么都没有处理,直接return 0返回操作系统,我们创建的窗口就在屏幕上显示出来了。
2.WM_CREATE消息是Windows程序产生的第一个消息,也是唯一一次产生的消息。通常我们在处理WM_CREATE消息时做一些窗口的初始化工作,例如添加子窗口。
3.细心的读者会发现,当我们点击窗口右上角系统菜单关闭窗口后,程序仍然没有退出,这是怎么回事呢?是否还记得,我们讲解消息循环时,只有当GetMessage函数获取WM_QUIT标记时才会结束消息循环,退出程序。因此,虽然我们关闭了窗口,但是消息循环仍然在运行,等待获取新的窗口消息。
4.动手实验:为了加深映像,理解Windows程序的消息驱动机制,请读者在case WM_CREATE:处下一个断点,然后单步跟踪程序的运行,观察程序执行的流程。我们会惊讶的发现,Windows程序并不是按照代码的先后顺序执行的,这与我们之前学习的面向过程的C语言或汇编语言程序完全不同。当我们处理WM_CREATE消息return 0返回后,返回到了窗口过程的结尾,然后跳转到CreateWindow函数,接着执行ShowWindow函数后,屏幕出现窗口。这也证明了Windows程序是跟着消息传递的顺序执行的。我们看到的Windows程序仅仅是浮出水面的冰山一角。在后面的学习中,希望读者能够多做一些这样的实验,体会消息驱动机制。
5.【注意】switch结构的语法,DefWindowProc函数可以放在default语句后,也可以直接放到return语句后。如果将DefWindowProc函数放置在return语句后,则case语句中可以直接使用return 0;返回。
2.2.6 第13练:处理WM_PAINT消息
/*------------------------------------------------------------------------
013 编程达人win32 API每日一练
第13个例子WM_PAINT.C:回调函数---处理WM_PAINT消息
WM_PAINT消息
BeginPaint函数
PAINTSTRUCT结构
EndPaint函数
GetClientRect函数
TextOut函数
Ellipse函数
DrawText函数
注意:关闭窗口后,程序仍然没有退出。
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
/*******************************回调函数**************************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/*************************程序入口 ******************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin"); // 窗口类名
/***********************************1、设计窗口类*****************************/
WNDCLASS wndclass; //定义窗口类结构变量
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口风格
wndclass.lpfnWndProc = WndProc; //窗口处理函数---回调函数的地址
wndclass.cbClsExtra = 0; //窗口扩展:预留空间的附加值,此程序没用到
wndclass.cbWndExtra = 0; //窗口实例扩展:预留空间的附加值,此程序没用到
wndclass.hInstance = hInstance; //应用程序实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //装载窗口标题栏图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //装载窗口鼠标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
wndclass.lpszMenuName = NULL; //装载窗口菜单,此处程序没有
wndclass.lpszClassName = szAppName; // 窗口类名---程序的名字
/********************************2、注册窗口类***************************/
if (!RegisterClass(&wndclass)) //未成功注册
{
MessageBox(NULL, TEXT("注册失败!"),
szAppName, MB_ICONEXCLAMATION);
return 0;
}
/***************************3、创建窗口类********************************/
HWND hwnd;//定义窗口句柄
hwnd = CreateWindow(szAppName, //已注册窗口类名
TEXT("我的第一个窗口程序!"),//窗体标题
WS_OVERLAPPEDWINDOW, //窗体风格
CW_USEDEFAULT, //窗体左上角x坐标
CW_USEDEFAULT, //窗体左上角y坐标
CW_USEDEFAULT, //窗口的宽度
CW_USEDEFAULT, //窗口的高度
NULL, //父窗口句柄,此处NULL
NULL, //窗口菜单句柄,此处NULL
hInstance,//应用程序句柄
NULL
);
/********************************4、显示窗口和更新窗口************************/
ShowWindow(hwnd, nCmdShow);//发送WM_SIZE和WM_SHOWWINDOW消息送入消息队列
UpdateWindow(hwnd); //发送WM_PAINT消息给窗口过程
/******************************5、消息循环***********************************/
MSG msg;//定义消息变量
*/
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); //将虚拟键消息转换为字符消息
DispatchMessage(&msg); //分发消息函数
}
return msg.wParam;//msg.wParam 来自一条表示退出的消息,返回这个值给系统
}
/*************************回调函数——消息处理过程***************************/
//窗口回调函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC; //设备环境——绘图的地方
PAINTSTRUCT ps; //绘图结构体变量
RECT rect; //绘图区范围
switch (message)
{
case WM_CREATE: //创建窗口消息
//return 0;
break; //也可以
case WM_PAINT://绘图函数,在窗口客户区绘画!
{
hDC = BeginPaint(hwnd, &ps);
/*ps结构的第3个成员为RECT rcPaint,是指一个无效区域。当调用BeginPaint函数时,由操作系统填充。因此,严格来讲,hDC为无效区效的“设备环境”,在此区域外的作图,都将被忽略(即x,y坐标在该区域外的)。*/
//获得客户区大小
GetClientRect(hwnd, &rect);
//绘制字符串
TextOut(hDC, 200, 200, TEXT("爱达人!"), lstrlen(TEXT("爱达人!")));
//绘制椭圆图形
Ellipse(hDC, 250, 250, 1200, 500);
//绘制格式化文本,客户区中间垂直居中对齐
DrawText(hDC, TEXT("我的第一个窗口程序!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(hwnd, &ps); //绘图结束,释放设备环境句柄
break;
}
//default: //可以放在最后
//return DefWindowProc(hwnd, message, wParam, lParam); //调用默认窗口过程以为应用程序未处理的任何窗口消息提供默认处理
}
//return 0;
return DefWindowProc(hwnd, message, wParam, lParam); //调用默认窗口过程以为应用程序未处理的任何窗口消息提供默认处理
}
/************************************注意**************************************
WM_PAINT消息:
BeginPaint函数:指定的窗口绘画和填充一个PAINTSTRUCT结构有关绘画的信息。
HDC BeginPaint(
HWND hWnd,//处理要重绘的窗口
LPPAINTSTRUCT lpPaint//指向将接收绘画信息的PAINTSTRUCT结构的指针
);
返回值:
如果函数成功,则返回值是指定窗口的显示设备上下文的句柄。
如果函数失败,则返回值为NULL,表明没有显示设备上下文可用。
*****************************************************************************
PAINTSTRUCT结构:包含应用程序的信息。此信息可用于绘制该应用程序拥有的窗口的客户区。
typedef struct tagPAINTSTRUCT {
HDC hdc; //用于绘画的显示设备DC的句柄
BOOL fErase; //这个值非零表示窗口的背景需要擦除,其他情况为零。
RECT rcPaint;//一个RECT结构,以相对于客户区域左上角的设备单位指定请求绘制的矩形的左上角和右下角坐标。
BOOL fRestore;//保留;由系统内部使用。
BOOL fIncUpdate;//保留;由系统内部使用。
BYTE rgbReserved[32];//保留;由系统内部使用。
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;
*******************************************************************************
EndPaint函数:标记指定窗口绘画的结束。每次调用BeginPaint函数都需要此函数,但是仅在完成绘制之后。
BOOL EndPaint(
HWND hWnd,//处理已重新绘制的窗口。
const PAINTSTRUCT *lpPaint//指向一个PAINTSTRUCT结构的指针,该结构包含BeginPaint检索的绘画信息。
);
返回值:返回值始终为非零。
*******************************************************************************
GetClientRect函数:检索窗口的工作区的坐标。客户坐标指定客户区域的左上角和右下角。因为客户坐标是相对于窗口客户区的左上角的,所以左上角的坐标是(0,0)。
BOOL GetClientRect(
HWND hWnd,//要获取其客户坐标的窗口的句柄。
LPRECT lpRect//指向接收客户坐标的RECT结构的指针。包括客户区左上角坐标(0,0)和客户区的宽度和高度。
);
返回值类型:布尔
如果函数成功,则返回值为非零。
如果函数失败,则返回值为零。要获取扩展的错误信息,请调用GetLastError。
*******************************************************************************
TextOut函数:用当前选择的字体、背景颜色和正文颜色将一个字符串写到指定位置。
BOOL TextOutA(
HDC hdc,//设备环境句柄。
int x, //系统用来对齐字符串的参考点的x坐标(以逻辑坐标表示)。
int y, //系统用来对齐字符串的参考点的y坐标(以逻辑坐标表示)。
LPCSTR lpString,//指向要绘制的字符串的指针。字符串不需要以零结尾,因为cString参数指定了字符串的长度。
int cString //lpString指向的字符串的长度,以字符为单位。
);
*******************************************************************************
Ellipse函数:画一个椭圆,椭圆的中心是限定矩形的中心,使用当前画笔画椭圆,用当前的画刷填充椭圆。
BOOL Ellipse(
HDC hdc, //设备上下文的句柄
int left, //边界矩形左上角的x坐标(以逻辑坐标表示)。
int top, //边界矩形左上角的y坐标(以逻辑坐标表示)。
int right,//边界矩形右下角的x坐标(以逻辑坐标表示)。
int bottom//边界矩形右下角的y坐标(以逻辑坐标表示)。
);
返回值:
如果函数成功,则返回值为非零。
如果函数失败,则返回值为零。
*******************************************************************************
DrawText函数:在指定的矩形绘制格式化文本。它根据指定的方法(展开标签,对齐字符,换行等)来格式化文本。
若要指定其他格式设置选项,请使用DrawTextEx函数。
int DrawText(
HDC hdc, //设备上下文的句柄。
LPCTSTR lpchText, //指向指定要绘制文本的字符串的指针。如果nCount参数为-1,则字符串必须以零结尾。
int nCount, //字符串的长度(以字符为单位)。
LPRECT lpRect, //指向RECT结构的指针,该结构包含要在其中格式化文本的矩形(在逻辑坐标中)。
UINT uformat //格式化文本的方法。此参数可以是以下一个或多个值。DT_CENTER---在矩形中将文本水平居中。
);
*/
运行结果:
图2-5 WM_PAINT消息
提示
此例主程序没有任何变化,窗口过程中增加了一个WM_PAINT消息的处理。绝大多数绘图都在WM_PAINT消息中处理的。
如果要绘图必须要满足以下几个条件:
1.取得绘图设备环境的句柄。
2.指定绘图的矩形区域。
3.定义一个PAINTSTRUCT绘图结构体变量。
4.绘图结束,释放设备环境句柄。
在绘图开始前,调用BeginPaint(hwnd, &ps);返回窗口客户区设备环境句柄。待绘图结束后,调用EndPaint(hwnd, &ps);释放设备环境句柄。这是在WM_PAINT消息中绘图的固定模式。
在BeginPaint和EndPaint之间首先调用GetClientRect函数获取窗口客户区的矩形。接着再分别调用TextOut函数、Ellipse函数和DrawText函数在窗口客户区绘制字符串、椭圆和格式化字符串。
【注意】TextOut函数、Ellipse函数和DrawText函数的第一个参数都是hDC设备环境句柄,这是GDI绘图函数的共同特点。如果想要绘制图形,必须调用显示设备。Windows应用程序不需要熟悉设备驱动,直接使用Windows系统返回的设备环境句柄就可以绘图了。此外,由于设备环境占用的内存空间比较大,当不再使用时,需要即使释放。因此,BeginPaint函数和EndPaint函数总是同时成对出现,不可以缺省。
本例中调用的API函数请参阅注释,如要查看更为详细的信息可以查阅MSDN文档。
【注意】MSDN上对API接口函数的解释是非常全面的,我们不需要死记硬背,用的多了自然就熟悉了,过段时间忘了还可以再查。另外就是MSDN有很多暂时无关的解释不需要了解,我们只需要关注本例中的用法就可以了,待以后遇到时再去了解,这样可以大大节约时间,提高学习效率。
2.2.7 第14练:处理WM_DESTROY消息
/*------------------------------------------------------------------------
014 编程达人win32 API每日一练
第14个例子WM_DESTROY.C:回调函数---处理WM_DESTROY消息
WM_DESTROY消息
PostQuitMessage函数
注意:关闭窗口后,程序退出。
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
/********************************回调函数*************************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //消息回调函数
/*************************程序入口 ******************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
……(略)
return msg.wParam;//msg.wParam 来自一条表示退出的消息,返回这个值给系统
}
/*************************回调函数——消息处理过程***************************/
//窗口回调函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC; //设备环境——绘图的地方
PAINTSTRUCT ps; //绘图结构体变量
RECT rect; //绘图区范围
switch (message)
{
case WM_CREATE: //创建窗口消息
//return 0;
break; //也可以
case WM_PAINT://绘图函数,在窗口上绘图!
hDC = BeginPaint(hwnd, &ps);
//获得客户区大小
GetClientRect(hwnd, &rect);
//绘制字符串
TextOut(hDC, 200, 200, TEXT("爱达人!"), lstrlen(TEXT("爱达人!")));
//绘制椭圆图形
Ellipse(hDC, 250, 250, 1200, 500);
//绘制格式化文本,客户区中间垂直居中对齐
DrawText(hDC, TEXT("我的第一个窗口程序!"), -1, &rect,DT_CENTER |
DT_VCENTER | DT_SINGLELINE);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY://处理退出消息
//绘制椭圆图形
//Ellipse(hDC, 250, 250, 1200, 500);//测试
PostQuitMessage(0);//此消息直接进入消息队列的头部!默认情况下,
//DefWindowProc函数调用DestroyWindow函数来销毁窗口。
break;
//default: //可以放在最后
//return DefWindowProc(hwnd, message, wParam, lParam);
}
//return 0;
//调用默认窗口过程以为应用程序未处理的任何窗口消息提供默认处理
return DefWindowProc(hwnd, message, wParam, lParam);
}
/*************************************注意************************************
/*
WM_DESTROY消息:窗口销毁后(调用DestroyWindow()后),消息队列添加WM_DESTROY消息。
流程:用户通过点击关闭程序按钮后,消息队列增加一条消息WM_CLOSE,然后程序从消息队列中取走WM_CLOSE,WM_CLOSE消息的默认处理方式为调用DestroyWindow()向窗口发送一个WM_DESTROY消息,消息队列增加WM_DESTROY,主程序消息循环再次取出后交给Windows系统,Windows系统调用窗口过程处理WM_DESTROY消息,处理方式为调用ostQuitMessage(),在消息队列中添加WM_QUIT标记,消息循环获取WM_QUIT后退出消息循环,程序退出。
如果窗口过程不处理WM_CLOSE消息,默认情况下,默认窗口过程DefWindowProc函数同样是调用DestroyWindow函数。
****************************************************************************
PostQuitMessage函数:向系统指示线程已请求终止(退出)。通常用于响应WM_DESTROY消息。
void PostQuitMessage(
int nExitCode //指定的退出码,是一个整数值。这个退出码通常用来表示程序执行的结果或状态。此值用作WM_QUIT消息的wParam参数。
);
备注
PostQuitMessage函数添加一个WM_QUIT退出标记到线程的消息队列并立即返回;这个函数只是向系统表明这个线程在将来的某个时候请求退出。
当线程从它的消息队列中检索WM_QUIT标记时,它应该退出它的消息循环并将控制权返回给系统。返回到系统的退出值必须是WM_QUIT消息的wParam参数。
(有些书上将WM_QUIT视为一个消息,插入到消息队列头,然后立即退出,消息队列中剩余的消息不会被处理,虽然比较形象,但是不太严谨,WM_QUIT其实是一个退出标记。)。
*/
总结
1.本例在关闭窗口后,终于可以正常退出程序了。原因就在于窗口过程WndProc处理了WM_DESTROY消息,向窗口消息队列添加一个WM_QUIT退出标记。消息循环GetMessage函数获取WM_QUIT后返回值为0,退出消息循环,进而退出进程。
2.动手实验:在WM_DESTROY消息处理模块添加一个绘制椭圆的函数:
Ellipse(hDC, 250, 250, 1200, 500);
编译器编译后提示:error C4700: 使用了未初始化的局部变量“hDC”。
错误原因:窗口过程中设置的hDC为局部变量,在WM_PAINT消息模块中使用,但是在WM_DETROY模块无法识别,说明局部变量在窗口过程中不可以跨消息模块使用,作用域仅限一个消息模块内。记得水面以下的冰山部分并不是由我们实现的,Windows程序是消息驱动的程序。
解决方案:将变量定义为全局区的静态变量static HDC hDC;如果一个变量需要跨消息模块使用,请将其定义为static变量。但是在这里并不建议将hDC定义为static变量,因为hDC会持续占用大块内存空间,随用随取就可以了。
在Ellipse函数处下断点,当关闭窗口后,执行Ellipse函数调用并不会显示窗口。原因很简单,因为窗口已经不存在了。
本文摘自编程达人系列教材《Windows API每日一练》。