父窗口和子窗口的消息分發


《Windows 程序設計》第七章中的 Checker3 和 Checker4 程序

為父窗口和子窗口分別定義了窗口過程 WndProc 和 ChildWndProc

並且父窗口和子窗口使用同一個消息循環

Checker4 的代碼如下

#include <windows.h>

#define DIVISIONS 5

LRESULT CALLBACK WndProc      (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;

int      idFocus = 0 ;
TCHAR szChildClass[] = TEXT ("Checker4_Child") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT ("Checker4") ;
    HWND         hwnd ;
    MSG             msg ;
    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 ;

    if (!RegisterClass (&wndclass))
    {
        MessageBox (NULL, TEXT ("Program requires Windows NT!"), 
            szAppName, MB_ICONERROR) ;
        return 0 ;
    }

    wndclass.lpfnWndProc   = ChildWndProc ;
    wndclass.cbWndExtra    = sizeof (long) ;
    wndclass.hIcon         = NULL ;
    wndclass.lpszClassName = szChildClass ;

    RegisterClass (&wndclass) ;

    hwnd = CreateWindow (szAppName, TEXT ("Checker4 Mouse Hit-Test Demo"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL) ;

    ShowWindow (hwnd, iCmdShow) ;
    UpdateWindow (hwnd) ;

    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage (&msg) ;
        DispatchMessage (&msg) ;
    }
    return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND            hwndChild[DIVISIONS][DIVISIONS] ;
    int                    cxBlock, cyBlock, x, y ;
    
    switch (message)
    {
    case WM_CREATE:
        for (x = 0 ; x < DIVISIONS ; x++)
            for (y = 0 ; y < DIVISIONS ; y++)
                hwndChild[x][y] = CreateWindow (szChildClass, NULL,
                                                WS_CHILDWINDOW | WS_VISIBLE,
                                                0, 0, 0, 0,
                                                hwnd, (HMENU) (y <<8 | x),
                                                (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE),
                                                 NULL) ;
        return 0 ;

    case WM_SIZE:
        cxBlock = LOWORD (lParam) / DIVISIONS ;
        cyBlock = HIWORD (lParam) / DIVISIONS ;

        for (x = 0 ; x < DIVISIONS ; x++)
            for (y = 0 ; y < DIVISIONS ; y++)
                MoveWindow (hwndChild[x][y], x * cxBlock, y * cyBlock,
                            cxBlock, cyBlock, TRUE) ;
        return 0 ;

    case WM_LBUTTONDOWN:
        MessageBeep (1) ;
        return 0 ;

    case WM_KEYDOWN:
        x = idFocus & 0xFF ;
        y = idFocus >> 8 ;

        switch (wParam)
        {
        case VK_UP:    y-- ;                    break ;
        case VK_DOWN:  y++ ;                    break ;
        case VK_LEFT:  x-- ;                    break ;
        case VK_RIGHT: x++ ;                    break ;
        case VK_HOME:  x = y = 0 ;              break ;
        case VK_END:   x = y = DIVISIONS - 1 ;  break ;
        default:       return 0 ;
        }

        x = (x + DIVISIONS) % DIVISIONS ;
        y = (y + DIVISIONS) % DIVISIONS ;

        idFocus = (y << 8) | x ;

        SetFocus (GetDlgItem (hwnd, idFocus)) ;    //???
        return 0 ;
        
    case WM_DESTROY:
        PostQuitMessage (0) ;
        return 0 ;
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;
}

LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC            hdc ;
    PAINTSTRUCT ps ;
    RECT        rect ;

    switch (message)
    {
    case WM_CREATE:
        SetWindowLong (hwnd, 0, 0) ;
        return 0 ;

    case WM_KEYDOWN:
        if (wParam != VK_RETURN && wParam != VK_SPACE)
        {
            SendMessage (GetParent (hwnd), message, wParam, lParam) ;
            return 0 ;
        }

    case WM_LBUTTONDOWN:
        SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ;
        SetFocus (hwnd) ;
        InvalidateRect (hwnd, NULL, FALSE) ;
        return 0 ;

    case WM_SETFOCUS:
        idFocus = GetWindowLong (hwnd, GWL_ID) ;

    case WM_KILLFOCUS:
        InvalidateRect (hwnd, NULL, TRUE) ;
        return 0 ;

    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps) ;

        GetClientRect (hwnd, &rect) ;
        Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;

        if (GetWindowLong (hwnd, 0))
        {
            MoveToEx (hdc, 0,          0, NULL) ;
            LineTo   (hdc, rect.right, rect.bottom) ;
            MoveToEx (hdc, 0,          rect.bottom, NULL) ;
            LineTo   (hdc, rect.right, 0) ;
        }

        if (hwnd == GetFocus ())
        {
            rect.left    += rect.right / 10 ;
            rect.right    -= rect.left ;
            rect.top    += rect.bottom / 10 ;
            rect.bottom -= rect.top ;

            SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
            SelectObject (hdc, CreatePen (PS_DASH, 0, 0)) ;
            Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
            DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;
        }

        EndPaint (hwnd, &ps) ;
        return 0 ;
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;
}

