天天看點

第7章 滑鼠_7.1-7.4 滑鼠基礎知識和滑鼠消息

7.1 滑鼠的基礎知識

功能

GetSysMetrics的

參數

傳回值
判斷是否安裝滑鼠 SM_MOUSEPRESENT

WINNT以上:TRUE己安裝。0未安裝

Windows98:總是TRUE。

滑鼠按鈕個數 SM_CMOUSEBUTTONS

WINNT以上:0為未安裝滑鼠

Windows98:有安裝滑鼠傳回按鈕個數,沒安裝滑鼠傳回2。

滑鼠按鈕是否被切換 SM_SWAPBUTTON

設定滑鼠其他參數(如輕按兩下):用SystemParametrsInfo函數擷取或設定

(2)滑鼠指針:wndClass.hCursor = LoadCursor(NULL,IDC_ARROW)。

7.2 客戶區的滑鼠消息

(1)客戶區滑鼠消息(10個)

事件 消息
滑鼠經過 WM_MOUSEMOVE
左鍵 WM_LBUTTONDOWN、WM_LBUTTONUP、WM_LBUTTONDBLCLK(輕按兩下)
中鍵 WM_MBUTTONDOWN、WM_MBUTTONUP、WM_MBUTTONDBLCLK(第二次按下)
右鍵 WM_RBUTTONDOWN、WM_RBUTTONUP、WM_RBUTTONDBLCLK

  說明:①輕按兩下事件的處理隻有視窗類定義接收時,才起作用:即

差異 沒包含輕按兩下事件 包含輕按兩下事件
wndClass.style CS_HREDRAW| CS_VREDRAW CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS
消息順序

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

        ②MBUTTON隻對三鍵滑鼠起作用。單鍵滑鼠隻處理LBUTTON消息。

③WM_MOUSEMOVE:消息的個數取決于滑鼠硬體和視窗處理的速度。Windows不會為

經過的每個像素位置都産生一個WM_MOUSEMOVE消息。

        ④WM_LBUTTONDOWN和WM_LBUTTONUP并不一定會成對出現。可能隻收到其中的一種

消息。

⑤鍵盤消息發送到目前具有輸入焦點的視窗。滑鼠消息發送給被單擊的窗或滑鼠經

過的視窗,即使該視窗處于非活動或不帶輸入焦點。兩種例外:A、“捕獲滑鼠”

時,滑鼠離開視窗仍繼續接收滑鼠消息;B、模式對話框處于活動狀态時,其他

程式不能接收滑鼠消息。

 (2)滑鼠消息的參數

wParam(按鈕、Shift、Ctrl鍵狀态) lParam(滑鼠位置)

MK_LBUTTON(0x0001):按下左鍵

MK_RBUTTON(0x0002):按下右鍵

MK_SHIFT  (0x0004):按下Shift

MK_CONTROL(0x0008):按下Ctrl

MK_MBUTTON(0x0010):按下中鍵

★或運算結果

x = LOWORD(lParam);

y = HIWORD(lParam);

       eg.當WM_LBUTTONDOWN時,是否同時按下Shift鍵:wParam & MK_SHIFT。

7.2.1 簡單的滑鼠處理示例

【Connect程式】

(1)操作方法:

①第一種——在客戶區按下左鍵,略微移動,再松開左鍵

②第二種——在客戶區按下左鍵,快速移動滑鼠。

(2)己知的問題:在客戶區外釋放左鍵,Connnect不會連接配接這些點,因為沒收到WM_LBUTTONUP消息。

(3)該程式較耗時,繪制時,滑鼠變沙漏形,處理WM_PAINT完後回原來的狀态。用SetCursor來切換滑鼠。ShowCursor隐藏或顯示滑鼠指針。

 【效果圖】

