当前位置: 首页 > news >正文

新书推荐:2.2.4 第11练:消息循环

/*------------------------------------------------------------------------

 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每日一练》。

相关文章:

新书推荐:2.2.4 第11练:消息循环

/*------------------------------------------------------------------------ 011 编程达人win32 API每日一练 第11个例子GetMessage.c&#xff1a;消息循环 MSG结构 GetMessage函数 TranslateMessage函数&#xff1a;将虚拟键消息转换为字符消息 DispatchMessage函数…...

MASA:匹配一切、分割一切、跟踪一切

文章目录 摘要1、引言2、相关工作2.1、学习实例级关联2.2、Segment and Track Anything 模型 3、方法3.1、预备知识&#xff1a;SAM3.2、通过分割任何事物来匹配任何事物3.2.1、MASA流程3.2.2、MASA适配器3.2.3、推理 4、实验4.1、实验设置4.2、与最先进技术的比较4.3、消融研究…...

Websocket前端传参:深度解析与实战应用

Websocket前端传参&#xff1a;深度解析与实战应用 在现代Web开发中&#xff0c;Websocket作为一种双向通信协议&#xff0c;已经广泛应用于实时数据传输场景。前端传参作为Websocket通信的重要组成部分&#xff0c;其正确性和高效性直接影响到应用的性能和用户体验。本文将深…...

造假高手——faker

在测试写好的代码时通常需要用到一些测试数据&#xff0c;大量的真实数据有时候很难获取&#xff0c;如果手动制造测试数据又过于繁重无聊&#xff0c;显得不够优雅&#xff0c;今天我们介绍的faker这个轮子可以完美的解决这个问题。faker是一个用于生成各种类型假数据的库&…...

前端工程化工具系列(十二)—— PostCSS(v8.4.38):CSS 转换工具

PostCSS 是转换 CSS 语法的工具。它提供 API 来对 CSS 文件进行分析和修改它的规则。 PostCSS 本身并不能直接使用&#xff0c;主要是使用基于 PostCSS 编写的插件。 1 安装 pnpm add -D postcss-import postcss-nested postcss-preset-env cssnano2 配置 在项目根目录下创…...

Scanpy(3)单细胞数据分析常规流程

单细胞数据分析常规流程 面对高效快速的要求上,使用R分析数据越来越困难,转战Python分析,我们通过scanpy官网去学习如何分析单细胞下游常规分析。 数据3k PBMC来自健康的志愿者,可从10x Genomics免费获得。在linux系统上,可以取消注释并运行以下操作来下载和解压缩数据。…...

【Stable Diffusion】(基础篇二)—— Stable Diffusion图形界面介绍和基本使用流程

本系列笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili 在上一篇博客中&#xff0c;我们成功…...

OpenCv之简单的人脸识别项目(动态处理页面)

人脸识别 准备九、动态处理页面1.导入所需的包2.设置窗口2.1定义窗口外观和大小2.2设置窗口背景2.2.1设置背景图片2.2.2创建label控件 3.定义视频处理脚本4.定义相机抓取脚本5.定义关闭窗口的函数6.按钮设计6.1视频处理按钮6.2相机抓取按钮6.3返回按钮 7.定义关键函数8.动态处理…...

【Linux】进程间通信

目录 一、进程间通信概念 二、进程间通信的发展 三、进程间通信的分类 四、管道 4.1 什么是管道 4.2 匿名管道 4.2 基于匿名管道设计进程池 4.3 命名管道 4.4 用命名管道实现server&client通信 五、system V共享内存 5.1 system V共享内存的引入 5.2 共享内存的…...

UI与前端:揭秘两者的微妙差异

UI与前端&#xff1a;揭秘两者的微妙差异 在数字化时代的浪潮中&#xff0c;UI设计和前端开发已成为塑造用户体验的两大核心力量。然而&#xff0c;这两者之间究竟有何区别&#xff1f;本文将深入剖析UI设计与前端开发的四个方面、五个方面、六个方面和七个方面的差异&#xf…...

idea如何根据路径快速在项目中快速打卡该页面

在idea项目中使用快捷键shift根据路径快速找到该文件并打卡 双击shift(连续按两下shift) -粘贴文件路径-鼠标左键点击选中跳转的路径 自动进入该路径页面 例如&#xff1a;我的实例路径为src/views/user/govType.vue 输入src/views/user/govType或加vue后缀src/views/user/go…...

探索成功者的特质——俞敏洪的观点启示

在人生的舞台上&#xff0c;我们常常对成功者充满好奇与敬仰&#xff0c;试图探寻他们成功的奥秘。俞敏洪指出&#xff0c;成功者都具备七个特质&#xff0c;而这些特质与家庭背景和大学的好坏并无直接关系。让我们深入剖析这七个特质&#xff0c;或许能从中获得对我们自身成长…...

MCU的环形FIFO

fifo.h #ifndef __FIFO_H #define __FIFO_H#include "main.h"#define RINGBUFF_LEN (500) //定义最大接收字节数 500typedef struct {uint16_t Head; // 头指针 指向可读起始地址 每读一个&#xff0c;数字1uint16_t Tail; // 尾指针 指…...

使用proteus仿真51单片机的流水灯实现

proteus介绍&#xff1a; proteus是一个十分便捷的用于电路仿真的软件&#xff0c;可以用于实现电路的设计、仿真、调试等。并且可以在对应的代码编辑区域&#xff0c;使用代码实现电路功能的仿真。 汇编语言介绍&#xff1a; 百度百科介绍如下&#xff1a; 汇编语言是培养…...

【漏洞复现】Apache OFBiz 路径遍历导致RCE漏洞(CVE-2024-36104)

0x01 产品简介 Apache OFBiz是一个电子商务平台&#xff0c;用于构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类应用系统。是美国阿帕奇(Apache)基金会的一套企业资源计划(ERP)系统。该系统提供了一整套基于Java的Web应用程序组件和工具。 0x02 …...

数据库表中创建字段查询出来却为NULL?

起因&#xff1a; 今天新创建了一张表,其中一个字段命名为"word_num"带下划线&#xff0c;我在前端页面怎么也查询不出来word_num的值&#xff0c;后来在后端接口处打印了一下数据库查询出来的数据&#xff0c;发现这个字段一直为NULL&#xff0c;然后我就想到是不是…...

缓存方法返回值

1. 业务需求 前端用户查询数据时,数据查询缓慢耗费时间; 基于缓存中间件实现缓存方法返回值:实现流程用户第一次查询时在数据库查询,并将查询的返回值存储在缓存中间件中,在缓存有效期内前端用户再次查询时,从缓存中间件缓存获取 2. 基于Redis实现 参考1 2.1 简单实现 引入…...

【十大排序算法】快速排序

在乱序的世界中&#xff0c;快速排序如同一位智慧的园丁&#xff0c; 以轻盈的手法&#xff0c;将无序的花朵们重新安排&#xff0c; 在每一次比较中&#xff0c;沐浴着理性的阳光&#xff0c; 终使它们在有序的花园里&#xff0c;开出绚烂的芬芳。 文章目录 一、快速排序二、…...

linux系统ubuntu中在命令行中打开图形界面的文件夹

在命令行中打开当前路径&#xff0c;以文件管理器的形式打开&#xff1a; 命令 # 打开文件管理器 当前的路径 nautilus .nautilus 是一个与 GNOME 桌面环境集成的文件管理器的命令行启动程序。在 Linux 系统中&#xff0c;特别是使用 GNOME 作为桌面环境时&#xff0c;用户经…...

【C++11数据结构与算法】C++ 栈

C 栈(stack) 文章目录 C 栈(stack)栈的基本介绍栈的算法运用单调栈实战题LC例题&#xff1a;[321. 拼接最大数](https://leetcode.cn/problems/create-maximum-number/)LC例题&#xff1a;[316. 去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) 栈的基…...

pdf文件如何防篡改内容

PDF文件防篡改内容的方法有多种&#xff0c;以下是一些常见且有效的方法&#xff0c;它们可以帮助确保PDF文件的完整性和真实性&#xff1a; 加密PDF文档&#xff1a; 原理&#xff1a;通过设置密码来保护PDF文档&#xff0c;防止未经授权的访问和修改。注意事项&#xff1a;密…...

QT 音乐播放器【二】 歌词同步+滚动+特效

文章目录 效果图概述代码解析歌词歌词同步歌词特效 总结 效果图 概述 先整体说明一下这个效果的实现&#xff0c;你所看到的歌词都是QGraphicsObject&#xff0c;在QGraphicsView上绘制(paint)出来的。也就是说每一句歌词都是一个图元(item)。 为什么用QGraphicsView框架&…...

关于怎么用Cubemx生成的USBHID设备实现读取一体的鼠标键盘设备(改进版)

主要最近做了一个要用STM32实现读取鼠标键盘一体的那种USB设备&#xff0c;STM32的界面上要和电脑一样的能通过这个USB接口实现鼠标移动&#xff0c;键盘的按键。然后我就很自然的去参考了正点原子的例程&#xff0c;可是找了一圈&#xff0c;发现正点原子好像用的库函数&#…...

Soildworks学习笔记(二)

放样凸台基体&#xff1a; 自动生成连接两个物体两个面的基体&#xff1a; 2.旋转切除&#xff1a; 3.剪切实体&#xff1a; 4.转换实体引用&#xff1a; 将实体的轮廓线转换至当前草图使其成为当前草图的图元,主要用于在同一平面或另一个坐标中制作草图实体或其尺寸的副本。 …...

Linux配置uwsgi环境

Linux配置uwsgi环境 1.进入虚拟环境 source /envs/django_-shop-system/bin/activate2.安装uwsgi pip install uwsgi3.基于uwsgi运行项目 – 基于配置文件 在项目目录下创建配置文件 #socket 0.0.0.0:8005 http 0.0.0.0:8005 # http120.55.47.111:8005 chdir/opt/www/djang…...

Nagios的安装和使用

*实验* *nagios安装和使用* Nagios 是一个监视系统运行状态和网络信息的监视系统。Nagios 能监视所指定的本地或远程主机以及服务&#xff0c;同时提供异常通知功能等. Nagios 可运行在 Linux/Unix 平台之上&#xff0c;同时提供一个可选的基于浏览器的 WEB 界面以方便系统管…...

Numba 的 CUDA 示例(4/4):原子和互斥

本教程为 Numba CUDA 示例 第 4 部分。 本系列第 4 部分总结了使用 Python 从头开始学习 CUDA 编程的旅程 介绍 在本系列的前三部分&#xff08;第 1 部分&#xff0c;第 2 部分&#xff0c;第 3 部分&#xff09;中&#xff0c;我们介绍了 CUDA 开发的大部分基础知识&#xf…...

【机器学习】机器学习引领AI:重塑人类社会的新纪元

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀机器学习引领AI &#x1f4d2;1. 引言&#x1f4d5;2. 人工智能&#xff08;AI&#xff09;&#x1f308;人工智能的发展&#x1f31e;应用领…...

【制作面包game】

编写一个制作面包的游戏代码涉及到游戏设计、编程和用户界面设计等多个方面。这里我可以提供一个简化版本的Python代码示例&#xff0c;用于创建一个基本的面包制作游戏。这个游戏将会有一个简单的用户界面&#xff0c;玩家可以通过输入命令来制作面包。 游戏的基本流程如下&a…...

Django更改超级用户密码

Django更改超级用户密码 1、打开shell 在工程文件目录下敲入&#xff1a; python manage.py shell再在python交互界面输入&#xff1a; from django.contrib.auth.models import User user User.objects.get(username root) user.set_password(123456) user.save()其中ro…...

网站访问量大/搜索引擎案例分析结论

在上一部分&#xff0c;我们完成了数据库的一部分安装&#xff0c;下面接着来安装。检查Oracle的环境变量&#xff0c;确保PATH中包括/u01/app/oracle/product/10.2.0/db_1/bin执行dbca命令&#xff0c;会弹出数据库配置助理界面。点击“Next”进入下一个界面。这里选择创建数据…...

山东省疫情防控政策/seo厂商

1、Java编译器的重排序(Reording)操作有可能导致执行顺序和代码顺序不一致。 假设代码有两条语句&#xff0c;代码顺序是语句1先于语句2执行&#xff1b;那么只要语句2不依赖于语句1的结果&#xff0c;打乱它们的顺序对最终的结果没有影响的话&#xff0c;那么真正交给CPU去执行…...

网站app简单做/任何小说都能搜到的软件

连接格点 题目 思路&#xff1a;把二维坐标映射到一维的点上&#xff0c;就是一个最小生成树问题&#xff0c;再把点连线&#xff0c;先连上下再连左右可以省去排序过程 具体代码如下 #include<iostream> #include<algorithm>using namespace std;const int N …...

网站建设基础策划/百度快照推广是什么意思

5. Spring框架中的新功能和增强功能 III sunRainAmazing 5.1核心容器改造 1、诸如Bean使用Java 8默认方法检测和处理的注释&#xff0c;允许使用默认Bean方法从接口组态配置类。 2、配置类可以Import使用常规组件类声明&#xff0c;允许混合导入的配置类和组件类。 3、配置类…...

山东省建设部官方网站/如何网上销售自己的产品

问题总结一、什么是Nginx 是一个轻量级的高性能的反向代理&#xff0c;web服务器&#xff0c;实现非常高效的反向代理&#xff0c;负载均衡&#xff0c;可以处理高并发的连接数&#xff0c;支持5万并发 二、为什么要使用 跨平台性&#xff0c;配置简单&#xff0c; 反向代理&a…...

专业做网站服务/windows优化大师会员

如何实现富文本文字链接完全自定义效果图实现UITextView 的配置链接点击事件重定向效果图 环境&#xff1a;XCode12.3 - IOS14.3 语言&#xff1a;Objective-C 副标题为富文本实现的文字链接 实现 带链接的富文本只能使用 UITextView&#xff0c;使用 UILabel 无法完全自定…...