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隐藏或顯示滑鼠指針。
【效果圖】

/*------------------------------------------------------------
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重新繪制該無效區。
【效果圖】
/*------------------------------------------------------------
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);
}