《Windows API每日一練》6.4 程序測試

前面我們討論了鼠標的一些基礎知識,本節我們將通過一些實例來講解鼠標消息的不同處理方式。

本節必須掌握的知識點:

??? ????第36練:鼠標擊中測試1

??? ????第37練:鼠標擊中測試2—增加鍵盤接口

??? ????第38練:鼠標擊中測試3—子窗口

??? ????第39練:鼠標擊中測試4—子窗口增加鍵盤接口

??? ????第40練:捕獲鼠標消息1

??? ????第41練:捕獲鼠標消息2

??? ????第42練:獲取系統配置信息No.2—增加鼠標滾輪

6.4.1 第36練:鼠標擊中測試1

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

036? WIN32 API 每日一練

???? 第36個例子CHECKER1.C:鼠標擊中測試1

???? WM_LBUTTONDOWN:單擊鼠標左鍵消息

(c) www.bcdaren.com, 2020

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

#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") ;

??? (略)

???? return msg.wParam ;

}

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

{

???? static BOOL fState[DIVISIONS][DIVISIONS];//默認初始化為0

???? static int cxBlock,cyBlock;

???? HDC hdc;

???? int x,y;

???? PAINTSTRUCT ps;

???? RECT rect;

???? switch (message)

???? {

???? case WM_SIZE:

????????? //矩形方塊的寬和高為客戶區的1/5

????????? 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;//0:1 狀態切換

?????????????? rect.left = x *cxBlock;

?????????????? rect.top = y *cyBlock;

?????????????? rect.right = (x + 1) * cxBlock;

?????????????? rect.bottom = (y + 1)* cyBlock;

?????????????? //重繪矩形區域

?????????????? InvalidateRect(hwnd,&rect,FALSE);

????????? }

????????? else //鼠標點擊客戶區外

?????????????? MessageBeep(0);//蜂鳴

????????? return 0;

???? case WM_PAINT:

????????? hdc = BeginPaint(hwnd,&ps);

????????? //繪制客戶區以DIVSIONS為單位的矩形

????????? for (x = 0; x < DIVISIONS;x++)

????????? {

?????????????? for (y = 0;y < DIVISIONS;y++)

?????????????? {

??????????????????? Rectangle(hdc,x * cxBlock,y * cyBlock,

???????????????????????? (x + 1) * cxBlock,(y + 1) * cyBlock);

??????????????????? //如果鼠標點擊有效區域

??????????????????? 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);

????????? break;

???? }

???? return DefWindowProc (hwnd, message, wParam, lParam) ;

}

/*****************************************************************************

WM_LBUTTONDOWN:表示鼠標左鍵按下事件。在Windows操作系統中,消息是用來傳遞事件和命令的一種機制,

每個消息都有一個唯一的標識符。"WM_LBUTTONDOWN"消息通常在用戶按下鼠標左鍵時觸發,

它告訴應用程序用戶進行了一個鼠標左鍵按下的操作。應用程序可以根據這個消息來執行相應的操作,

例如捕獲鼠標坐標,執行特定的功能或者進行其他處理。

*/

運行結果:

????????????????????

圖6-2 鼠標擊中測試1

?

??????總結

實例CHECKER1.C的窗口過程首先處理WM_SIZE消息的處理,以此獲取當前窗口客戶區寬和高的五分之一。

?????? 接著窗口過程處理WM_LBUTTONDOWN消息,捕獲鼠標左鍵,通過lParam參數獲取鼠標點擊時的x和y坐標,并判斷鼠標點擊坐標位置是否位于5*5的客戶區矩形區域內。如果不在區域內,則蜂鳴提示。如果在區域內,使用fState[x][y] ^= 1;語句保存0:1 狀態切換,并記錄所在矩形區域的rect矩形坐標,重繪窗口客戶區。

?????? 然后處理WM_PAINT消息時在窗口客戶區內繪制5*5矩形,如果fState[x][y]值為1,則在rect矩形內繪制對角線。

6.4.2 第37練:鼠標擊中測試2—增加鍵盤接口

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

037? WIN32 API 每日一練

???? 第37個例子CHECKER2.C:鼠標擊中測試2——增加鍵盤接口

???? 添加鍵盤消息WM_KEYDOWN處理

???? GetCursorPos函數

???? SetCursorPos函數

???? SendMessage函數

???? MAKELONG

???? WM_SETCURSOR消息

??? ?WM_KILLFOCUS消息

???? ShowCursor函數

(c) www.bcdaren.com, 2020

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

#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") ;

? ? (略)

???? return msg.wParam ;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM

wParam,LPARAM lParam)