第7章 滑鼠_7.1-7.4 滑鼠基礎知識和滑鼠消息
/*------------------------------------------------------------
CONNECT.C -- Connect-the-Dots Mouse Demo program
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#define MAXPOINTS 1000
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Connect");
    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("This program requires Windows NT!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
        TEXT("Connect-the-Points Mouse Demo"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    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)
{
    HDC         hdc;
    PAINTSTRUCT ps;
    static POINT pt[MAXPOINTS];
    static int iCount = 0;
    switch (message)
    {
    case WM_LBUTTONDOWN:
        iCount = 0;
        InvalidateRect(hwnd, NULL, TRUE); //每次按下左鍵時,先消屏
        return 0;
    case WM_MOUSEMOVE:  //驗證移動時,并不是每個像素都産生WM_MOUSEMOVE消息。
        if (wParam & MK_LBUTTON && iCount<MAXPOINTS) //隻有按下左鍵+滑鼠移動時,才畫點
        {
            pt[iCount].x = LOWORD(lParam);
            pt[iCount++].y = HIWORD(lParam);

            //畫點
            hdc = GetDC(hwnd);
            SetPixel(hdc, LOWORD(lParam), HIWORD(lParam), 0);
            ReleaseDC(hwnd, hdc);
        }
        return 0;
    case WM_LBUTTONUP:  //滑鼠放開時,開始連線
        InvalidateRect(hwnd, NULL, FALSE); //FALSE,不擦除背景,可以保留WM_MOUSEMOVE裡畫下的點。
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        SetCursor(LoadCursor(NULL, IDC_WAIT));
        ShowCursor(TRUE); //滑鼠指針顯示為等待狀态
        for (int i = 0; i < iCount; i++)   //每一點而其他各點的連線
        for (int j = i + 1; j < iCount; j++)
        {
            MoveToEx(hdc, pt[i].x, pt[i].y, NULL);
            LineTo(hdc, pt[j].x, pt[j].y);
        }
        ShowCursor(FALSE);
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}      

7.2.2 處理Shift鍵

處理過程依賴Shift和Ctrl鍵的邏輯處理 單鍵滑鼠模拟雙鍵滑鼠

if (wParam & MK_SHIFT)  //按下Shift

{

    if (wParam & MK_CONTROL)

    {

        [按下Shift + Ctrl鍵];

     }

    else{

        [隻按下Shift鍵];

    }

}else{                  //未按Shift

        [隻按下Ctrl鍵];

    }else{

        [Shift和Ctrl都沒被按下];

}

case WM_LBUTTONDOWN:

//未按Shift時,直接處理左鍵

    if (!(wParam & MK_SHIFT))

        [這裡處理左鍵];

        return 0;

     }   //注意,這裡沒有return。

    //使用者按下了滑鼠左鍵+Shift,執行以下代碼,模拟右鍵。

case WM_RBUTTONDOWN: 

    [這裡處理右鍵];

    return 0;

★注意:雙鍵滑鼠也是可以正常處理的。單鍵滑鼠可以通過按住滑鼠左鍵+Shift,來模拟滑鼠右鍵的功能。

注意:GetKeyState可以通過VK_LBUTTON、VK_RBUTTON、VK_SHIFT、VK_CONTROL等擷取滑鼠目前狀态。但滑鼠或鍵盤未被按下的鍵不能使用GetKeyState。隻有被按下時才會報告其按下狀态。(while(GetKeyState(VK_LBUTTON)>=0))是錯誤的代碼。

7.3 非客戶區的滑鼠消息

(1)非客戶區滑鼠消息(11個)

WM_NCMOUSEMOVE
擊中測試 WM_NCHITTEST
WM_NCLBUTTONDOWN、WM_NCLBUTTONUP、WM_NCLBUTTONDBLCLK(輕按兩下)
WM_NCMBUTTONDOWN、WM_NCMBUTTONUP、WM_NCMBUTTONDBLCLK(第二次按下)
WM_NCRBUTTONDOWN、WM_NCRBUTTONUP、WM_NCRBUTTONDBLCLK

 (2)非客戶區滑鼠消息的參數

wParam(視窗的哪個部位)

(20多個位置,見MSDN)

lParam(滑鼠螢幕坐标)

HTCLINET  客戶區

HTNOWHERE 不在任何視窗

HTRANSPARENT 被另一個視窗覆寫

HTERROR  使函數DefWindowProc産生警示聲

……

Pt.x =LOWORD(lParam);

Pt.y =HIWORD(lParam);

//螢幕坐标與客戶區坐标轉換

ScreenToClient(hwnd,&pt);

ClientToScreen(hwnd,&pt);

7.3.1 擊中測試消息:WM_NCHITTEST

(1)消息優先級高于其他滑鼠消息

(2)wParam參數如上表所示。

(3)DefWindowProc在處理WM_NCHITTEST後,如果傳回HTCLIENT,則會把螢幕坐标轉成客戶區坐标,并産生一個客戶區滑鼠消息。

(4)使滑鼠按鈕操作失效——阻止系統向視窗發送所有客戶區和非客戶區滑鼠消息:

       case WM_NCHITTEST:return (LRESULT)HTNOWHERE;

7.3.2 消息引發的消息

(1)Windows利用WM_NCHITTEST來産生其他所有的滑鼠消息。

(2)舉例:輕按兩下系統圖示來關閉視窗,由WM_NCHITTEST引發的消息

順序 目前消息 wParam 傳回
1、輕按兩下系統圖示,産生WM_NCHITTEST,由DefWindowProc處理後,産生下一條消息。 Not used HTSYSMENU
2、預設處理後,再産生下條消息 WM_NCLBUTTONDBLCLK
3、預設處理後,産生下條消息 WM_SYSCOMMAND SC_CLOSE
4、DefWindowProc調用DestroyWindow處理這條消息,并産生下條消息 WM_CLOSE
5、預設處理或使用者處理,并銷毀。 WM_DESTROY

7.4 程式中的擊中測試

7.4.1 一個簡單的程式

(1)矩形寬度為cxBlock,高度為cyBlock

(2)WM_LBUTTONDOWN判斷滑鼠落在哪個矩形内

(3)如果客戶區寬度和長度不能被5整除,會在左邊和底部出現一個空白區。

(4)點選滑鼠時産生一個無效區(矩形),并發送WM_PAINT重新繪制該無效區。

【效果圖】

第7章 滑鼠_7.1-7.4 滑鼠基礎知識和滑鼠消息
/*------------------------------------------------------------
CHECKER1.C -- Mouse Hit-Test demo Program No.2
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#define  DIVISIONS 5
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Checker1");
    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("This program requires Windows NT!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
        TEXT("Checker1 Mouse Hit-Test Demo"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    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)
{
    HDC         hdc;
    PAINTSTRUCT ps;
    RECT        rect;
    static cxBlock, cyBlock;
    static BOOL fState[DIVISIONS][DIVISIONS];
    int x, y;
    switch (message)
    {
    case WM_CREATE:

        return 0;
    case WM_SIZE:
        cxBlock = LOWORD(lParam) / DIVISIONS;
        cyBlock = HIWORD(lParam) / DIVISIONS;
        return 0;
    case WM_LBUTTONDOWN:
        //單擊的矩形索引
        x = LOWORD(lParam) / cxBlock;
        y = HIWORD(lParam) / cyBlock;
        if (x < DIVISIONS && y < DIVISIONS)
        {
            fState[x][y] ^= 1; //即fState[x][y] =!fState[x][y];
            rect.top = y*cyBlock;
            rect.bottom = (y + 1)*cyBlock;
            rect.left = x*cxBlock;
            rect.right = (x + 1)*cxBlock;
            InvalidateRect(hwnd, &rect, TRUE); //因用透明繪制,是以要清除背景
        }
        else{
            //當寬度和長度不是5個倍數時,會在左或底側出現個空白區,讓其發出錯誤的聲音。
            MessageBeep(0);
        }
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        RECT tmpRect;
        SelectObject(hdc, GetStockObject(NULL_BRUSH));//透明繪制
        for (int x = 0; x < DIVISIONS; x++)
        for (int y = 0; y < DIVISIONS; y++)
        {
            rect.top = y*cyBlock;
            rect.bottom = (y + 1)*cyBlock;
            rect.left = x*cxBlock;
            rect.right = (x + 1)*cxBlock;
            IntersectRect(&tmpRect, &rect, &ps.rcPaint);
            if (IsRectEmpty(&tmpRect)) continue;//判斷目前矩形區是否在無效區内

            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);

            if (fState[x][y])
            {
                MoveToEx(hdc, x*cxBlock, y*cyBlock, NULL);
                LineTo(hdc, (x + 1)*cxBlock, (y + 1)*cyBlock);
                MoveToEx(hdc, x*cxBlock, (y + 1)*cyBlock, NULL);
                LineTo(hdc, (x + 1)*cxBlock, y*cyBlock);
            }
        }

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}      

7.4.2 使用鍵盤模仿滑鼠操作

(1)顯示或隐藏滑鼠

   ShowCursor(TRUE);//增加滑鼠顯示計數,預設0,隻有在>=0時,才顯示。

   ShowCursor(FALSE);//減少滑鼠計數,隻有在<0時才隐藏。

(2)擷取和設定滑鼠位置

   ①Get、Set滑鼠位置

      GetCursorPos(&pt);//螢幕坐标(因為沒有hwnd參數),可與客戶區坐标轉換

      SetCursorPos(&pt);

   ②GetCursorPos與滑鼠消息的參數lParam的差别。

      A、GetCursorPos坐标并轉成客戶區坐标,傳回的滑鼠目前位置

      B、lParam包含的坐标是指産生消息的那一刻的滑鼠位置。

 (3)在CHECKER中增加鍵盤接口

    ①用空格與Enter鍵模拟滑鼠按鈕;方向鍵每按一次切換到另一個矩形。

    ②Home鍵回到左上角的矩形,End鍵滑鼠落到右下角矩形。

 【Checker2程式】

/*------------------------------------------------------------
CHECKER2.C -- Mouse Hit-Test demo Program No.2
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#define  DIVISIONS 5
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Checker2");
    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("This program requires Windows NT!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
        TEXT("Checker2 Mouse Hit-Test Demo"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    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)
{
    HDC         hdc;
    PAINTSTRUCT ps;
    RECT        rect;
    static cxBlock, cyBlock;
    static BOOL fState[DIVISIONS][DIVISIONS];
    int x, y;
    POINT pt;
    switch (message)
    {
    case WM_CREATE:

        return 0;
    case WM_SIZE:
        cxBlock = LOWORD(lParam) / DIVISIONS;
        cyBlock = HIWORD(lParam) / DIVISIONS;
        return 0;
    case WM_SETFOCUS:
        ShowCursor(TRUE); //顯示滑鼠,現在都安裝了滑鼠,可以省略
        return 0;
    case WM_KILLFOCUS:
        ShowCursor(FALSE);
        return 0;
    case WM_KEYDOWN:

        GetCursorPos(&pt); //因wParam為虛拟鍵,lParam為擊鍵的6個字段,沒滑鼠坐标。
        ScreenToClient(hwnd, &pt);

        //目前選中的矩形
        x = max(0, min(DIVISIONS - 1, pt.x / cxBlock));    //限定在[0-4]之間
        y = max(0, min(DIVISIONS - 1, pt.y / cyBlock));
        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;
        case VK_SPACE:
        case VK_RETURN:
            SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x*cxBlock, y*cyBlock));
            return 0;//也可以break,執行下面代碼,将滑鼠設定到目前矩形中央位置。
        }
        x = (x + DIVISIONS) % DIVISIONS; //x原區間為[0,4],移到[5,9]區間,防止x--後出現負數區間。
        y = (y + DIVISIONS) % DIVISIONS;

        //設定滑鼠位置到矩形中央位置
        pt.x = x*cxBlock + cxBlock / 2;
        pt.y = y*cyBlock + cyBlock / 2;
        ClientToScreen(hwnd, &pt); //客戶區坐标轉成螢幕坐标
        SetCursorPos(pt.x, pt.y);
        return 0;
    case WM_LBUTTONDOWN:
        //單擊的矩形索引
        x = LOWORD(lParam) / cxBlock;
        y = HIWORD(lParam) / cyBlock;
        if (x < DIVISIONS && y < DIVISIONS)
        {
            fState[x][y] ^= 1; //即fState[x][y] =!fState[x][y];
            rect.top = y*cyBlock;
            rect.bottom = (y + 1)*cyBlock;
            rect.left = x*cxBlock;
            rect.right = (x + 1)*cxBlock;
            InvalidateRect(hwnd, &rect, TRUE); //因用透明繪制,是以要清除背景
        }
        else{
            //當寬度和長度不是5個倍數時,會在左或底側出現個空白區,讓其發出錯誤的聲音。
            MessageBeep(0);
        }
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        RECT tmpRect;
        SelectObject(hdc, GetStockObject(NULL_BRUSH));//透明繪制
        for (int x = 0; x < DIVISIONS; x++)
        for (int y = 0; y < DIVISIONS; y++)
        {
            rect.top = y*cyBlock;
            rect.bottom = (y + 1)*cyBlock;
            rect.left = x*cxBlock;
            rect.right = (x + 1)*cxBlock;
            IntersectRect(&tmpRect, &rect, &ps.rcPaint);
            if (IsRectEmpty(&tmpRect)) continue;//判斷目前矩形區是否在無效區内

            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);

            if (fState[x][y])
            {
                MoveToEx(hdc, x*cxBlock, y*cyBlock, NULL);
                LineTo(hdc, (x + 1)*cxBlock, (y + 1)*cyBlock);
                MoveToEx(hdc, x*cxBlock, (y + 1)*cyBlock, NULL);
                LineTo(hdc, (x + 1)*cxBlock, y*cyBlock);
            }
        }

        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}      

7.4.3 在擊中測試中使用子視窗

(1)包含25個子視窗,兩個視窗過程:WndProc和ChildWndProc

(2)子視窗類的cbWndExtra設定4個位元組,用來儲存該視窗繪畫狀态,打“X”用1表示。

(3)WndProc中建立子視窗時須調用GetWindowsLong,從注冊的視窗結構中提取hInstance.

(4)hwndChild數組為每個子視窗儲存了視窗句柄,MoveWindows函數中要用到。

(5)CreateWindows主視窗與子視窗參數的比較

主視窗 子視窗
視窗類 “Checker3” “Checker3_Child”
視窗标題 “Checker3…” NULL
視窗風格 WS_OVERLAPPEDWINDOW WS_CHILDWINDOW|WS_VISIBLE
水準位置 CW_USEDEFAULT
垂直位置
寬度
高度
父視窗句柄 hwnd
菜單句柄/子ID (HMENU)(y<<8 | x)(唯一辨別子視窗的數值)
執行個體句柄 hInstance (HINSTANCE)GetWindowLong(hwnd,GWL_INSTANCE)
額外參數

【Checker3程式】

/*------------------------------------------------------------
CHECKER3.C -- Mouse Hit-Test demo Program No.3
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#define  DIVISIONS 5
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主視窗過程
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);  //子視窗的視窗過程
TCHAR szChildClass[] = TEXT("Checker_Child"); //須定義為全局變量,因為WinMain和WndProc中都要用到。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("Checker3");
    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("This program requires Windows NT!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    //子視窗類
    wndclass.lpfnWndProc = ChildWndProc;
    wndclass.cbWndExtra = sizeof(long); //也可以為設定,直接儲存在窗體的GWL_USERDATA中
    wndclass.hIcon = NULL;
    wndclass.lpszClassName = szChildClass;
    RegisterClass(&wndclass); //己經是NT了,不需要再檢查了。
    hwnd = CreateWindow(szAppName,                  // window class name
        TEXT("Checker3 Mouse Hit-Test demo"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    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];
    static int cxBlock, cyBlock;
    switch (message)
    {
    case WM_CREATE:
        for (int x = 0; x < DIVISIONS; x++)
        for (int y = 0; y < DIVISIONS; y++)
        {
            hwndChild[x][y] = CreateWindow(szChildClass, NULL,
                WS_CHILD | WS_VISIBLE, //沒WS_VISIBLE時,建立好視窗,要自己ShowWindow
                0, 0, 10, 10,
                hwnd,
                (HMENU)(y << 8 | x),
                (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), //從主窗體獲得hInstance 
                NULL);
        }
        return 0;
    case WM_SIZE:
        cxBlock = LOWORD(lParam) / DIVISIONS;
        cyBlock = HIWORD(lParam) / DIVISIONS;
        for (int x = 0; x < DIVISIONS; x++)
        for (int y = 0; y < DIVISIONS; y++)
        {
            MoveWindow(hwndChild[x][y], x*cxBlock, y*cyBlock, cxBlock, cyBlock, TRUE);
        }
        return 0;
    case WM_LBUTTONDOWN:
        MessageBeep(0);
        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);//開/關标記,儲存cbWndExtra空間
        //SetWindowLong(hwnd, GWL_USERDATA, 0);//儲存在USERDATA中
        return 0;
    case WM_LBUTTONDOWN:
        SetWindowLong(hwnd, 0, 1 ^ GetWindowLong(hwnd, 0));
        //SetWindowLong(hwnd, GWL_USERDATA, 1 ^ GetWindowLong(hwnd, GWL_USERDATA));
        InvalidateRect(hwnd, NULL, FALSE); //因為矩形預設是填充白色的,會覆寫之前繪畫,是以用False
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);
        Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
        if (GetWindowLong(hwnd, 0)) //if (GetWindowLong(hwnd, GWL_USERDATA))
        {
            MoveToEx(hdc, rect.left, rect.top, NULL);
            LineTo(hdc, rect.right, rect.bottom);
            MoveToEx(hdc, rect.left, rect.bottom, NULL);
            LineTo(hdc, rect.right, rect.top);
        }
        EndPaint(hwnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}      

7.4.4 子視窗和鍵盤

(1)單擊子視窗時,得到輸入焦點的是父視窗,而不是子視窗。(記這點很重要)。滑鼠但鍵盤消息隻給被具有焦點的視窗接收。(父子視窗如何共享鍵盤消息呢?如,當按空格或Enter鍵時,接收消息的是子視窗,要反轉選中狀态。按方向鍵時,接收消息的是父視窗,要将焦點移到另一個子視窗上?方法:父視窗直接将焦點轉移給子視窗,讓子視窗接管鍵盤消息,子窗體先處理Enter鍵和空格鍵等鍵盤消息。但遇到方向鍵時,将消息通過SendMessage返給父視窗,注意這個過程不必将焦點還給父視窗。整個過程,即父視窗将鍵盤消息讓給子視窗去接管,子視窗處理自己興趣的消息,把不處理的消息重新分發給父視窗)

(2)父視窗中操作子視窗

  操作 函數
擷取子視窗ID的方法

idChild = GetWindowLong(hwndChild,GWL_ID);

idChild = GetDlgCtrlID(hwndChild)

擷取子視窗的句柄 hwndChild = GetDlgItem(hwndParent,idChild)
/*------------------------------------------------------------
CHECKER4.C -- Mouse Hit-Test demo Program No.3
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#define  DIVISIONS 5
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主視窗過程
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);  //子視窗的視窗過程
TCHAR szChildClass[] = TEXT("Checker_Child"); //須定義為全局變量,因為WinMain和WndProc中都要用到。
int idFocus; //目前選中的矩形(用子視窗ID來辨別)
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("This program requires Windows NT!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    //子視窗類
    wndclass.lpfnWndProc = ChildWndProc;
    wndclass.cbWndExtra = sizeof(long);
    wndclass.hIcon = NULL;
    wndclass.lpszClassName = szChildClass;
    RegisterClass(&wndclass); //己經是NT了,不需要再檢查了。
    hwnd = CreateWindow(szAppName,                  // window class name
        TEXT("Checker4 Mouse Hit-Test demo"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    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];
    static int cxBlock, cyBlock;
    int x, y;
    switch (message)
    {
    case WM_CREATE:
        for (int x = 0; x < DIVISIONS; x++)
        for (int y = 0; y < DIVISIONS; y++)
        {
            hwndChild[x][y] = CreateWindow(szChildClass, NULL,
                WS_CHILD | WS_VISIBLE, //沒WS_VISIBLE時,建立好視窗,要自己ShowWindow
                0, 0, 10, 10,
                hwnd,
                (HMENU)(y << 8 | x),
                (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), //從主窗體獲得hInstance 
                NULL);
        }
        return 0;
    case WM_SIZE:
        cxBlock = LOWORD(lParam) / DIVISIONS;
        cyBlock = HIWORD(lParam) / DIVISIONS;
        for (int x = 0; x < DIVISIONS; x++)
        for (int y = 0; y < DIVISIONS; y++)
        {
            MoveWindow(hwndChild[x][y], x*cxBlock, y*cyBlock, cxBlock, cyBlock, TRUE);
        }
        return 0;
    case WM_SETFOCUS:
        SetFocus(GetDlgItem(hwnd, idFocus)); //将子窗體設定為視窗窗體,以便接管鍵盤消息。
        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_LBUTTONDOWN:
        MessageBeep(0);
        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);//開/關标記,儲存cbWndExtra空間
        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); //因為矩形預設是填充白色的,會覆寫之前繪畫,是以用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, rect.left, rect.top, rect.right, rect.bottom);
        if (GetWindowLong(hwnd, 0))
        {
            MoveToEx(hdc, rect.left, rect.top, NULL);
            LineTo(hdc, rect.right, rect.bottom);
            MoveToEx(hdc, rect.left, rect.bottom, NULL);
            LineTo(hdc, rect.right, rect.top);
        }

        if (hwnd == GetFocus())
        {
            rect.left += rect.right / 20;
            rect.right -= rect.left;
            rect.top += rect.bottom / 20;
            rect.bottom -= rect.top;
            SelectObject(hdc, GetStockObject(NULL_BRUSH));
            SelectObject(hdc, CreatePen(PS_DOT, 0, 0));
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            DeleteObject(SelectObject(hdc, GetStockObject(BLACK_PEN)));
        }
        EndPaint(hwnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}