於是對其父窗口和子窗口消息的分發產生了困惑

如當用戶在客戶區移動或點擊鼠標時,相關消息發送給 WndProc 還是 ChildWndProc

當用戶按下鍵盤按鍵時,相關消息發送給 WndProc 還是 ChildWndProc


 

針對這兩種情況我分別進行了下跟蹤調試,過程如下

首先,在兩個窗口過程的消息處理中添加對 WM_MOUSEMOVE 消息的支持

    case WM_MOUSEMOVE:
        return  0 ;

然后對其設置斷點,編譯-鏈接-調試,在客戶區移動鼠標進行測試

因為該程序在客戶區定義了25個子窗口,以 5 * 5 的格子形分布

所以在客戶區寬高不被5整除時,客戶區的右側或下側會留下部分空白

如下圖

首先在網格內移動鼠標,在 ChildWndProc 中的斷點攔截到消息

然后在空白處移動鼠標,在 WndProc 中的斷點攔截到消息

再進行單擊的測試,取消 WM_MOUSEMOVE 的斷點

對 WM_LBUTTONDOWN 消息設置斷點

在網格內單擊鼠標,ChildWndProc 中的斷點攔截到消息

在空白處單擊鼠標,WndProc 中的斷點攔截到消息

由此可以推測,

Windows 會自動查找當前鼠標位置所歸屬的窗口句柄,並對其分發消息

假設其25個子窗口是平鋪在客戶區上,在鼠標經過時

主窗口的客戶區已被子窗口客戶區遮蓋

所以此時窗口消息屬於子窗口,由子窗口接收窗口消息


 

因為對消息分發不甚理解,所以該程序對 WM_KEYDOWN 的處理也是雲里霧里

首先分別對兩個窗口過程的消息處理中對 WM_KEYDOWN 消息設置斷點

調試程序,任意按下一個按鍵

當第一次按鍵時,消息被分發到了 WndProc 窗口過程

之后的按鍵均先經過 ChildWndProc 窗口過程,如非空格和回車再回傳給 WndProc

因為程序中有對輸入焦點的設置,於是判斷鍵盤消息的分發與輸入焦點有關

在 WndProc 中對 WM_SIZE 消息處理中添加了一條語句

        SetFocus (GetDlgItem (hwnd, idFocus)) ;

重新編譯調試,這次所有的鍵盤消息均先經過 ChildWndProc

最小化程序,然后再恢復窗口,鍵盤消息又被分發到了 WndProc

由此可以推測

鍵盤消息會被分發到當前具有輸入焦點的窗口句柄中

或者說鍵盤消息會發生在當前具有輸入焦點的窗口中

然后改進下程序因為失去焦點再恢復后導致的子窗口虛線丟失的問題

在 WndProc 的消息處理中添加 WM_SETFOCUS 消息的處理

    case WM_SETFOCUS:
        SetFocus (GetDlgItem (hwnd, idFocus)) ;
        return 0 ;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM