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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/751965.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ECharts 源码代码规范

代码规范 - Apache EChartsApache ECharts&#xff0c;一款基于JavaScript的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数据可视化图表。https://echarts.apache.org/zh/coding-standard.html 源文件 [强制] JavaScr…

基于ARM的通用的Qt移植思路

文章目录 实验环境介绍一、确认Qt版本二、确认交叉编译工具链三、配置Qt3.1、修改qmake.conf3.2、创建autoConfig.sh配置文件 四、编译安装Qt五、移植Qt安装目录六、配置Qt creator6.1、配置qmake6.2、配置GCC编译器6.3、配置G编译器6.4、配置编译器套件6.5、创建应用 七、总结…

论文速览 | IEEE Signal Processing Letters, 2024 | 基于时空上下文学习的事件相机立体深度估计

论文速览 | IEEE Signal Processing Letters, 2024 | 基于时空上下文学习的事件相机立体深度估计 1 引言 在计算机视觉领域,立体深度估计一直是一个备受关注的研究热点。传统的基于帧的方法虽然取得了长足的进步,但在处理运动模糊、低照度和平坦区域等挑战性场景时仍面临诸多…

二进制方式部署k8s集群

前置知识点 1、生产环境部署K8s集群的两种方式 • kubeadm Kubeadm是一个K8s部署工具&#xff0c;提供kubeadm init和kubeadm join&#xff0c;用于快速部署Kubernetes集群。 • 二进制包 从github下载发行版的二进制包&#xff0c;手动部署每个组件&#xff0c;组成Kub…

Linux的fread函数

