WIN32GUI消息机制简单探索

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37565736/article/details/78142024

利用现有图形库可以做到快速开发一个桌面程序,但当你不满足于库产生的控件想自己定义控件或者想了解图形系统运作时,却不能随心所欲。图形库把底层接口封装,固然大大简化了开发的工作量,但其也把具体实现隐与表层之下,WIN32API是操作系统留给我们的接口,直接用API进行GUI的简单设计无疑能够极大的贴近图形库运转的底层面貌,去了解消息循环的真正面貌。

以下为MSDN的创建一个主窗口的例子:

#include <windows.h> 
 
// Global variable 
 
HINSTANCE hinst; 
 
// Function prototypes. 
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int); 
InitApplication(HINSTANCE); 
InitInstance(HINSTANCE, int); 
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM); 
 
// Application entry point. 
 
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow) 
{ 
    MSG msg; 
 
    if (!InitApplication(hinstance)) 
        return FALSE; 
 
    if (!InitInstance(hinstance, nCmdShow)) 
        return FALSE; 
 
    BOOL fGotMessage;
    while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1) 
    { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    } 
    return msg.wParam; 
        UNREFERENCED_PARAMETER(lpCmdLine); 
} 
 
BOOL InitApplication(HINSTANCE hinstance) 
{ 
    WNDCLASSEX wcx; 
 
    // Fill in the window class structure with parameters 
    // that describe the main window. 
 
    wcx.cbSize = sizeof(wcx);          // size of structure 
    wcx.style = CS_HREDRAW | 
        CS_VREDRAW;                    // redraw if size changes 
    wcx.lpfnWndProc = MainWndProc;     // points to window procedure 
    wcx.cbClsExtra = 0;                // no extra class memory 
    wcx.cbWndExtra = 0;                // no extra window memory 
    wcx.hInstance = hinstance;         // handle to instance 
    wcx.hIcon = LoadIcon(NULL, 
        IDI_APPLICATION);              // predefined app. icon 
    wcx.hCursor = LoadCursor(NULL, 
        IDC_ARROW);                    // predefined arrow 
    wcx.hbrBackground = GetStockObject( 
        WHITE_BRUSH);                  // white background brush 
    wcx.lpszMenuName =  "MainMenu";    // name of menu resource 
    wcx.lpszClassName = "MainWClass";  // name of window class 
    wcx.hIconSm = LoadImage(hinstance, // small class icon 
        MAKEINTRESOURCE(5),
        IMAGE_ICON, 
        GetSystemMetrics(SM_CXSMICON), 
        GetSystemMetrics(SM_CYSMICON), 
        LR_DEFAULTCOLOR); 
 
    // Register the window class. 
 
    return RegisterClassEx(&wcx); 
} 
 
BOOL InitInstance(HINSTANCE hinstance, int nCmdShow) 
{ 
    HWND hwnd; 
 
    // Save the application-instance handle. 
 
    hinst = hinstance; 
 
    // Create the main window. 
 
    hwnd = CreateWindow( 
        "MainWClass",        // name of window class 
        "Sample",            // title-bar string 
        WS_OVERLAPPEDWINDOW, // top-level window 
        CW_USEDEFAULT,       // default horizontal position 
        CW_USEDEFAULT,       // default vertical position 
        CW_USEDEFAULT,       // default width 
        CW_USEDEFAULT,       // default height 
        (HWND) NULL,         // no owner window 
        (HMENU) NULL,        // use class menu 
        hinstance,           // handle to application instance 
        (LPVOID) NULL);      // no window-creation data 
 
    if (!hwnd) 
        return FALSE; 
 
    // Show the window and send a WM_PAINT message to the window 
    // procedure. 
 
    ShowWindow(hwnd, nCmdShow); 
    UpdateWindow(hwnd); 
    return TRUE; 
 
} 

总的来说创建一个主窗口大致分为三个部分:1.注册窗口类2.创建主窗口3.建立消息循环。

一。注册窗口类

这里有一个结构体WNDCLASSEX:

