《Windows API每日一练》6.4 程序测试

news2024/11/26 9:13:35

前面我们讨论了鼠标的一些基础知识,本节我们将通过一些实例来讲解鼠标消息的不同处理方式。

本节必须掌握的知识点:

        第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.coloradmin.cn/o/1871618.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

独立付费进群系统

无授权源码 有独立分销代理端 域名防封 可对接码支付 易支付 需要用到回调 源码链接

腾讯实时语音编码大突破 电梯、地库里通话也不卡顿

腾讯宣布&#xff0c;腾讯主导的新一代实时语音编码行业标准AVS3P10&#xff0c;即将正式对外发布。由腾讯会议天籁实验室携手腾讯AI Lab研发的Penguins编解码器&#xff08;即AVS3P10行业标准的原型&#xff09;&#xff0c;把经典信号处理和最新的深度学习技术结合在一起&…

pppd 返回错误码 含义

错误码 00&#xff1a; pppd已经断开&#xff0c;或者已经成功建立连接后请求方又中 断了。 01&#xff1a; 发成了一个严重错误&#xff0c;例如系统调用失败或者访问非法内存。 02&#xff1a; 处理给定操作是检测到错误&#xff0c;例如使用两个互斥的操作。 03&#xff1a;…

测试报告-HTMLTestRunner报告优化(中/英文)

引用原始的HTMLTestRunner.py文件生成的测试报告在美观性不是很好&#xff0c;使用在此文件基础上优化后的HTMLTestReportCN.py文件(生成的报告为中文)、HTMLTestReportEN.py文件(生成的报告为英文)。 1 首先新建一个Python项目 例如&#xff1a;testHtmlReport 创建case包&am…

【JVM】Java虚拟机运行时数据分区介绍

JVM 分区&#xff08;运行时数据区域&#xff09; 文章目录 JVM 分区&#xff08;运行时数据区域&#xff09;前言1. 程序计数器2. Java 虚拟机栈3. 本地方法栈4. Java 堆5. 方法区6. 运行时常量池7. 直接内存 前言 之前在说多线程的时候&#xff0c;提到了JVM虚拟机的分区内存…

# 音频处理4_傅里叶变换

1.离散傅里叶变换 对于离散时域信号 x[n]使用离散傅里叶变换&#xff08;Discrete Fourier Transform, DFT&#xff09;进行频域分析。 DFT 将离散信号 x[n] 变换为其频谱表示 X[k]&#xff0c;定义如下&#xff1a; X [ k ] ∑ n 0 N − 1 x [ n ] e − j 2 π k n N X[k]…

Qt 使用代码布局,而不使用UI布局

一、工程的建立&#xff1a; 1、打开Qt Creator&#xff0c;文件&#xff0c;新建文件或项目 2、选择Application&#xff0c;Qt Widgets Application 3、写入名称&#xff0c;选择qmake 4、选择基类Base class&#xff0c;去除Generate form 务必选择QWidget&#xff0c;若…

读AI新生:破解人机共存密码笔记14逆强化学习算法

1. 数学保证 1.1. 如果我们要沿着新的路线重建人工智能&#xff0c;那么它的基础必须是坚实的 1.2. 通过精确的定义和一步步的严格数学证明来提供无可辩驳的保证 1.3. 希望证明一个定理&#xff1a;设计人工智能系统的一种特殊方式可以确保它…

Linux如何安装openjdk1.8

文章目录 Centosyum安装jdk和JRE配置全局环境变量验证ubuntu使用APT(适用于Ubuntu 16.04及以上版本)使用PPA(可选,适用于需要特定版本或旧版Ubuntu)Centos yum安装jdk和JRE yum install java-1.8.0-openjdk-devel.x86_64 安装后的目录 配置全局环境变量 vim /etc/pr…

Python | Leetcode Python题解之第201题数字范围按位与

题目&#xff1a; 题解&#xff1a; class Solution:def rangeBitwiseAnd(self, m: int, n: int) -> int:while m < n:# 抹去最右边的 1n n & (n - 1)return n

spring-boot-starter-json配置对象属性为空不显示

问题背景 在Spring Boot中使用spring-boot-starter-json&#xff08;通常是通过jackson实现的&#xff09;时&#xff0c;如果你希望在序列化对象时&#xff0c;如果某个属性为空&#xff0c;则不显示该属性&#xff0c;你可以使用JsonInclude注解来实现这一点。 pom.xml <…

net Framework OAuth2.0

grant_type client_credentials 客户端凭证password 密码模式 用于资源所有者密码凭据token 隐藏式 、 简化式 简化模式又称为隐式授权码模式&#xff0c;它是授权码模式的一个简化版本authorization_code 授权码 A. 第三方程序向资源拥有者(用户)发送授权请求&#xf…

如何获得更高质量的回答-chatgpt

在与技术助手如ChatGPT进行交互时&#xff0c;提问的方式直接影响到你获得的答案质量。以下是几个关键的提问技巧&#xff0c;可以帮助你在与ChatGPT的互动中获得更有效的回答&#xff1a; 1. 清晰明了的问题 技巧&#xff1a;确保问题清晰明了&#xff0c;避免含糊不清或模糊的…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 特殊加密算法(200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

一键智控,舒适无限:网关在风机盘管智能温控中的应用

风机盘管智能控制系统采用钡铼技术系列无线网关&#xff0c;搭配各类风机设备及传感器组成无线物联中央空调室内机管理系统&#xff0c;实现整个办公楼的空调环境智能化管理。在建筑舒适度的前提下&#xff0c;降低能耗&#xff0c;避免能源浪费。 网关通信接口采用无线传输的…

vscode刷LeetCode算法题环境配置

首先&#xff0c;下载nodejs 在vscode中安装LeetCode插件 安装好进行配置 选择leetcode-cn 填上刚才下载node.exe的路径 完成之后重启一下vscode 重启之后登陆LeetCode 完成之后就可以看到题目了 点击 code now 就可以开始刷题了

Flutter循序渐进==>第一个界面

导言 const MyApp({Key? key}) : super(key: key); const: 这个关键字表示这是一个编译时常量构造函数。当一个类使用 const 构造函数初始化时&#xff0c;它的所有字段都将被设置为编译时常量&#xff0c;并且该对象将在编译时就被创建出来。这对于状态不变&#xff08;immu…

基于单片机和LabVIEW 的远程矿井水位监控系统设计

摘要 &#xff1a; 针 对 现 有 矿 井 水 位 监 控 系 统 存 在 结 构 复 杂 和 不 能 远 程 监 控 的 问 题 &#xff0c; 设计了基于单片机和&#xff2c;&#xff41;&#xff42;&#xff36;&#xff29;&#xff25;&#xff37; 的远程矿井水位监控系统 &#xff0c; 详…

python-(opencv)视频转glf

文章目录 前言python-(opencv)视频转glf1. 下载 opencv-python2. cv2&#xff08;OpenCV&#xff09;和imageio的区别3. demo源码 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说…

uView 2.0:uni-app生态的利剑出鞘,引领UI框架新纪元

引言 随着移动互联网的快速发展&#xff0c;跨平台应用开发成为了开发者们关注的焦点。uni-app&#xff0c;一个基于Vue.js的跨平台应用开发框架&#xff0c;因其高效、易用的特性而广受欢迎。在uni-app的生态系统中&#xff0c;UI框架的选择对于开发者而言至关重要。今天&…