当前位置: 首页 > news >正文

《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。

相关文章:

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

前面我们讨论了鼠标的一些基础知识&#xff0c;本节我们将通过一些实例来讲解鼠标消息的不同处理方式。 本节必须掌握的知识点&#xff1a; 第36练&#xff1a;鼠标击中测试1 第37练&#xff1a;鼠标击中测试2—增加键盘接口 第38练&#xff1a;鼠标击中测试3—子窗口 第39练&…...

[C#]基于opencvsharp实现15关键点人体姿态估计

数据集 正确选择数据集以对结果产生适当影响也是非常必要的。在此姿势检测中&#xff0c;模型在两个不同的数据集即COCO关键点数据集和MPII人类姿势数据集上进行了预训练。 1. COCO&#xff1a;COCO关键点数据集是一个多人2D姿势估计数据集&#xff0c;其中包含从Flickr收集的…...

lambda-map.merge

map.merge 结论: 1.当前传入的 key ,value biFunction 2.如果之前map不存在则直接put(当前key,当前value) 3.如果之前map已经有了,老value与 当前value 进入function处理后再 put(当前key,处理后的value)...

pppd 返回错误码 含义

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

XML 技术

XML 技术 XML(可扩展标记语言)是一种用于存储和传输数据的标记语言。它由万维网联盟(W3C)开发,并在1998年成为正式标准。XML的设计目标是既易于人类阅读,也易于机器解析。它是一种自描述的语言,允许用户定义自己的标签和文档结构。XML被广泛应用于各种领域,包括网络服…...

基于RabbitMQ的异步消息传递:发送与消费

引言 RabbitMQ是一个流行的开源消息代理&#xff0c;用于在分布式系统中实现异步消息传递。它基于Erlang语言编写&#xff0c;具有高可用性和可伸缩性。在本文中&#xff0c;我们将探讨如何在Python中使用RabbitMQ进行消息发送和消费。 安装RabbitMQ 在 Ubuntu 上安装 Rabbi…...

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

题目&#xff1a; 题解&#xff1a; func rangeBitwiseAnd(m int, n int) int {for m < n {n & (n - 1)}return n }...

竞争性谈判中,主要谈判什么内容?(电子化招采系统)

问&#xff1a;竞争性谈判中&#xff0c;主要谈判什么内容&#xff1f; 答&#xff1a;竞争性谈判是指采购人或代理机构通过与多家供应商&#xff08;不少于3家&#xff09;进行谈判&#xff0c;最后从中确定中标供应商的一种采购方式。在谈判的过程中&#xff0c;谈判的主要内…...

youlai-boot项目的学习(4) 前后端本地部署

环境 1、macOS, brew, IntelliJ IDEA, WebStrom 2、后端&#xff1a;https://gitee.com/youlaiorg/youlai-boot.git , master, 9a753a2e94985ed4cbbf214156ca035082e02723 3、前端&#xff1a;https://gitee.com/youlaiorg/vue3-element-admin.git, master, 66b913ef01dc880ad…...

Redis 5 种基础数据结构?

Redis 5 种基本数据结构(String、List、Hash、Set、Sorted Set)在面试中经常会被问到&#xff0c;这篇文章我们一起来回顾温习一下。 还有几种比较特殊的数据结构(HyperLogLogs、Bitmap 、Geospatial、Stream)也非常重要&#xff0c;我们后面下次再聊&#xff01; 下面是正文。…...

搜维尔科技:SenseGlove Nova2国内首款支持手掌心力回馈手套开售

《SenseGlove Nova 2》现正全球发行中! 搜维尔科技独家代理最新上市的 SenseGlove Nova 2 是世上首款&#xff0c;也是目前市面上唯一一款提供手掌力回馈的无缐VR力回馈手套&#xff0c;它结合了三种最先进的反馈技术&#xff0c;包括主动反馈、强力反馈及震动反馈&#xff0c…...

Java中的函数式编程入门

Java中的函数式编程入门 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我来为大家介绍一下Java中的函数式编程。随着Java 8的发布&#xff0c;函数式编程成…...

idea 自动生成序列化数字

目标&#xff1a;当类继承Serializable后自动生成序列化Uid 网上查了很多说勾选class without ‘serialVersionUID’ 但是我勾选没用 最后发现&#xff0c;我勾选的是Serialization issues里面的配置&#xff0c;要勾选的是JVM languages下的 如下图所示&#xff0c;记录一下…...

Java数据结构算法(最长递增序列二分查找)

前言: 最长递增子序列&#xff08;Longest Increasing Subsequence, LIS&#xff09;是指在一个给定的序列中&#xff0c;找到一个最长的子序列&#xff0c;使得这个子序列中的元素是单调递增的。子序列不要求在原序列中连续。 实现原理 使用一个 tails 列表&#xff0c;其中…...

编译VTK静态库

编译VTK静态库遇到问题 vtkCommonCore-9.3d.lib(vtkSMPToolsAPI.obj) : error LNK2019: unresolved external symbol "public: bool __cdecl vtk::detail::smp::vtkSMPToolsImpl<1>::IsParallelScope(void)" (?IsParallelScope?$vtkSMPToolsImpl$00smpdetai…...

Python中的@property装饰器:深入理解与应用

Python中的property装饰器&#xff1a;深入理解与应用 在Python中&#xff0c;property装饰器是一个强大的工具&#xff0c;它允许我们将方法作为属性来访问&#xff0c;使得代码更加简洁、清晰&#xff0c;并提供了更好的封装性。本文将深入探讨property装饰器的工作原理、应…...

springCloudalibabaAI孵化(一)

目录 1、what 1、简介 2、核心概念 3、高级特性 Prompt 和 AiResponse 4、功能 2、How 1、前言 2、在项目 pom.xml 中加入 2023.0.1.0 版本 Spring Cloud Alibaba 依赖&#xff1a; 3、在 配置文件中加入以下配置&#xff1a;application.yml 4、编写聊天服务实现类&a…...

【封装】Unity编辑器模式GUID加载资源

介绍 在编辑器模式下通过GUID获取工程目录下的指定资源的接口工具封装 工具原理 借助AssetDatabaseAPI FindAssets : 获取 GUID GUIDToAssetPath : 通过GUID获取路径LoadAssetAtPath<T>: 通过路径加载资源 代码&#xff1a; public static class GetAssetUtil {pub…...

安装 Docker 环境(通过云平台创建一个实例实现)

目录 1. 删除原有 yum 2. 手动配置 yum 源 3. 删除防火墙规则 4. 保存防火墙配置 5. 修改系统内核。打开内核转发功能。 6. 安装 Docker 7. 设置本地镜像仓库 8.重启服务 1. 删除原有 yum rm -rfv /etc/yum.repos.d/* 2. 手动配置 yum 源 使用 centos7-1511.iso 和 Xi…...

MySQL之可扩展性(六)

可扩展性 向外扩展 12.重新均衡分片数据 如有必要&#xff0c;可以通过在分片间移动数据来达到负载均衡。举个例子&#xff0c;许多读者可能听一些大型图片分享网站或流行社区网站的开发者提到过用于分片间移动用户数据的工具。在分片间移动数据的好处很明显。例如&#xff…...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

pam_env.so模块配置解析

在PAM&#xff08;Pluggable Authentication Modules&#xff09;配置中&#xff0c; /etc/pam.d/su 文件相关配置含义如下&#xff1a; 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块&#xff0c;负责验证用户身份&am…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...