{

???? static BOOL fState[DIVISIONS][DIVISIONS];

???? static int cxBlock,cyBlock;

???? HDC hdc;

???? int x,y;

???? PAINTSTRUCT ps;

???? POINT point;

???? RECT rect;

???? switch (message)

???? {

???? case WM_SIZE:

????????? cxBlock = LOWORD(lParam) / DIVISIONS;

????????? cyBlock = HIWORD(lParam) / DIVISIONS;

????????? return 0;

???? //鼠標移入窗口的消息:當光標進入或離開某個窗口或控件的客戶區域時,

??? ?//Windows 會生成 WM_SETCURSOR 消息并發送給窗口的消息隊列

???? case WM_SETCURSOR:

??????? //如果bShow為TRUE,則顯示計數增加一。如果bShow為FALSE,則顯示計數減一。

????????? ShowCursor(TRUE);//顯示計數+1,如果安裝了鼠標則忽略

????????? return 0;

//當窗口或控件失去焦點時,Windows 將生成 WM_KILLFOCUS 消息并發送給窗口消息隊列

???? case WM_KILLFOCUS:

????????? ShowCursor(FALSE);//顯示計數-1

????????? return 0;

???? case WM_KEYDOWN:

????????? //因wParam為虛擬鍵碼,lParam為擊鍵的6個字段,沒鼠標坐標。

????????? GetCursorPos(&point);//檢索鼠標光標在屏幕坐標中的位置

????????? ScreenToClient(hwnd,&point);//將屏幕坐標轉換為客戶區坐標

?????????

????????? x = max(0,min(DIVISIONS - 1,point.x / cxBlock));//0~4

????????? y = max(0,min(DIVISIONS - 1,point.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;

?????????????? break;

????????? case VK_RETURN:

????????? case VK_SPACE:

??????????? //模擬發送鼠標消息

??????????? SendMessage(hwnd,WM_LBUTTONDOWN,MK_LBUTTON,

??????????????? MAKELONG(x * cxBlock,y * cyBlock));//宏,置lParam參數高字和低字

?????????????? break;

????????? }

????????? //x原區間為[0,4]+5后,移到[5,9]區間,取模,防止x--后出現負數區間。

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

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

????????? //設置鼠標位置到矩形中央位置

????????? point.x = x * cxBlock + cxBlock / 2;

????????? point.y = y * cyBlock + cyBlock / 2;

????????? //客戶區坐標轉屏幕坐標,并設置鼠標位置

????????? ClientToScreen(hwnd,&point);

????????? SetCursorPos(point.x,point.y);

????????? return 0;

???? case WM_LBUTTONDOWN:

????????? x = LOWORD(lParam) / cxBlock;

????????? y = HIWORD(lParam) / cyBlock;

????????? //如果鼠標擊鍵消息在客戶區范圍內

????????? if (x < DIVISIONS && y <DIVISIONS)

????????? {

?????????????? fState[x][y] ^= 1;//點擊區域,繪制對角線的判斷條件

?????????????? rect.left = x *cxBlock;

?????????????? rect.top = y *cyBlock;

?????????????? rect.right = (x + 1) * cxBlock;

?????????????? rect.bottom = (y + 1)* cyBlock;

?????????????? //重繪矩形區域

?????????????? InvalidateRect(hwnd,&rect,FALSE);

????????? }

????????? else //鼠標點擊客戶區外

?????????????? MessageBeep(0);//蜂鳴

????????? return 0;

???? case WM_PAINT:

????????? hdc = BeginPaint(hwnd,&ps);

????????? //繪制客戶區以DIVSIONS為單位的矩形

????????? for (x = 0; x < DIVISIONS;x++)

????????? {

?????????????? for (y = 0;y < DIVISIONS;y++)

?????????????? {

??????????????????? Rectangle(hdc,x * cxBlock,y * cyBlock,

???????????????????????? (x + 1) * cxBlock,(y + 1) * cyBlock);

??????????????????? //如果鼠標點擊有效區域

??????????????????? 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);

????????? break;

???? }

???? return DefWindowProc (hwnd, message, wParam, lParam) ;

}

/****************************************************************************

GetCursorPos函數:檢索鼠標光標在屏幕坐標中的位置

BOOL GetCursorPos(

? LPPOINT lpPoint?? //指向接收光標的屏幕坐標的POINT結構的指針。

);

*****************************************************************************

SetCursorPos函數:將光標移動到指定的屏幕坐標

BOOL SetCursorPos(

? int X,

? int Y

);

*****************************************************************************

SendMessage函數:將指定的消息發送到一個或多個窗口。

LRESULT SendMessage(

? HWND?? hWnd, //

? UINT?? Msg,? //WM_LBUTTONDOWN

? WPARAM wParam,//MK_LBUTTON

? LPARAM lParam//MAKELONG(x * cxBlock,y * cyBlock)

);

*****************************************************************************

MAKELONG宏:通過串聯指定的值來創建LONG值

DWORD MAKELONG(

?? WORD wLow,//新值的低位字。

?? WORD wHigh//新值的高位字。

);

*****************************************************************************

WM_SETCURSOR消息:當光標進入或離開某個窗口或控件的客戶區域時,

Windows 會生成 WM_SETCURSOR 消息并發送給窗口的消息隊列

#define WM_SETCURSOR???????????? ???????0x0020

參數wParam:包含光標的窗口的句柄。

lParam

lParam的低位字指定光標位置的命中測試結果。請參閱WM_NCHITTEST的返回值以獲取可能的值。

lParam的高位字指定觸發此事件的鼠標窗口消息,例如WM_MOUSEMOVE。當窗口進入菜單模式時,該值為零。

返回值

如果應用程序處理此消息,則應返回TRUE停止進一步處理,或者返回FALSE繼續。

*****************************************************************************

WM_KILLFOCUS消息:通知應用程序失去焦點(focus)。

當窗口或控件失去焦點時,Windows 將生成 WM_KILLFOCUS 消息并發送給窗口的消息隊列。

應用程序可以通過處理這個消息來響應窗口或控件失去焦點的事件。

*****************************************************************************

ShowCursor函數:顯示或隱藏光標。

int ShowCursor(

? BOOL bShow?? //如果bShow為TRUE,則顯示計數增加一。如果bShow為FALSE,則顯示計數減一。

);

返回值

類型:int

返回值指定新的顯示計數器。

*/

運行結果:

圖6-3 鼠標擊中測試2

???? ?總結

實例CHECKER2.C在CHECKER1.C的基礎上增加了兩個消息的處理和一個鍵盤接口。

?????? ?●WM_SETCURSOR消息:WM_SETCURSOR通知應用程序設置光標的外觀。當光標進入或離開某個窗口或控件的客戶區域時,Windows 會生成 WM_SETCURSOR 消息并發送給窗口的消息隊列。窗口過程接到WM_SETCURSOR消息時執行ShowCursor(TRUE);語句,將鼠標顯示計數加一(鼠標顯示計數為0時,隱藏鼠標)。

應用程序可以通過處理這個消息來決定在特定情況下如何設置光標的外觀。

WM_SETCURSOR 消息的處理通常涉及以下幾個步驟:

1.應用程序接收到 WM_SETCURSOR 消息,并確定光標所在的窗口或控件。

2.應用程序確定當前光標所處位置的特定情況,例如是否在客戶區域、非客戶區域或控件的邊界上。

3.應用程序根據特定情況選擇合適的光標形狀,并使用系統函數(如 SetCursor)設置光標的外觀。

通過處理 WM_SETCURSOR 消息,應用程序可以實現自定義的光標行為,例如根據不同的控件或窗口狀態顯示不同的光標形狀。

需要注意的是,WM_SETCURSOR 消息通常與鼠標移動事件相關聯。在處理 WM_SETCURSOR 消息時,應用程序通常還需要處理與鼠標移動相關的消息,如 WM_MOUSEMOVE。

?????? ?●WM_KILLFOCUS消息:通知應用程序失去焦點(focus)。當窗口或控件失去焦點時,Windows 將生成 WM_KILLFOCUS 消息并發送給窗口的消息隊列。應用程序可以通過處理這個消息來響應窗口或控件失去焦點的事件。窗口過程接到WM_KILLFOCUS消息時執行ShowCursor(FALSE);語句,將鼠標顯示計數減一(鼠標顯示計數為0時,隱藏鼠標)。

失去焦點意味著窗口或控件不再是當前接收用戶輸入的對象。這可能發生在用戶將焦點移動到另一個窗口、將焦點轉移到桌面或切換到另一個應用程序時。

在處理 WM_KILLFOCUS 消息時,應用程序可以執行特定的操作,如保存當前輸入狀態、更新界面或執行其他相關的處理邏輯。

需要注意的是,WM_KILLFOCUS 消息是與獲得焦點的消息 WM_SETFOCUS 相對應的。當窗口或控件獲得焦點時,將生成 WM_SETFOCUS 消息。通過處理這兩個消息,應用程序可以跟蹤焦點的變化并作出相應的響應。

?????? ●WM_KEYDOWN消息:實例CHECKER2.C通過處理WM_KEYDOWN消息為實例增加一個鍵盤接口,以此支持用戶通過鍵盤上下左右方向鍵和HOME、END鍵在25個矩形內移動鼠標指針,通過空格和回車鍵模擬點擊鼠標左鍵。

?????? 窗口過程處理WM_KEYDOWN消息時,首先調用GetCursorPos函數獲取鼠標在屏幕上的坐標,然后調用ScreenToClient函數將屏幕坐標轉換為客戶區坐標。

?????? 【注意】通過使用min和max宏,將x和y坐標值鎖定在0~4之間。

?????? 接著通過WM_KEYDOWN消息的wParam參數判斷按下了哪個鍵盤按鍵。

?????? 如果是上下左右方向鍵,則分別將x和y坐標值加一或減一。

?????? 如果是HOME鍵,則x=y=0;

?????? 如果是END鍵,則x = y = DIVISIONS - 1;

?????? 如果是空格或回車鍵,則調用SendMessage函數發送一個鼠標WM_LBUTTONDOW消息,wParam參數為鼠標左鍵虛擬鍵碼MK_LBUTTON,lParam參數為x和y坐標值(使用MAKELONG宏置lParam參數的高字和低字)。

?????? 最后將x和y坐標置于矩形中心位置,并調用ClientToScreen函數將坐標轉換為屏幕坐標,然后調用SetCursorPos函數將其設置為鼠標坐標。

6.4.3 第38練:鼠標擊中測試3—子窗口

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

038? WIN32 API 每日一練

???? 第38個例子CHECKER3.C:鼠標擊中測試3——子窗口

???? 同時注冊主窗口與子窗口

???? 子窗口的預留空間

???? 子窗口ID:wndclass.lpszMenuName

???? GetWindowLong函數

???? MoveWindow函數

???? SetWindowLong函數

(c) www.bcdaren.com, 2020

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

#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");

??? (略)

???? //注冊子窗口類

???? wndclass.cbWndExtra = sizeof(long);//保留額外4個字節空間

???? wndclass.lpszClassName = szChildClass;

???? wndclass.hIcon = NULL;

???? wndclass.lpfnWndProc = ChildWndProc;

???? RegisterClass(&wndclass);

??? (略)

???? 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)

???? {

??????? ?//獲取主窗口進程句柄hInstance的三種方法:

??????? ?//1、hInstance設置為全局變量

??????? ?//2、(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE);

??????? ?//3、WM_CREATE消息的(CREATESTRUCTA)lParam->hInstance

???? case WM_CREATE:

????????? //創建25個子窗口

????????? for (x = 0;x < DIVISIONS;x++)

????????? {

?????????????? for (y = 0; y < DIVISIONS;y++)

?????????????? {

????????? ????? ?hwndChild[x][y] = CreateWindow(szChildClass,NULL, ?

WS_CHILDWINDOW | WS_VISIBLE,//沒WS_VISIBLE需要調用ShowWindow

????????????????? 0,0,0,0,

????????????????? hwnd,(HMENU)((y << 8) | x),//菜單句柄子ID作為子窗口的唯一標識

?????????????? ??(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),//獲得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(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 :

???????? //wndclass.cbWndExtra = sizeof(long);//給子窗口預留4個字節空間保存信息

??????? ?//更改指定窗口的擴展風格、窗口過程地址或用戶數據

// on/off flag 子窗口額外4個字節存儲空間中保存一個0作為標記值

??????? SetWindowLong (hwnd, 0, 0) ;

?????????? return 0 ;

????? case WM_LBUTTONDOWN :

//鼠標點擊后,將將額外存儲空間內的0或1進行交替轉換

?????????? SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ;

?????????? InvalidateRect (hwnd, NULL, FALSE) ; //重繪窗口

?????????? return 0 ;

????? case WM_PAINT :

?????????? hdc = BeginPaint(hwnd, &ps);

?????????? GetClientRect(hwnd, &rect);

?????????? Rectangle(hdc, 0, 0, rect.right, rect.bottom);

?????????? if (GetWindowLong(hwnd, 0))//檢索有關指定窗口的信息,返回值0表示失敗

?????????? {

?????????????? //畫對角線

??????????????? MoveToEx(hdc, 0, 0, NULL);

??????????????? LineTo(hdc, rect.right, rect.bottom);

??????????????? MoveToEx(hdc, 0, rect.bottom, NULL);

??????????????? LineTo(hdc, rect.right, 0);

?????????? }

?????????? EndPaint(hwnd, &ps);

?????????? return 0;

????? }

????? return DefWindowProc (hwnd, message, wParam, lParam) ;

}

/***************************************************************************

MoveWindow函數

更改指定窗口的位置和尺寸。對于頂級窗口,位置和尺寸是相對于屏幕的左上角的。

對于子窗口,它們相對于父窗口客戶區的左上角。

BOOL MoveWindow(

? HWND hWnd,

? int? X,

? int? Y,

? int? nWidth,

? int? nHeight,

? BOOL bRepaint//TRUE重繪,FALSE

);

***************************************************************************

GetWindowLong函數:用于獲取窗口屬性的函數,它可以用來檢索指定窗口的擴展風格、窗口樣式、窗口過程地址或用戶數據。

LONG GetWindowLongA(

? HWND hWnd,// 要獲取屬性的窗口句柄。

? int? nIndex//若指定值大于0,返回窗口內存中指定偏移量的32位值。

);

***************************************************************************

SetWindowLong函數:改指定窗口的屬性。該函數還將指定偏移量的32位(長)值設置到額外的窗口存儲器中。

LONG SetWindowLongA(

? HWND hWnd,?? //窗口句柄

? int? nIndex, //從零開始的要設置值的偏移量。

? LONG dwNewLong//替換值。

);

*/

運行結果:

圖6-4 鼠標擊中測試3

?

總結

?????? 實例38和39是通過在窗口客戶區繪制25個矩形,由主窗口過程負責捕獲鼠標左鍵和鍵盤消息,并繪制矩形對角線。而實例CHECKER3.C則是在窗口客戶區繪制了25個子窗口,由子窗口過程負責捕獲鼠標左鍵消息并繪制子窗口客戶區對角線。

?????? ●首先我們來看主窗口過程:

?????? 主窗口過程處理WM_CREATE消息時,調用CreateWindow繪制25個子窗口(子窗口初始尺寸為0)。子窗口的標識符為菜單項ID。

然后在WM_SIZE消息中調用MoveWindow函數更改子窗口尺寸。

如果主窗口接到WM_LBUTTONDOWN消息時,調用MessageBeep函數蜂鳴示警,表示鼠標點擊位置落在了主窗口內。

?????? ●再看子窗口過程:

?????? 子窗口過程處理WM_CREATE消息時,調用SetWindowLong函數將窗口預留的4個字節存儲空間標記值置0(主程序注冊子窗口類時初始值為空)。

接著在處理WM_LBUTTONDOWN消息時,先調用GetWindowLong函數獲取窗口額外存儲空間的值,并與常量值1進行異或運算在0和1之間較替切換,然后調用SetWindowLong函數將切換后的值置于窗口額外存儲空間。

最后在處理WM_PAINT消息時,依據窗口額外存儲空間的值繪制客戶區對角線。

●SetWindowLong函數:用于修改窗口屬性的函數,它可以用來更改指定窗口的擴展風格、窗口過程地址或用戶數據。

在較新的 Windows 版本中,推薦使用 SetWindowLongPtr 函數來替代 SetWindowLong,特別是在編寫 64 位應用程序時,以支持更大范圍的窗口句柄。SetWindowLongPtr 函數的功能與 SetWindowLong 類似,但接受一個 LONG_PTR 參數,可以處理 32 位和 64 位窗口句柄。以下是 SetWindowLongPtr 的函數原型:

LONG_PTR SetWindowLongPtr(

? HWND???? hWnd,

? int????? nIndex,

? LONG_PTR dwNewLong

);

其中,參數說明如下:

hWnd:要修改屬性的窗口句柄。

nIndex:要修改的屬性索引。可以是以下常量之一:

GWL_EXSTYLE:用于修改窗口的擴展風格。

GWL_STYLE:用于修改窗口的樣式。

GWL_WNDPROC:用于修改窗口過程地址。

GWL_HINSTANCE:用于修改窗口實例句柄。

GWL_USERDATA:用于修改窗口的用戶數據。

dwNewLong:新的屬性值。

SetWindowLongPtr 函數返回被修改屬性的舊值,可以在需要時進行保存或進一步處理。

需要注意的是,修改窗口屬性可能會對窗口的行為和外觀產生重要影響,因此在使用 SetWindowLongPtr 函數時應謹慎,并根據具體需求和文檔準確理解每個屬性的含義和影響。

●GetWindowLong函數:用于獲取窗口屬性的函數,它可以用來檢索指定窗口的擴展風格、窗口樣式、窗口過程地址或用戶數據。

在較新的 Windows 版本中,推薦使用 GetWindowLongPtr 函數來替代 GetWindowLong,特別是在編寫 64 位應用程序時,以支持更大范圍的窗口句柄。GetWindowLongPtr 函數的功能與 GetWindowLong 類似,但接受一個 LONG_PTR 參數,可以處理 32 位和 64 位窗口句柄。以下是 GetWindowLongPtr 的函數原型:

LONG_PTR GetWindowLongPtr(

? HWND hWnd,

? int? nIndex

);

其中,參數說明如下:

hWnd:要獲取屬性的窗口句柄。

nIndex:要獲取的屬性索引。可以是以下常量之一:

GWL_EXSTYLE:用于獲取窗口的擴展風格。

GWL_STYLE:用于獲取窗口的樣式。

GWL_WNDPROC:用于獲取窗口過程地址。

GWL_HINSTANCE:用于獲取窗口實例句柄。

GWL_USERDATA:用于獲取窗口的用戶數據。

GetWindowLongPtr 函數返回對應屬性的值,可以根據需要進一步處理或使用。

需要注意的是,獲取窗口屬性可以用于了解窗口的當前狀態和配置,但在修改窗口屬性之前,應該仔細考慮可能的影響和限制。

6.4.4 第39練:鼠標擊中測試4—子窗口增加鍵盤接口

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

039? WIN32 API 每日一練

???? 第39個例子CHECKER4.C:鼠標擊中測試4——子窗口增加鍵盤接口

???? WM_SETFOCUS消息

???? WM_KILLFOCUS消息

???? SetFocus函數

???? GetDlgItem函數

???? GetParent函數

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#define DIVISIONS 5

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

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

int idFocus = 0 ; //焦點,當前選中的矩形(用子窗口ID來標識)

TCHAR szChildClass[] = TEXT ("Checker4_Child") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

?PSTR szCmdLine, int iCmdShow)

{

???? static TCHAR szAppName[] = TEXT("Checker4");

??? (略)

???? 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 (0) ;

?????????? return 0 ;

????? //? 將焦點設置為子窗口

????? case WM_SETFOCUS: //將接收輸入焦點的子窗口 ID保存在全局變量idFocus中

?????????? SetFocus (GetDlgItem (hwnd, idFocus)) ; //將鍵盤焦點設置到指定的窗口

?????????? return 0 ;

????? // On key-down 消息上,會更改焦點窗口

????? 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) ; // on/off flag 保存在cbWndExtra空間

?????????? return 0 ;

???? case WM_KEYDOWN:

?????????? //? 將大多數按鍵發送到父窗口

?????????? if (wParam != VK_RETURN && wParam != VK_SPACE)

?????????? {

?????????????? //回車空格鍵除外的消息返回給父窗口

?????????????? SendMessage (GetParent (hwnd), message, wParam, lParam) ;

?????????????? return 0 ;

?????????? }

??????? ?? //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) ; //獲取焦點窗口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_DASHDOT, 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) ;

}

/**************************************************************************

WM_SETFOCUS消息:獲得鍵盤焦點后發送到窗口

**************************************************************************

WM_KILLFOCUS消息:在失去鍵盤焦點之前立即發送到窗口

**************************************************************************

SetFocus函數:對指定的窗口設置鍵盤焦點

HWND SetFocus(

? HWND hWnd

);

**************************************************************************

GetDlgItem函數:在指定的對話框中檢索控件的句柄

HWND GetDlgItem(

? HWND hDlg,

? int? nIDDlgItem//要檢索的控件的標識符

);

**************************************************************************

GetParent函數:檢索指定窗口的父級或所有者的句柄

HWND GetParent(

? HWND hWnd

);

*/

?????? 運行結果:

圖6-5 鼠標擊中測試4

?

總結

?????? 實例CHECKER4.C在CHECKER3.C的基礎上增加了鍵盤接口。這里了的關鍵是焦點窗口在主窗口與子窗口之間的切換。

?????? ●當我們處理鼠標消息時,只需要判斷鼠標的坐標位置落在哪個窗口客戶區內,就可以將窗口焦點切換到該窗口客戶區。

?????? ●當我們處理鍵盤消息時,鍵盤消息只能被送入當前具有輸入焦點的窗口,因此,需要我們先轉移輸入焦點至我們想要獲得鍵盤輸入的窗口才可以。

?????? ●主窗口過程:

?????? 主窗口過程在處理M_SETFOCUS消息時,調用GetDlgItem (hwnd, idFocus)獲取之前具有輸入焦點的子窗口句柄,然后再調用SetFocus函數將焦點還給該子窗口。

?????? 主窗口過程處理WM_KEYDOWN消息時,說明主窗口當前獲取了輸入焦點,否則也不可能獲取按鍵消息。在處理WM_KEYDOWN消息時,分別處理上下左右和HOME、END按鍵,重置坐標x和y的值。【注意】重置坐標后,還需要調用SetFocus函數再次將輸入焦點還給之前具有輸入焦點的子窗口。

?????? ●子窗口過程:

?????? 子窗口過程在處理M_KEYDOWN消息時,只負責處理回車和空格鍵,其他按鍵消息調用SendMessage函數將其返還給主窗口。

?????? 如果是回車和空格按鍵消息或者是WM_LBUTTONDOWN鼠標左鍵消息,則重置窗口額外空間存儲的標記值,然后調用SetFocus函數讓當前窗口獲取輸入焦點。(不要返回)接著處理WM_KILLFOCUS消息,在當前子窗口失去焦點時重繪子窗口。

?????? ●實例新增兩個函數

?????? 1.GetDlgItem函數:在指定的對話框中檢索控件的句柄。

GetDlgItem 函數的函數原型:

HWND GetDlgItem(

? HWND hDlg,

? int? nIDDlgItem

);

其中,參數說明如下:

hDlg:對話框的句柄,即包含目標控件的對話框窗口。

nIDDlgItem:控件的標識符(ID),它是在對話框模板中為每個控件分配的唯一標識符。

GetDlgItem 函數會根據指定的對話框句柄和控件標識符,在對話框中查找對應控件的句柄,并返回該句柄。

通過獲取控件的句柄,應用程序可以進一步操作和控制該控件,例如修改其屬性、獲取或設置其文本內容、發送消息給控件等。

?????? 2.GetParent函數:檢索指定窗口的父級或所有者的句柄。

?????? GetParent 函數的函數原型:

HWND GetParent(

? HWND hWnd

);

其中,參數說明如下:

hWnd:要獲取父窗口句柄的窗口句柄。

GetParent 函數會返回指定窗口的父窗口句柄。父窗口通常是容器窗口,包含了子窗口或控件。

通過獲取父窗口句柄,應用程序可以對父窗口及其子窗口進行操作和控制,例如修改父窗口的屬性、發送消息給父窗口或子窗口等。

需要注意的是,父窗口并不一定是直接的父子關系,可能存在多層嵌套的窗口結構。在多層嵌套的情況下,GetParent 函數僅返回指定窗口的直接父窗口句柄。

另外,頂級窗口(沒有父窗口的窗口)的父窗口句柄通常是桌面窗口的句柄。

6.4.5 第40練:捕獲鼠標消息1

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

040? WIN32 API 每日一練

???? 第40個例子BLOKOUT1.C:捕獲鼠標消息1

???? SetROP2函數

???? SetCursor函數

缺陷:無法捕捉客戶區外的鼠標消息

(c) www.bcdaren.com, 2020

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

#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("BlokOut1");

??? (略)

???? return msg.wParam;

}

//繪制矩形圖

void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd)

{

???? HDC hdc;

???? hdc = GetDC(hwnd);

???? SetROP2(hdc, R2_NOT);//顏色取反。可刪除舊的邊框,新邊框顏色為黑色。

???? SelectObject(hdc, GetStockObject(NULL_BRUSH));//空筆刷

???? Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);

???? ReleaseDC(hwnd, hdc);

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM

lParam)

{

????? static BOOL fBlocking, fValidBox ;

????? static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;

????? HDC hdc ;

????? PAINTSTRUCT ps ;

????? switch (message)

????? {

????? case WM_LBUTTONDOWN :

????????? //獲取鼠標位置信息

?????????? ptBeg.x = ptEnd.x = LOWORD (lParam) ;

?????????? ptBeg.y = ptEnd.y = HIWORD (lParam) ;

????????? //繪制矩形(0,0,0,0)

?????????? DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

????????? //捕獲鼠標,設置鼠標形狀

?????????? SetCursor (LoadCursor (NULL, IDC_CROSS)) ;

????????? //標記值

?????????? fBlocking = TRUE ; //阻塞

?????????? return 0 ;

????? case WM_MOUSEMOVE :

?????????? if (fBlocking)

?????????? {

?????????????? //捕獲鼠標,設置鼠標形狀

??????????????? SetCursor(LoadCursor(NULL, IDC_CROSS));

?????????????? //刪除舊邊框,顏色取反

??????????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

?????????????? //獲取鼠標位置信息

??????????????? ptEnd.x = LOWORD(lParam);

??????????????? ptEnd.y = HIWORD(lParam);

?????????????? //繪制新邊框,顏色再次取反

??????????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

?????????? }

?????????? return 0 ;

????? case WM_LBUTTONUP : //釋放鼠標左鍵

?????????? if (fBlocking) //按下鼠標左鍵并繪制矩形

?????????? {

?????????????? //刪除舊矩形

??????????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

?????????????? //獲取鼠標位置信息

??????????????? ptBoxBeg = ptBeg;

??????????????? ptBoxEnd.x = LOWORD(lParam);

??????????????? ptBoxEnd.y = HIWORD(lParam);

?????????????? //捕獲鼠標,設置鼠標位圖

??????????????? SetCursor(LoadCursor(NULL, IDC_ARROW));

?????????????? ??fBlocking = FALSE;//標記沒有按下鼠標左鍵

?????????????? ?fValidBox = TRUE;//標記已釋放鼠標左鍵

?????????????? //重繪窗口客戶區

??????????????? InvalidateRect(hwnd, NULL, TRUE);

?????????? }

?????????? return 0;

????? case WM_CHAR :

??????????? // Escape鍵 & fBlocking,否則將不斷切換顯示與隱藏邊框

?????????? if (fBlocking & (wParam == '\x1B'))

?????????? {

??????????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

??????????????? SetCursor(LoadCursor(NULL, IDC_ARROW));

??????????????? fBlocking = FALSE;

?????????? }

?????????? return 0 ;

????? case WM_PAINT :

?????????? hdc = BeginPaint (hwnd, &ps) ;

?????????? if (fValidBox) //捕獲到WM_LBUTTONUP消息時

?????????? {

?????????????? //填充矩形

??????????????? SelectObject(hdc, GetStockObject(BLACK_BRUSH));

??????????????? Rectangle(hdc, ptBoxBeg.x, ptBoxBeg.y,

???????????????????? ptBoxEnd.x, ptBoxEnd.y);

?????????? }?

?????????? EndPaint (hwnd, &ps) ;

?????????? return 0 ;

????? case WM_DESTROY :

?????????? PostQuitMessage (0) ;

?????????? return 0 ;

????? }

????? return DefWindowProc (hwnd, message, wParam, lParam) ;

}