typedef struct tagWNDCLASSEX {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
  HICON     hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
这个结构体包含了窗口类的信息,被用于 RegisterClassEx (即注册窗口类的函数)和GetClassInfoEx这两个函数。这个结构体比较重要的一个参数是lpfnWndProc,lpfn即long pointer to function,是一个指向函数的长指针,指向窗口的处理函数。

Window Procedures的使用中,MSDN给出了其例子:

LRESULT CALLBACK MainWndProc(
    HWND hwnd,        // handle to window
    UINT uMsg,        // message identifier
    WPARAM wParam,    // first message parameter
    LPARAM lParam)    // second message parameter
{ 
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
            // Initialize the window. 
            return 0; 
 
        case WM_PAINT: 
            // Paint the window's client area. 
            return 0; 
 
        case WM_SIZE: 
            // Set the size and position of the window. 
            return 0; 
 
        case WM_DESTROY: 
            // Clean up window-specific data objects. 
            return 0; 
 
        // 
        // Process other messages. 
        // 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return 0; 
}


注册窗口类的函数为RegisterClassEx 

ATOM WINAPI RegisterClassEx(
  _In_ const WNDCLASSEX *lpwcx
);
它是以指向结构体WNDCLASSEX的指针为参数的。

二。创建主窗口

创建主窗口用的是CreatWindows函数,返回的是指向窗口的指针。调用ShowWindow就可以显示主窗口了

三。建立消息循环

一般消息循环创建如下:

/* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }
GetMessage不断抓取消息,转换后分派给相应的窗口处理函数(窗口过程Window Procedure)。
每当用户移动鼠标,单击鼠标按钮或敲击键盘时,鼠标或键盘的设备驱动程序将输入转换为消息并将其放置在系统消息队列中。系统从系统消息队列中删除一个消息,检查它们以确定目标窗口,然后将其发布到创建目标窗口的线程的消息队列中。线程的消息队列接收线程创建的窗口的所有鼠标和键盘消息(GetMessage)。该线程从其队列中删除消息,并引导系统将其发送到适当的窗口过程(TranslateMessage DispatchMessage)进行处理。
-------------------------------------------------------我是帅气的分割线--------------------------------------------------------------------------

上面简单说了创建一个主窗口,但我想要的是消息是如何循环的。现在知道的是程序会将消息队列中的消息一条条抓取并分派到相应窗口的处理函数让其对这条消息进行处理,我现在想知道的是消息的产生及分派:消息如何产生,并且还能标记出要发往的窗口,是操作系统完成了这一切吗?还有就是消息如何分派,是抓取之后直接传给控件所在主窗口(假设按下了主窗口的一个按钮),还是说传给了按钮,再由按钮把消息给主窗口?下面进行实验:

创建按钮:

hwndButton=CreateWindow(
                    "BUTTON",  // Predefined class; Unicode assumed
                    "OK",      // Button text
                    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  // Styles
                    10,         // x position
                    10,         // y position
                    100,        // Button width
                    100,        // Button height
                    hwnd,     // Parent window
                    (HMENU)520,       // No menu.
                    (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
                    NULL);      // Pointer not needed.
主窗口的Window Procedures:

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{


    switch (message)                  /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        case WM_COMMAND:
            if (LOWORD(wParam) == 520 && HIWORD(wParam) == BN_CLICKED)
            {
                MessageBox(hwnd, TEXT("22222"), TEXT("11111"), MB_OK);
            }
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}
其中WM_COMMAND即为处理按键按下消息的处理分支。按鍵能正常工作,如下:


下面获取按钮的处理函数并设置新的处理函数:

SetWindowLong函数返回一个指向原始窗口过程的指针; 使用此指针将消息传递到原始过程。子类窗口过程必须使用CallWindowProc函数调用原始窗口过程。

LRESULT CALLBACK ButtonWinProc(HWND, UINT, WPARAM, LPARAM);//新的按键回调函数
WNDPROC OldButtonwinProc;//保存旧回调函数的指针
OldButtonwinProc=(WNDPROC)SetWindowLong(hwndButton,GWL_WNDPROC,(LONG)ButtonWinProc);//设置新的回调函数保存旧的
LRESULT CALLBACK ButtonWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)//新的回调函数
{
    if(message==WM_LBUTTONDOWN) cout<<"down ";
    if(message==WM_LBUTTONUP) cout<<"up ";
    if(message==BM_CLICK) cout<<"click ";

    switch (message)                  /* handle the messages */
    {
        default:
            return CallWindowProc(OldButtonwinProc,hwnd,message,wParam,lParam);//默认调用原有处理函数
    }
}
在WinMain里发送模拟按键点击的消息给按键:

SendMessage(hwndButton,BM_CLICK,520,0);
主窗口回调函数如下:

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        case WM_COMMAND:
            if (LOWORD(wParam) == 520 && HIWORD(wParam) ==  BN_CLICKED)
            {
                cout<<"inmainframe ";
            }
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }
    return 0;
}
結果如下:


而如果按键的回调函数改为如下:

LRESULT CALLBACK ButtonWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if(message==WM_LBUTTONDOWN) cout<<"down ";
    if(message==WM_LBUTTONUP) cout<<"up ";
    if(message==BM_CLICK) cout<<"click ";

    switch (message)                  /* handle the messages */
    {
        default:;
            //return CallWindowProc(OldButtonwinProc,hwnd,message,wParam,lParam);
    }
}
则只会打印click。


这就能够有一定可信度的说明,当按键处理函数接到BM_CLICK后,会给按键发送WM_LBUTTONDOWN和WM_LBUTTONUP,然后按键给主窗口发送了按键点击的消息,也就完成了一次按键点击的模拟。另外类似BM_CLICK中的M表示向控件发送的消息,也就是命令控件做事情类似BN_CLICKED中的N则表明是控件向外部(父窗口)发出的的通知,表示自己的状态。

再将WinMian中消息循环改为如下:

    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */

        if(messages.hwnd==hwndButton&&messages.message==WM_LBUTTONDOWN) cout<<"111";
        if(messages.hwnd==hwnd&&messages.message==WM_COMMAND) cout<<"222";

        DispatchMessage(&messages);
    }
结果为:


这说明按键按下确实是WinMain循环里抓取并直接送往按键的,而按键则是负责按键点击消息的发送,且不会再次进入WinMain消息循换了。

由此可总结,操作系统将各种消息放入应用程序队列,而应用程序抓取消息驱动整个程序一步步响应。一次点击按键,WinMain里面抓取并派送给按键鼠标左键按下与抬起这两个消息,而后按键把自身“点击”这个状态发送给父窗口,从而使父窗口能够处理鼠标点击事件。至于消息的产生,如何标记出要发往的窗口?谁标记的?这些还有待探索。。。。

展开阅读全文

没有更多推荐了,返回首页