fread函数 从文件中读入数据到指定的地址中 函数原型 : size_t fread(void*buff , size_t size, size_t count , FILE* stream) /* * description : 对已打开的流进行数据读取 * param ‐ ptr &#xff1a;指向 数据块的指针 * param ‐ size &#xff1a;指定读取的每…

LabVIEW编程控制ABB机械臂

使用LabVIEW编程控制ABB机械臂是一项复杂但十分有价值的任务。通过LabVIEW&#xff0c;可以实现对机械臂的精确控制和监控&#xff0c;提升自动化水平和操作效率。 1. 项目规划和硬件选型 1.1 确定系统需求 运动控制&#xff1a;确定机械臂需要执行的任务&#xff0c;如抓取、…

【总线】AXI4第四课时:握手机制详解

大家好,欢迎来到今天的总线学习时间!如果你对电子设计、特别是FPGA和SoC设计感兴趣&#xff0c;那你绝对不能错过我们今天的主角——AXI4总线。作为ARM公司AMBA总线家族中的佼佼者&#xff0c;AXI4以其高性能和高度可扩展性&#xff0c;成为了现代电子系统中不可或缺的通信桥梁…

乐观锁和悲观锁(MySQL和Java)

乐观锁和悲观锁(MySQL和Java) 在并发编程中&#xff0c;为了确保数据的一致性和完整性&#xff0c;我们通常需要使用锁机制来控制对共享资源的访问。锁主要分为两种&#xff1a;乐观锁和悲观锁。本文将详细介绍这两种锁的概念、工作原理以及它们的优缺点。 悲观锁 悲观锁(Pe…

LabVIEW电涡流检测系统

开发了一种基于LabVIEW的软件与硬件结合的电涡流检测系统&#xff0c;通过同步采样技术和编码器的协同工作&#xff0c;显著提高了大型结构物的损伤检测精度和效率&#xff0c;具有良好的应用前景和实用价值。 项目背景 传统的手持式电涡流检测方法因其速度慢、灵敏度低、准确…

根文件系统

根文件系统 1 介绍1.1 根文件系统介绍1.2 根文件系统目录1.3 常见的根文件系统 2 Buildroot 根文件系统的构建2.1 介绍2.2 依赖文件2.3 交叉编译工具2.4 构建2.4.1 配置 Target options2.4.2 配置 Toolchain2.4.3 配置 System configuration2.4.4 配置 Filesystem images2.4.5 …

微服务知识

传统架构 传统架构会出现的问题 配置烦琐&#xff0c;上线容易出错 加机器要重启 负载均衡单点 管理困难 CAP原则。 CAP原则是指在一个分布式系统中&#xff0c;Consistency&#xff08;一致性&#xff09;、Availability&#xff08;可用性&#xff09;、Partition Toleranc…

产品中心|高效能双处理器Xilinx FPGA 4通道射频收发板卡

1、产品概述 基于Xilinx XC7K325T芯片的4通道射频收发板卡&#xff0c;搭载高能效Cortex-A8内核处理器、1组16bit/2GB DDR3及1组4GB DDR3、 1组2GB Nand Flash、1路USB接口、4路高速ADC、4路高速DAC&#xff0c;支持外触发&#xff0c;外时钟。用于FPGA程序加载板卡工作温度范…

Zynq7000系列FPGA中的DMA控制器简介(一)

DMA控制器&#xff08;DMAC&#xff09;使用64位AXI主接口来执行与系统存储器和PL外围设备之间的DMA数据传输&#xff0c;操作频率同CPU_2x的时钟速率。传输由DMA指令执行引擎控制。DMA引擎运行在一个小指令集上&#xff0c;该指令集提供了一种灵活的指定DMA传输的方法。这种方…

激光雷达数据处理

激光雷达技术以其高精度、高效率的特点&#xff0c;已经成为地表特征获取、地形建模、环境监测等领域的重要工具。掌握激光雷达数据处理技能&#xff0c;不仅可以提升工作效率&#xff0c;还能够有效提高数据的质量和准确性&#xff0c;为决策提供可靠的数据支持。 第一章、激…

STM32_hal库学习(3)-OLED显示

硬件&#xff1a;stm32f103c8t6&#xff0c;四脚oled 四脚OLED用的是iic通讯协议&#xff0c;什么是IIC通讯协议&#xff1f;具体可看这篇文章。 stm32中IIC通讯协议-CSDN博客 既然了解了iic协议&#xff0c;接下来我们就利用stm32cubemx来配置oled。 1.新建一个工程 2.然…

愁煞了,UI设计师是闷葫芦,会干不会说,该咋办呢?

Hi&#xff0c;我是大千UI工场&#xff0c;经常有粉丝反映做好设计&#xff0c;不知道咋给客户和团队小伙伴阐述&#xff0c;传达设计里面&#xff0c;换言之就是设计师有必要提升表达能力&#xff0c;该如何提升。 UI设计师需要提升语言表达能力的原因有以下几点&#xff1a;…

科技赋能·创领未来丨智合同和百胜中国就Contract AI Studio项目达成合作

#智合同 #百胜中国 #AIGC #NLP #LLM #Contract AI Studio 近期&#xff0c;国内AIGC和LLM大语言模型发展可谓是如火如荼&#xff0c;其迅速崛起为社会和产业发展起到了非常重要的作用。人们利用AI技术&#xff08;AIGC、LLM大语言模型、NLP等&#xff09;将其赋能到企业生…

<sa8650>QCX ISP Tuning 使用详解 — Tuning前置条件

<sa8650>QCX ISP Tuning 使用详解 — Tuning前置条件 一 如何安装 Qualcomm Chromatix™ 摄像头校准工具二 如何使用 Qualcomm Chromatix™ tuning工具创建tuning项目2.1 创建工程前提依赖2.2 创建工程2.3 添加场景2.4 编辑区域触发器三 如何创建Tuning 树一 如何安装 Qualco…

ChatGPT国内中文版镜像网站整理(2024/6/25)

一、国内外模型大对比 1.交互式对话测评 用同样一个问题问文言一心3.5模型和ChatGPT3.5模型&#xff0c;以下是得到的两个结果&#xff1a; 文言一心3.5模型的回答 文言一心的这个回答显然非常愚蠢&#xff0c;虽然回答了很长一段话&#xff0c;但是“一斤土豆的重量和土豆的…

详细分析SpringBootTest中的测试类(附Demo)

目录 前言1. 基本知识2. Demo3. 实战3.1 项目测试3.2 功能测试 前言 书写测试类&#xff0c;一般只需要加入Test即可&#xff0c;但是结合Springboot项目来整体测试对应需要怎么下手 详细的Java知识点推荐阅读&#xff1a;java框架 零基础从入门到精通的学习路线 附开源项目面…