/****************************************************************************

SetROP2函數:設置當前的前景混合模式。

GDI使用前景混合模式將筆和填充對象的內部與屏幕上已經存在的顏色結合起來。

前景混合模式定義如何將畫筆或筆中的顏色與現有圖像中的顏色進行組合。

int SetROP2(

? HDC hdc,//設備上下文的句柄

? int rop2//混合模式。R2_NOT:顏色取反

);

****************************************************************************

SetCursor函數:設置鼠標位圖。

HCURSOR SetCursor(

? HCURSOR hCursor?? //光標句柄

);

光標的句柄。游標必須已經由CreateCursor函數創建或已由LoadCursor或LoadImage函數加載。

如果此參數為NULL,則將光標從屏幕上移開。

*/

?????? 運行結果:

圖6-6 捕獲鼠標消息1

? ? ?總結

? 1.實例BLOKOUT1.C自定義了一個繪圖函數DrawBoxOutline。先將繪圖二元光柵操作模式設置為顏色取反,然后選入空畫刷填充矩形背景,調用Rectangle繪制矩形。

?????? 2.在窗口過程中,首先處理WM_LBUTTONDOWN消息,由消息參數lParam獲取鼠標位置,接著調用DrawBoxOutline函數繪制矩形,并調用SetCursor將鼠標位圖設置為十字形。將標記變量fBlocking設為TRUE,表示已按下鼠標左鍵并繪制矩形。

?????? 3.接著處理鼠標移動消息WM_MOUSEMOVE。調用SetCursor捕獲鼠標并將鼠標位圖設置為十字。通過lParam參數獲取移動鼠標的當前坐標。

?????? 【注意】這里兩次調用DrawBoxOutline函數,第一次擦掉原來的矩形,第二次繪制新坐標位置的矩形。

?????? 4.接著處理WM_LBUTTONUP消息,當釋放鼠標左鍵時,調用DrawBoxOutline函數刪除舊的矩形。通過lParam參數獲取當前鼠標坐標信息。調用SetCursor函數捕獲鼠標并將鼠標位圖重新設置為箭頭。接著把標記變量fBlocking設為FALSE,表示沒有按下鼠標左鍵,把標記變量fValidBox設置為TRUE,表示已釋放鼠標左鍵。最后調用InvalidateRect重繪窗口客戶區并擦除背景。

?????? 5.處理WM_CHAR消息時,當按下ESC鍵并且按下鼠標左鍵時,調用DrawBoxOutline函數擦除矩形,并將鼠標位圖改為箭頭。標記變量fBlocking設為FALSE。

?????? 6.處理WM_PAINT消息,當捕獲釋放鼠標左鍵時,選入黑色畫刷,填充由Rectangle繪制的矩形。

?????? 【注意】該實例無法捕捉窗口客戶區之外的鼠標,因此,當鼠標移動到窗口客戶區之外時,無法正常繪制矩形。我們將在下一個實例中修正。

6.4.6 第41練:捕獲鼠標消息2

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

041? WIN32 API 每日一練

???? 第41個例子BLOKOUT2.C:捕獲鼠標消息2

???? SetCapture函數

???? ReleaseCapture函數

修正:無法捕捉客戶區外的鼠標消息

(c) www.bcdaren.com, 2020

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

#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("BlokOut2");

??? (略)

???? return msg.wParam;

}

//繪圖函數

void DrawBoxOutline (HWND hwnd, POINT ptBeg, POINT ptEnd)

{

??? (略)

}

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

{

????? static BOOL fBlocking, fValidBox ;

????? static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;

????? HDC hdc ;

????? PAINTSTRUCT ps ;

????? switch (message)

????? {

????? case WM_LBUTTONDOWN :

?????????? ptBeg.x = ptEnd.x = LOWORD(lParam);

?????????? ptBeg.y = ptEnd.y = HIWORD(lParam);

?????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

????????? //新增代碼1

?????????? SetCapture(hwnd);//將鼠標捕獲設置為屬于當前線程的指定窗口

?????????? SetCursor(LoadCursor(NULL, IDC_CROSS));

?????????? fBlocking = TRUE;

?????????? return 0;

????? case WM_MOUSEMOVE :

??????????? (略)

?????????? return 0;

????? case WM_LBUTTONUP :

?????????? if (fBlocking)

?????????? {

??????????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

??????????????? ptBoxBeg = ptBeg;

??????????????? ptBoxEnd.x = LOWORD(lParam);

??????????????? ptBoxEnd.y = HIWORD(lParam);

??????????????? //新增代碼2

//從當前線程的窗口中釋放鼠標捕獲,并恢復正常的鼠標輸入處理

??????????????? ReleaseCapture();

??????????????? SetCursor(LoadCursor(NULL, IDC_ARROW));

??????????????? fBlocking = FALSE;

??????????????? fValidBox = TRUE;

??????????????? InvalidateRect(hwnd, NULL, TRUE);

?????????? }

?? ????????return 0;

????? case WM_CHAR :

?????????? if (fBlocking & (wParam == '\x1B')) // i.e., Escape

?????????? {

??????????????? DrawBoxOutline(hwnd, ptBeg, ptEnd);

?????????????? //新增代碼3

//從當前線程的窗口中釋放鼠標捕獲,并恢復正常的鼠標輸入處理

??????????????? ReleaseCapture();

??????????????? SetCursor(LoadCursor(NULL, IDC_ARROW));

??????????????? fBlocking = FALSE;

?????????? }

?????????? return 0 ;

????? case WM_PAINT :

?????????? hdc = BeginPaint(hwnd, &ps);

??????????? (略)?????

EndPaint(hwnd, &ps);

?????????? return 0;

????? case WM_DESTROY :

?????????? PostQuitMessage(0);

?????????? return 0;

????? }

????? return DefWindowProc (hwnd, message, wParam, lParam) ;

}

/******************************************************************************

SetCapture函數:將鼠標捕獲設置為屬于當前線程的指定窗口。

當鼠標懸停在捕獲窗口上方時,或者當鼠標懸停在捕獲窗口上方,

按下鼠標按鈕時,SetCapture捕獲鼠標輸入。一次只能捕獲一個窗口。

如果鼠標光標位于另一個線程創建的窗口上,則僅當按下鼠標按鈕時,系統才會將鼠標輸入定向到指定的窗口。

HWND SetCapture(

? HWND hWnd??? //當前線程中要捕獲鼠標的窗口的句柄。

);

*******************************************************************************

ReleaseCapture函數:從當前線程的窗口中釋放鼠標捕獲,并恢復正常的鼠標輸入處理。

捕獲光標的窗口將接收所有鼠標輸入,而與光標的位置無關,除非在光標熱點位于另一個線程的窗口中時單擊鼠標按鈕。

BOOL ReleaseCapture();

*/

?????? 運行結果:

????????????????????

圖6-7 捕獲鼠標消息2

?

總結

?????? 實例BLOKOUT2.C新增了三處代碼:

?????? 1.處理WM_LBUTTONDOWN消息時,調用SetCapture函數。

SetCapture(hwnd);//將鼠標捕獲設置為屬于當前線程的指定窗口

?????? 2.處理WM_LBUTTONUP消息時,調用函數ReleaseCapture。

ReleaseCapture();//從當前線程的窗口中釋放鼠標捕獲,并恢復正常的鼠標輸入處理

?????? 3.處理WM_CHAR消息時,調用函數ReleaseCapture。

ReleaseCapture();//從當前線程的窗口中釋放鼠標捕獲,并恢復正常的鼠標輸入處理

?????? 這樣窗口就可以捕捉和釋放客戶區之外的鼠標坐標位置信息,即使鼠標移動到客戶區之外,也可以正常繪制矩形了。

?????? SetCapture函數:用于設置指定窗口捕獲鼠標輸入。以下是 SetCapture 函數的函數原型:

HWND SetCapture(

? HWND hWnd??? //要捕獲鼠標輸入的窗口句柄

);

4.SetCapture 函數用于將鼠標輸入的捕獲設置到指定的窗口。一旦窗口捕獲了鼠標輸入,無論鼠標是否在窗口的客戶區內,窗口都將收到鼠標消息。通常情況下,只有在特定的情況下才需要使用 SetCapture 函數。

以下是一些常見的使用情況:

實現拖拽操作:在開始拖拽操作時,調用 SetCapture 函數將鼠標輸入捕獲到拖拽的窗口,這樣即使鼠標移出窗口的客戶區,窗口也能持續接收鼠標消息,直到松開鼠標按鈕。

自定義鼠標操作:在某些特殊的應用場景中,可能需要自定義鼠標操作,例如繪制自定義的鼠標形狀或處理特定的鼠標事件。通過調用 SetCapture 函數,可以捕獲鼠標輸入并自行處理相應的鼠標消息。

需要注意的是,使用 SetCapture 函數后,必須在適當的時候調用 ReleaseCapture 函數來釋放對鼠標輸入的捕獲。這樣可以確保在不需要捕獲鼠標輸入時,將鼠標輸入的控制權交還給系統。

?????? 5.ReleaseCapture 函數:用于釋放對鼠標輸入的捕獲。以下是 ReleaseCapture 函數的函數原型:

BOOL ReleaseCapture();

ReleaseCapture 函數用于釋放先前使用 SetCapture 函數設置的鼠標輸入捕獲。一旦調用 ReleaseCapture 函數,窗口將不再捕獲鼠標輸入,鼠標輸入將返回給系統。

通常情況下,與 SetCapture 函數配對使用,在不需要繼續捕獲鼠標輸入時調用 ReleaseCapture 函數。

6.4.7 第42練:獲取系統信息—增加鼠標滾輪

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

042? WIN32 API 每日一練

??? ?第42個例子SYSMETS.C:獲取系統配置信息No.2—增加鼠標滾輪

??? ?WM_SETTINGCHANGE消息

??? ?WM_MOUSEWHEEL消息

??? ?SystemParametersInfo函數

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#include "sysmets.h"

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

??? PSTR szCmdLine, int iCmdShow)

{

??? static TCHAR szAppName[] = TEXT("SysMets");

??? (略)

??? return msg.wParam;

}

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

{

??? static int? cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth;

??? HDC???????? hdc;

??? int???????? i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd;

??? PAINTSTRUCT ps;

??? SCROLLINFO? si;

??? TCHAR?????? szBuffer[10];

??? TEXTMETRIC? tm;

??? ULONG ulScrollLines;//鼠標滾動行數

??? static int iDeltaPerLine, iAccumDelta; //每行增量和累積增量

??? switch (message)

??? {

??? case WM_CREATE:

??? (略)

??????? return 0;

//鼠標滾輪消息,在用戶操作鼠標滾輪時發送給窗口,以通知窗口發生了滾輪滾動事件

??????? //wParam

??????? //高序位字指示滑輪旋轉的距離,以滑輪增量的倍數或間隔表示,即120。

??????? //正值表示滾輪向前旋轉,遠離用戶; 負值表示滑輪向后旋轉,朝向用戶。

??????? //低序位字指示各種虛擬鍵是否已關閉。

??????? //lParam

??????? //低序位字指定指針的 x 坐標,相對于屏幕的左上角。

??????? //高序位字指定指針的 y 坐標(相對于屏幕左上角)。

??? case WM_MOUSEWHEEL:

??????? if (iDeltaPerLine == 0) break;

??????? iAccumDelta += (short)HIWORD(wParam); //累積增量=±120

??????? //通過該循環,將iAccumDelta由120變為0

??????? while (iAccumDelta >= iDeltaPerLine)

??????? {

??????????? SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);

??????????? iAccumDelta -= iDeltaPerLine;

??????? }

??????? //通過該循環,將iAccumDelta由-120變為0

??????? while (iAccumDelta <= -iDeltaPerLine) //

??????? {

??????????? SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);

??????????? iAccumDelta += iDeltaPerLine;

??????? }

??????? return 0;

??? case WM_KEYDOWN: //處理鍵盤消息

??????? (略)

??????? return 0;

??? case WM_SIZE:

??????? (略)

??????? return 0;

??? case WM_VSCROLL:

??????? (略)

??????? return 0;

??? case WM_HSCROLL:

??????? (略)

??????? return 0;

??? case WM_PAINT:

??????? hdc = BeginPaint(hwnd, &ps);

??????? (略)

??????? EndPaint(hwnd, &ps);

??????? return 0;

??? case WM_DESTROY:

??????? PostQuitMessage(0);

??????? return 0;

??? }

??? return DefWindowProc(hwnd, message, wParam, lParam);

}

/******************************************************************************

WM_SETTINGCHANGE消息:當SystemParametersInfo函數更改系統范圍的設置或更改策略設置時,發送到所有頂級窗口的消息。

更改系統參數后,應用程序應將WM_SETTINGCHANGE發送給所有頂級窗口。(此消息不能直接發送到窗口。)

WM_SETTINGCHANGE消息發送到所有頂級窗口,請使用SendMessageTimeout函數,并將hwnd參數設置為HWND_BROADCAST。

窗口通過其WindowProc函數接收此消息。

*******************************************************************************

WM_MOUSEWHEEL消息:旋轉鼠標滾輪時發送到焦點窗口。

DefWindowProc會將消息沿父鏈傳播,直到找到處理該消息的窗口為止。

消息不應進行內部轉發.

窗口通過其WindowProc函數接收此消息。

wParam

高序位字指示滑輪旋轉的距離,以滑輪增量的倍數或間隔表示,即120。

正值表示滾輪向前旋轉,遠離用戶;負值表示滑輪向后旋轉,朝向用戶。

低序位字指示各種虛擬鍵是否已關閉。

lParam

低序位字指定指針的 x 坐標,相對于屏幕的左上角。

高序位字指定指針的 y 坐標(相對于屏幕左上角)。

返回值

如果應用程序處理此消息,則它應返回零。

*******************************************************************************

SystemParametersInfo函數:查詢或設置系統級參數。

該函數也可以在設置參數中更新用戶配置文件,這個函數還有很多其它功能,比如獲取桌面工作區的大小。

BOOL SystemParametersInfoA(

? UINT? uiAction,//要檢索或設置的系統范圍參數。

? UINT? uiParam,//參數的用法和格式取決于要查詢或設置的系統參數。

? PVOID pvParam,//參數的用法和格式取決于要查詢或設置的系統參數。

? UINT? fWinIni//如果正在設置系統參數,則指定是否要更新用戶配置文件,

//如果要更新,則是否將WM_SETTINGCHANGE消息廣播到所有頂級窗口以將更改通知他們。????????? ?? //如果您不想更新用戶配置文件或廣播WM_SETTINGCHANGE消息,

??????????? ?? //則此參數可以為零,也可以為以下值中的一個或多個。

);

*/

?????? 運行結果:

圖6-8 獲取系統信息2

總結

?????? 實例SYSMETS.C:獲取系統配置信息No.2在第三章獲取系統配置信息No.1版本的基礎上增加了對鼠標滾輪消息的處理

?????? 1.WM_SETTINGCHANGE消息用于通知應用程序系統設置的更改,是由系統發送給頂級窗口(Top-level Window)以通知它們系統設置的更改,例如顯示設置、輸入設置、語言設置等。

當系統設置發生更改時,Windows 將發送 WM_SETTINGCHANGE 消息給所有頂級窗口,以便它們可以更新并適應新的設置。應用程序可以通過處理這個消息來獲取有關系統設置更改的通知,并相應地更新其用戶界面或執行其他操作。

以下是 WM_SETTINGCHANGE 消息的消息參數:

WM_SETTINGCHANGE

??? WPARAM wParam;

LPARAM lParam;

其中,wParam 和 lParam 參數的含義取決于具體的設置更改。通常情況下,lParam 參數是一個指向以 NULL 結尾的字符串的指針,該字符串包含有關所做更改的信息。應用程序可以通過檢查 lParam 參數來確定具體的設置更改類型。

2.本實例在處理WM_SETTINGCHANGE消息時,先調用SystemParametersInfo函數獲取鼠標滾輪每次滾動的行數,預設值一般為3。如果每次滾動的行數ulScrollLines為0,則iDeltaPerLine = 0;每次滾動一行需要0個止動器值。如果每次滾動的行數ulScrollLines為3,則iDeltaPerLine = 40;每次滾動一行需要40個止動器值。

WM_MOUSEWHEEL 是 Windows 消息中的一個消息代碼,用于通知應用程序鼠標滾輪的滾動事件。

3.WM_MOUSEWHEEL 消息在用戶操作鼠標滾輪時發送給窗口,以通知窗口發生了滾輪滾動事件。這個消息提供了有關滾輪滾動的信息,例如滾動的距離和滾動的方向。

以下是 WM_MOUSEWHEEL 消息的消息參數:

WM_MOUSEWHEEL

??? WPARAM wParam;

??? LPARAM lParam;

其中,wParam 參數包含了關于滾輪滾動的信息,主要包括以下內容:

高位字(16位):表示鼠標滾輪滾動的距離,單位為 WHEEL_DELTA(通常為 120)。正值表示向前滾動,負值表示向后滾動。

低位字(16位):保留,未使用。

lParam 參數包含了關于鼠標滾輪滾動事件發生時的鼠標位置信息。

4.本實例處理WM_MOUSEWHEEL 消息:

當累加增量iAccumDelta等于+120時,調用SendMessage函數向垂直滾動條發送WM_VSCROLL消息,向上滾動一行,直至累積增量為0。

當累加增量iAccumDelta等于-120時,調用SendMessage函數向垂直滾動條發送WM_VSCROLL消息,向下滾動一行,直至累積增量為0。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/36753.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/36753.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/36753.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

3.imput 字符串常用方法 字符串倒序,切片

1.input input()函數接收一個標準輸入數據返回string類型 2.字符串常用方法 upper()將字符串中的小寫字母變為大寫 lower()大寫變小寫 len()獲取長度 count(子字符串)統計某個字符出現的次數 index(子字符串)可以返回子字符串出現的位置, rindex從右邊找 find(子字符串)可以返回…

vite-ts-cesium項目集成mars3d修改相關的包和配置參考

如果vite技術棧下使用原生cesium&#xff0c;請參考下面文件的包和配置修改&#xff0c;想用原生創建的viewer結合我們mars3d的功能的話。 1. package.json文件 "dependencies": {"cesium": "^1.103.0","mars3d": "^3.7.18&quo…

重啟ubuntu后命令行出現(initramfs),無圖形界面問題。

由于ubuntu內部軟件問題&#xff0c;需要重啟ubuntu&#xff0c;導致重啟后圖像界面消失&#xff0c;出現如下的命令行&#xff1a; (initramfs): 這里表示進入圖形界面初始化時&#xff0c;某個分區的文件損壞&#xff0c;損壞文件名稱會在上方顯示。 解決方法&#xff1a;…

深度學習 - Transformer 組成詳解

整體結構 1. 嵌入層&#xff08;Embedding Layer&#xff09; 生活中的例子&#xff1a;字典查找 想象你在讀一本書&#xff0c;你不認識某個單詞&#xff0c;于是你查閱字典。字典為每個單詞提供了一個解釋&#xff0c;幫助你理解這個單詞的意思。嵌入層就像這個字典&#xf…

Micrometer+ZipKin分布式鏈路追蹤

目錄 背景MicrometerMicrometer與ZipKin之間的關系專業術語分布式鏈路追蹤原理 ZipKin安裝下載 MicrometerZipKin 案例演示相關文獻 背景 一個系統頁面上的按鈕點擊到結果反饋&#xff0c;在微服務框架里&#xff0c;是由N個服務組成返回結果&#xff0c;中間可能經過a->b-…

【Electron】Electron入門實現

Electron 學習筆記 Electron 是一個開源框架&#xff0c;允許開發者使用網頁技術&#xff08;HTML、CSS 和 JavaScript&#xff09;來構建跨平臺的桌面應用程序。它由 GitHub 開發并維護&#xff0c;最初是為了支持開發 Atom 編輯器。Electron 結合了 Chromium&#xff08;用于…

密碼學及其應用 —— 對稱加密技術

1. 對稱加密、流加密和塊加密 1.1 對稱加密 對稱加密&#xff08;也稱為密鑰加密&#xff09;是一種加密方式&#xff0c;其中加密和解密使用相同的密鑰。這種加密方法基于二進制層面的操作&#xff0c;如XOR&#xff08;異或&#xff09;、SHIFT&#xff08;位移&#xff09;…

Redis Stream Redisson Stream

目錄 一、Redis Stream1.1 場景1&#xff1a;多個客戶端可以同時接收到消息1.1.1 XADD - 向stream添加Entry&#xff08;發消息 &#xff09;1.1.2 XREAD - 從stream中讀取Entry&#xff08;收消息&#xff09;1.1.3 XRANGE - 從stream指定區間讀取Entry&#xff08;收消息&…

【DevExpress】WPF DevExpressMVVM 24.1版本開發指南

DevExpressMVVM WPF 環境安裝 前言重要Bug&#xff08;必看&#xff09;環境安裝控件目錄Theme 主題LoginWindow 登陸窗口INavigationService 導航服務DockLayout Dock類型的畫面布局TreeView 樹狀列表注意引用類型的時候ImageSource是PresentationCore程序集的博主找了好久&am…

[筆記] keytool 導入服務器證書和證書私鑰

背景 我當前手頭已有一個服務器證書和對應的私鑰&#xff0c;現在需要轉換為 Java KeyStore 格式使用&#xff0c;找了一大圈才發現 keytool 無法直接導入服務器證書和私鑰&#xff0c;當然證書可以直接導入&#xff0c;但是私鑰是無法直接導入。找了一大圈發現可以先將服務器…

LeetCode題解:1669. 合并兩個鏈表,JavaScript,詳細注釋

原題鏈接&#xff1a; https://leetcode.cn/problems/merge-in-between-linked-lists/ 解題思路&#xff1a; 注意該題傳入的a和b是鏈表的索引&#xff0c;而不是節點的值先遍歷list1&#xff0c;找到a-1和b1節點將a-1的next指向list2的頭節點在將list2的尾節點的next指向b1節…

Navicat 外網連接 mysql (1、通過SSH方式內網訪問 2、對外開放3306端口)

1、通過SSH方式內網訪問 直接常規方式使用IP、賬號密碼連接&#xff0c;失敗 SSH方式&#xff1a; 常規 選項卡中&#xff1a;localhost錄入數據庫賬號密碼 SSH 選項卡中&#xff1a;勾選使用SSH&#xff0c;輸入服務器IP、賬號、密碼 如果出現該錯誤&#xff0c;可能是服務器…

計算機網絡重點名詞解釋整理

名詞解釋 GPTVersion 一、網絡協議 網絡協議 數據交換的規則 組成&#xff1a;語義、語法、定時 二、DHCP DHCP 動態規劃主機配置協議 作用&#xff1a;讓計算機自動獲取IP地址 特點&#xff1a;即插即用&#xff0c;不需要手動設置 三、信號的基本調制方法以及定義 …

Windows下activemq開啟jmx

1.activemq版本信息 activemq&#xff1a;apache-activemq-5.18.4 2.Windows下activemq開啟jmx 1.進入activemq conf目錄&#xff0c;備份activemq.xml文件 2.編輯activemq.xml文件&#xff0c;在broker節點增加useJmx"true" <broker xmlns"http://active…

C++循環隊列 自定義queue

原理解析 看main部分的注釋&#xff0c;對照著函數&#xff0c;應該能看懂。 #include <iostream> class Queue {public:static constexpr int MAX_SIZE 5;int items[MAX_SIZE];int front, rear;Queue() : front(-1), rear(-1) {}void enqueue(int value) {if ((rear …

理解 Vue.js 中的 immediate: true

理解 Vue.js 中的 immediate: true 在使用 Vue.js 時&#xff0c;監聽器 (watchers) 是一種非常重要的工具&#xff0c;它允許我們觀察和響應數據的變化。在定義監聽器時&#xff0c;我們通常會在組件的 watch 選項中添加相關配置。immediate: true 是其中的一個配置選項。本文…

無線通訊幾種常規天線類別簡介

天線對于無線模塊來說至關重要&#xff0c;合適的天線可以優化通信網絡&#xff0c;增加其通信的范圍和可靠性。天線的選型對最后的模塊通信影響很大&#xff0c;不合適的天線會導致通信質量下降。針對不同的市場應用&#xff0c;天線的材質、安置方式、性能也大不一樣。下面簡…

近期計算機領域的熱點技術

隨著科技的飛速發展&#xff0c;計算機領域的新技術、新趨勢層出不窮。本文將探討近期計算機領域的幾個熱點技術趨勢&#xff0c;并對它們進行簡要的分析和展望。 一、人工智能與機器學習 人工智能&#xff08;AI&#xff09;和機器學習&#xff08;ML&#xff09;是近年來計算…

基于Vue 3.x與TypeScript的PPTIST本地部署與無公網IP遠程演示文稿

文章目錄 前言1. 本地安裝PPTist2. PPTist 使用介紹3. 安裝Cpolar內網穿透4. 配置公網地址5. 配置固定公網地址 前言 本文主要介紹如何在Windows系統環境本地部署開源在線演示文稿應用PPTist&#xff0c;并結合cpolar內網穿透工具實現隨時隨地遠程訪問與使用該項目。 PPTist …

[gpt胡說八道篇] 使用Docker快速啟動Doris

Docker 是一種輕量級的虛擬化技術&#xff0c;我們可以利用 Docker 快速的在本地啟動一個 Doris 的實例&#xff0c;方便進行開發和測試。下面我們來看一下如何操作。 1. 拉取 Docker 鏡像 首先&#xff0c;我們需要從 Docker Hub 上拉取 Doris 的鏡像。打開終端&#xff0c;輸…