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

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

本节必须掌握的知识点:

        第36练:鼠标击中测试1

        第37练:鼠标击中测试2—增加键盘接口

        第38练:鼠标击中测试3—子窗口

        第39练:鼠标击中测试4—子窗口增加键盘接口

        第40练:捕获鼠标消息1

        第41练:捕获鼠标消息2

        第42练:获取系统配置信息No.2—增加鼠标滚轮

6.4.1 第36练:鼠标击中测试1

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

036  WIN32 API 每日一练

     第36个例子CHECKER1.C:鼠标击中测试1

     WM_LBUTTONDOWN:单击鼠标左键消息

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#define DIVISIONS 5

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine,

int iCmdShow)

{

     static TCHAR szAppName[] = TEXT ("Checker1") ;

    (略)

     return msg.wParam ;

}

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

{

     static BOOL fState[DIVISIONS][DIVISIONS];//默认初始化为0

     static int cxBlock,cyBlock;

     HDC hdc;

     int x,y;

     PAINTSTRUCT ps;

     RECT rect;

     switch (message)

     {

     case WM_SIZE:

          //矩形方块的宽和高为客户区的1/5

          cxBlock = LOWORD(lParam) / DIVISIONS;

          cyBlock = HIWORD(lParam) / DIVISIONS;

          return 0;

     case WM_LBUTTONDOWN:

          //单击的矩形索引

          x = LOWORD(lParam) / cxBlock;

          y = HIWORD(lParam) / cyBlock;

          //如果鼠标击键消息在客户区范围内

          if (x < DIVISIONS && y <DIVISIONS)

          {

               //点击区域非零,作为绘制对角线的判断条件;

               fState[x][y] ^= 1;//0:1 状态切换

               rect.left = x *cxBlock;

               rect.top = y *cyBlock;

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

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

               //重绘矩形区域

               InvalidateRect(hwnd,&rect,FALSE);

          }

          else //鼠标点击客户区外

               MessageBeep(0);//蜂鸣

          return 0;

     case WM_PAINT:

          hdc = BeginPaint(hwnd,&ps);

          //绘制客户区以DIVSIONS为单位的矩形

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

          {

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

               {

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

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

                    //如果鼠标点击有效区域

                    if (fState[x][y])//非零表示有效区域

                    {

                        //矩形区域内绘制对角线

                         MoveToEx(hdc,x * cxBlock,y * cyBlock,NULL);

                         LineTo(hdc,(x + 1) *cxBlock,(y + 1) * cyBlock);

                         MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);

                         LineTo(hdc, (x + 1) *cxBlock, y * cyBlock);

                    }

               }

          }

          EndPaint(hwnd,&ps);

          return 0;

     case WM_DESTROY:

          PostQuitMessage(0);

          break;

     }

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

}

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

WM_LBUTTONDOWN:表示鼠标左键按下事件。在Windows操作系统中,消息是用来传递事件和命令的一种机制,

每个消息都有一个唯一的标识符。"WM_LBUTTONDOWN"消息通常在用户按下鼠标左键时触发,

它告诉应用程序用户进行了一个鼠标左键按下的操作。应用程序可以根据这个消息来执行相应的操作,

例如捕获鼠标坐标,执行特定的功能或者进行其他处理。

*/

运行结果:

                    

图6-2 鼠标击中测试1

 

      总结

实例CHECKER1.C的窗口过程首先处理WM_SIZE消息的处理,以此获取当前窗口客户区宽和高的五分之一。

       接着窗口过程处理WM_LBUTTONDOWN消息,捕获鼠标左键,通过lParam参数获取鼠标点击时的x和y坐标,并判断鼠标点击坐标位置是否位于5*5的客户区矩形区域内。如果不在区域内,则蜂鸣提示。如果在区域内,使用fState[x][y] ^= 1;语句保存0:1 状态切换,并记录所在矩形区域的rect矩形坐标,重绘窗口客户区。

       然后处理WM_PAINT消息时在窗口客户区内绘制5*5矩形,如果fState[x][y]值为1,则在rect矩形内绘制对角线。

6.4.2 第37练:鼠标击中测试2—增加键盘接口

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

037  WIN32 API 每日一练

     第37个例子CHECKER2.C:鼠标击中测试2——增加键盘接口

     添加键盘消息WM_KEYDOWN处理

     GetCursorPos函数

     SetCursorPos函数

     SendMessage函数

     MAKELONG

     WM_SETCURSOR消息

     WM_KILLFOCUS消息

     ShowCursor函数

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#define DIVISIONS 5

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine,

int iCmdShow)

{

     static TCHAR szAppName[] = TEXT ("Checker2") ;

    (略)

     return msg.wParam ;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM

wParam,LPARAM lParam)

{

     static BOOL fState[DIVISIONS][DIVISIONS];

     static int cxBlock,cyBlock;

     HDC hdc;

     int x,y;

     PAINTSTRUCT ps;

     POINT point;

     RECT rect;

     switch (message)

     {

     case WM_SIZE:

          cxBlock = LOWORD(lParam) / DIVISIONS;

          cyBlock = HIWORD(lParam) / DIVISIONS;

          return 0;

     //鼠标移入窗口的消息:当光标进入或离开某个窗口或控件的客户区域时,

     //Windows 会生成 WM_SETCURSOR 消息并发送给窗口的消息队列

     case WM_SETCURSOR:

        //如果bShow为TRUE,则显示计数增加一。如果bShow为FALSE,则显示计数减一。

          ShowCursor(TRUE);//显示计数+1,如果安装了鼠标则忽略

          return 0;

//当窗口或控件失去焦点时,Windows 将生成 WM_KILLFOCUS 消息并发送给窗口消息队列

     case WM_KILLFOCUS:

          ShowCursor(FALSE);//显示计数-1

          return 0;

     case WM_KEYDOWN:

          //因wParam为虚拟键码,lParam为击键的6个字段,没鼠标坐标。

          GetCursorPos(&point);//检索鼠标光标在屏幕坐标中的位置

          ScreenToClient(hwnd,&point);//将屏幕坐标转换为客户区坐标

         

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

          y = max(0,min(DIVISIONS - 1,point.y / cyBlock));

          switch (wParam)

          {

          case VK_UP:

               y--;

               break;

          case VK_DOWN:

               y++;

               break;

          case VK_LEFT:

               x--;

               break;

          case VK_RIGHT:

               x++;

               break;

          case VK_HOME:

               x = y = 0;

               break;

          case VK_END:

               x = y = DIVISIONS - 1;

               break;

          case VK_RETURN:

          case VK_SPACE:

            //模拟发送鼠标消息

            SendMessage(hwnd,WM_LBUTTONDOWN,MK_LBUTTON,

                MAKELONG(x * cxBlock,y * cyBlock));//宏,置lParam参数高字和低字

               break;

          }

          //x原区间为[0,4]+5后,移到[5,9]区间,取模,防止x--后出现负数区间。

          x = (x + DIVISIONS) % DIVISIONS;

          y = (y + DIVISIONS) % DIVISIONS;

          //设置鼠标位置到矩形中央位置

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

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

          //客户区坐标转屏幕坐标,并设置鼠标位置

          ClientToScreen(hwnd,&point);

          SetCursorPos(point.x,point.y);

          return 0;

     case WM_LBUTTONDOWN:

          x = LOWORD(lParam) / cxBlock;

          y = HIWORD(lParam) / cyBlock;

          //如果鼠标击键消息在客户区范围内

          if (x < DIVISIONS && y <DIVISIONS)

          {

               fState[x][y] ^= 1;//点击区域,绘制对角线的判断条件

               rect.left = x *cxBlock;

               rect.top = y *cyBlock;

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

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

               //重绘矩形区域

               InvalidateRect(hwnd,&rect,FALSE);

          }

          else //鼠标点击客户区外

               MessageBeep(0);//蜂鸣

          return 0;

     case WM_PAINT:

          hdc = BeginPaint(hwnd,&ps);

          //绘制客户区以DIVSIONS为单位的矩形

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

          {

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

               {

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

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

                    //如果鼠标点击有效区域

                    if (fState[x][y])//非零表示有效区域

                    {

                        //矩形区域内绘制对角线

                         MoveToEx(hdc,x * cxBlock,y * cyBlock,NULL);

                         LineTo(hdc,(x + 1) *cxBlock,(y + 1) * cyBlock);

                         MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);

                         LineTo(hdc, (x + 1) *cxBlock, y * cyBlock);

                    }

               }

          }

          EndPaint(hwnd,&ps);

          return 0;

     case WM_DESTROY:

          PostQuitMessage(0);

          break;

     }

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

}

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

GetCursorPos函数:检索鼠标光标在屏幕坐标中的位置

BOOL GetCursorPos(

  LPPOINT lpPoint   //指向接收光标的屏幕坐标的POINT结构的指针。

);

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

SetCursorPos函数:将光标移动到指定的屏幕坐标

BOOL SetCursorPos(

  int X,

  int Y

);

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

SendMessage函数:将指定的消息发送到一个或多个窗口。

LRESULT SendMessage(

  HWND   hWnd, //

  UINT   Msg,  //WM_LBUTTONDOWN

  WPARAM wParam,//MK_LBUTTON

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

);

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

MAKELONG宏:通过串联指定的值来创建LONG值

DWORD MAKELONG(

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

   WORD wHigh//新值的高位字。

);

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

WM_SETCURSOR消息:当光标进入或离开某个窗口或控件的客户区域时,

Windows 会生成 WM_SETCURSOR 消息并发送给窗口的消息队列

#define WM_SETCURSOR                    0x0020

参数wParam:包含光标的窗口的句柄。

lParam

lParam的低位字指定光标位置的命中测试结果。请参阅WM_NCHITTEST的返回值以获取可能的值。

lParam的高位字指定触发此事件的鼠标窗口消息,例如WM_MOUSEMOVE。当窗口进入菜单模式时,该值为零。

返回值

如果应用程序处理此消息,则应返回TRUE停止进一步处理,或者返回FALSE继续。

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

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

当窗口或控件失去焦点时,Windows 将生成 WM_KILLFOCUS 消息并发送给窗口的消息队列。

应用程序可以通过处理这个消息来响应窗口或控件失去焦点的事件。

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

ShowCursor函数:显示或隐藏光标。

int ShowCursor(

  BOOL bShow   //如果bShow为TRUE,则显示计数增加一。如果bShow为FALSE,则显示计数减一。

);

返回值

类型:int

返回值指定新的显示计数器。

*/

运行结果:

图6-3 鼠标击中测试2

      总结

实例CHECKER2.C在CHECKER1.C的基础上增加了两个消息的处理和一个键盘接口。

        ●WM_SETCURSOR消息:WM_SETCURSOR通知应用程序设置光标的外观。当光标进入或离开某个窗口或控件的客户区域时,Windows 会生成 WM_SETCURSOR 消息并发送给窗口的消息队列。窗口过程接到WM_SETCURSOR消息时执行ShowCursor(TRUE);语句,将鼠标显示计数加一(鼠标显示计数为0时,隐藏鼠标)。

应用程序可以通过处理这个消息来决定在特定情况下如何设置光标的外观。

WM_SETCURSOR 消息的处理通常涉及以下几个步骤:

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

2.应用程序确定当前光标所处位置的特定情况,例如是否在客户区域、非客户区域或控件的边界上。

3.应用程序根据特定情况选择合适的光标形状,并使用系统函数(如 SetCursor)设置光标的外观。

通过处理 WM_SETCURSOR 消息,应用程序可以实现自定义的光标行为,例如根据不同的控件或窗口状态显示不同的光标形状。

需要注意的是,WM_SETCURSOR 消息通常与鼠标移动事件相关联。在处理 WM_SETCURSOR 消息时,应用程序通常还需要处理与鼠标移动相关的消息,如 WM_MOUSEMOVE。

        ●WM_KILLFOCUS消息:通知应用程序失去焦点(focus)。当窗口或控件失去焦点时,Windows 将生成 WM_KILLFOCUS 消息并发送给窗口的消息队列。应用程序可以通过处理这个消息来响应窗口或控件失去焦点的事件。窗口过程接到WM_KILLFOCUS消息时执行ShowCursor(FALSE);语句,将鼠标显示计数减一(鼠标显示计数为0时,隐藏鼠标)。

失去焦点意味着窗口或控件不再是当前接收用户输入的对象。这可能发生在用户将焦点移动到另一个窗口、将焦点转移到桌面或切换到另一个应用程序时。

在处理 WM_KILLFOCUS 消息时,应用程序可以执行特定的操作,如保存当前输入状态、更新界面或执行其他相关的处理逻辑。

需要注意的是,WM_KILLFOCUS 消息是与获得焦点的消息 WM_SETFOCUS 相对应的。当窗口或控件获得焦点时,将生成 WM_SETFOCUS 消息。通过处理这两个消息,应用程序可以跟踪焦点的变化并作出相应的响应。

       ●WM_KEYDOWN消息:实例CHECKER2.C通过处理WM_KEYDOWN消息为实例增加一个键盘接口,以此支持用户通过键盘上下左右方向键和HOME、END键在25个矩形内移动鼠标指针,通过空格和回车键模拟点击鼠标左键。

       窗口过程处理WM_KEYDOWN消息时,首先调用GetCursorPos函数获取鼠标在屏幕上的坐标,然后调用ScreenToClient函数将屏幕坐标转换为客户区坐标。

       【注意】通过使用min和max宏,将x和y坐标值锁定在0~4之间。

       接着通过WM_KEYDOWN消息的wParam参数判断按下了哪个键盘按键。

       如果是上下左右方向键,则分别将x和y坐标值加一或减一。

       如果是HOME键,则x=y=0;

       如果是END键,则x = y = DIVISIONS - 1;

       如果是空格或回车键,则调用SendMessage函数发送一个鼠标WM_LBUTTONDOW消息,wParam参数为鼠标左键虚拟键码MK_LBUTTON,lParam参数为x和y坐标值(使用MAKELONG宏置lParam参数的高字和低字)。

       最后将x和y坐标置于矩形中心位置,并调用ClientToScreen函数将坐标转换为屏幕坐标,然后调用SetCursorPos函数将其设置为鼠标坐标。

6.4.3 第38练:鼠标击中测试3—子窗口

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

038  WIN32 API 每日一练

     第38个例子CHECKER3.C:鼠标击中测试3——子窗口

     同时注册主窗口与子窗口

     子窗口的预留空间

     子窗口ID:wndclass.lpszMenuName

     GetWindowLong函数

     MoveWindow函数

     SetWindowLong函数

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#define DIVISIONS 5

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //主窗口过程

LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);  //子窗口的窗口过程

TCHAR szChildClass[] = TEXT("Checker_Child"); //须定义为全局变量,因为WinMain和WndProc中都要用到。

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

 PSTR szCmdLine, int iCmdShow)

{

     static TCHAR szAppName[] = TEXT("Checker3");

    (略)

     //注册子窗口类

     wndclass.cbWndExtra = sizeof(long);//保留额外4个字节空间

     wndclass.lpszClassName = szChildClass;

     wndclass.hIcon = NULL;

     wndclass.lpfnWndProc = ChildWndProc;

     RegisterClass(&wndclass);

    (略)

     return msg.wParam;

}

//主窗口过程

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

{

     static HWND hwndChild[DIVISIONS][DIVISIONS];

     int cxBlock,cyBlock,x,y;

    

     switch (message)

     {

         //获取主窗口进程句柄hInstance的三种方法:

         //1、hInstance设置为全局变量

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

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

     case WM_CREATE:

          //创建25个子窗口

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

          {

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

               {

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

WS_CHILDWINDOW | WS_VISIBLE,//没WS_VISIBLE需要调用ShowWindow

                  0,0,0,0,

                  hwnd,(HMENU)((y << 8) | x),//菜单句柄子ID作为子窗口的唯一标识

                 (HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),//获得hInstance

                  NULL);

               }

          }

          return 0; 

  

     case WM_SIZE:

          cxBlock = LOWORD(lParam) / DIVISIONS;

          cyBlock = HIWORD(lParam) / DIVISIONS;

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

          {

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

               {

                    //更改子窗口的尺寸

                    MoveWindow(hwndChild[x][y],x * cxBlock,y * cyBlock,

                              cxBlock,cyBlock,TRUE);

               }

          }

          return 0;

    

     case WM_LBUTTONDOWN:

          MessageBeep(0);//有效区外点击鼠标左键,蜂鸣

          return 0;

     case WM_DESTROY:

          PostQuitMessage(0);

          return 0;

     }

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

}

//子窗口过程

LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message,

 WPARAM wParam, LPARAM lParam)

{

      HDC hdc ;

      PAINTSTRUCT ps ;

      RECT rect ;

      switch (message)

      {

      case WM_CREATE :

         //wndclass.cbWndExtra = sizeof(long);//给子窗口预留4个字节空间保存信息

         //更改指定窗口的扩展风格、窗口过程地址或用户数据

// on/off flag 子窗口额外4个字节存储空间中保存一个0作为标记值

        SetWindowLong (hwnd, 0, 0) ;

           return 0 ;

      case WM_LBUTTONDOWN :

//鼠标点击后,将将额外存储空间内的0或1进行交替转换

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

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

           return 0 ;

      case WM_PAINT :

           hdc = BeginPaint(hwnd, &ps);

           GetClientRect(hwnd, &rect);

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

           if (GetWindowLong(hwnd, 0))//检索有关指定窗口的信息,返回值0表示失败

           {

               //画对角线

                MoveToEx(hdc, 0, 0, NULL);

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

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

                LineTo(hdc, rect.right, 0);

           }

           EndPaint(hwnd, &ps);

           return 0;

      }

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

}

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

MoveWindow函数

更改指定窗口的位置和尺寸。对于顶级窗口,位置和尺寸是相对于屏幕的左上角的。

对于子窗口,它们相对于父窗口客户区的左上角。

BOOL MoveWindow(

  HWND hWnd,

  int  X,

  int  Y,

  int  nWidth,

  int  nHeight,

  BOOL bRepaint//TRUE重绘,FALSE

);

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

GetWindowLong函数:用于获取窗口属性的函数,它可以用来检索指定窗口的扩展风格、窗口样式、窗口过程地址或用户数据。

LONG GetWindowLongA(

  HWND hWnd,// 要获取属性的窗口句柄。

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

);

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

SetWindowLong函数:改指定窗口的属性。该函数还将指定偏移量的32位(长)值设置到额外的窗口存储器中。

LONG SetWindowLongA(

  HWND hWnd,   //窗口句柄

  int  nIndex, //从零开始的要设置值的偏移量。

  LONG dwNewLong//替换值。

);

*/

运行结果:

图6-4 鼠标击中测试3

 

总结

       实例38和39是通过在窗口客户区绘制25个矩形,由主窗口过程负责捕获鼠标左键和键盘消息,并绘制矩形对角线。而实例CHECKER3.C则是在窗口客户区绘制了25个子窗口,由子窗口过程负责捕获鼠标左键消息并绘制子窗口客户区对角线。

       ●首先我们来看主窗口过程:

       主窗口过程处理WM_CREATE消息时,调用CreateWindow绘制25个子窗口(子窗口初始尺寸为0)。子窗口的标识符为菜单项ID。

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

如果主窗口接到WM_LBUTTONDOWN消息时,调用MessageBeep函数蜂鸣示警,表示鼠标点击位置落在了主窗口内。

       ●再看子窗口过程:

       子窗口过程处理WM_CREATE消息时,调用SetWindowLong函数将窗口预留的4个字节存储空间标记值置0(主程序注册子窗口类时初始值为空)。

接着在处理WM_LBUTTONDOWN消息时,先调用GetWindowLong函数获取窗口额外存储空间的值,并与常量值1进行异或运算在0和1之间较替切换,然后调用SetWindowLong函数将切换后的值置于窗口额外存储空间。

最后在处理WM_PAINT消息时,依据窗口额外存储空间的值绘制客户区对角线。

●SetWindowLong函数:用于修改窗口属性的函数,它可以用来更改指定窗口的扩展风格、窗口过程地址或用户数据。

在较新的 Windows 版本中,推荐使用 SetWindowLongPtr 函数来替代 SetWindowLong,特别是在编写 64 位应用程序时,以支持更大范围的窗口句柄。SetWindowLongPtr 函数的功能与 SetWindowLong 类似,但接受一个 LONG_PTR 参数,可以处理 32 位和 64 位窗口句柄。以下是 SetWindowLongPtr 的函数原型:

LONG_PTR SetWindowLongPtr(

  HWND     hWnd,

  int      nIndex,

  LONG_PTR dwNewLong

);

其中,参数说明如下:

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

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

GWL_EXSTYLE:用于修改窗口的扩展风格。

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

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

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

GWL_USERDATA:用于修改窗口的用户数据。

dwNewLong:新的属性值。

SetWindowLongPtr 函数返回被修改属性的旧值,可以在需要时进行保存或进一步处理。

需要注意的是,修改窗口属性可能会对窗口的行为和外观产生重要影响,因此在使用 SetWindowLongPtr 函数时应谨慎,并根据具体需求和文档准确理解每个属性的含义和影响。

●GetWindowLong函数:用于获取窗口属性的函数,它可以用来检索指定窗口的扩展风格、窗口样式、窗口过程地址或用户数据。

在较新的 Windows 版本中,推荐使用 GetWindowLongPtr 函数来替代 GetWindowLong,特别是在编写 64 位应用程序时,以支持更大范围的窗口句柄。GetWindowLongPtr 函数的功能与 GetWindowLong 类似,但接受一个 LONG_PTR 参数,可以处理 32 位和 64 位窗口句柄。以下是 GetWindowLongPtr 的函数原型:

LONG_PTR GetWindowLongPtr(

  HWND hWnd,

  int  nIndex

);

其中,参数说明如下:

hWnd:要获取属性的窗口句柄。

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

GWL_EXSTYLE:用于获取窗口的扩展风格。

GWL_STYLE:用于获取窗口的样式。

GWL_WNDPROC:用于获取窗口过程地址。

GWL_HINSTANCE:用于获取窗口实例句柄。

GWL_USERDATA:用于获取窗口的用户数据。

GetWindowLongPtr 函数返回对应属性的值,可以根据需要进一步处理或使用。

需要注意的是,获取窗口属性可以用于了解窗口的当前状态和配置,但在修改窗口属性之前,应该仔细考虑可能的影响和限制。

6.4.4 第39练:鼠标击中测试4—子窗口增加键盘接口

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

039  WIN32 API 每日一练

     第39个例子CHECKER4.C:鼠标击中测试4——子窗口增加键盘接口

     WM_SETFOCUS消息

     WM_KILLFOCUS消息

     SetFocus函数

     GetDlgItem函数

     GetParent函数

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#define DIVISIONS 5

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

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

int idFocus = 0 ; //焦点,当前选中的矩形(用子窗口ID来标识)

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

 PSTR szCmdLine, int iCmdShow)

{

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

    (略)

     return msg.wParam;

}

//主窗口过程

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

{

     static HWND hwndChild[DIVISIONS][DIVISIONS] ;

     int cxBlock, cyBlock, x, y ;

      switch (message)

      {

      case WM_CREATE :

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

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

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

                          WS_CHILDWINDOW | WS_VISIBLE,

                          0, 0, 0, 0,

                          hwnd, (HMENU)(y << 8 | x),

                          (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),NULL);

           return 0;

      case WM_SIZE :

           cxBlock = LOWORD(lParam) / DIVISIONS;

           cyBlock = HIWORD(lParam) / DIVISIONS;

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

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

                     MoveWindow(hwndChild[x][y],

                          x * cxBlock, y * cyBlock,

                          cxBlock, cyBlock, TRUE);

           return 0;

      case WM_LBUTTONDOWN :

           MessageBeep (0) ;

           return 0 ;

      //  将焦点设置为子窗口

      case WM_SETFOCUS: //将接收输入焦点的子窗口 ID保存在全局变量idFocus中

           SetFocus (GetDlgItem (hwnd, idFocus)) ; //将键盘焦点设置到指定的窗口

           return 0 ;

      // On key-down 消息上,会更改焦点窗口

      case WM_KEYDOWN:

          //恢复原值

           x = idFocus & 0xFF;

           y = idFocus >> 8;

           switch (wParam)

           {

           case VK_UP: y--;        break;

           case VK_DOWN: y++;      break;

           case VK_LEFT: x--;      break;

           case VK_RIGHT: x++;     break;

           case VK_HOME: x = y = 0; break;

           case VK_END: x = y = DIVISIONS - 1; break;

           default: return 0;//其它按键不处理,直接返回

           }

           x = (x + DIVISIONS) % DIVISIONS;

           y = (y + DIVISIONS) % DIVISIONS;

           idFocus = y << 8 | x;

           SetFocus(GetDlgItem(hwnd, idFocus));//将键盘焦点设置到指定的子窗口

           return 0;

      case WM_DESTROY:

           PostQuitMessage(0);

           return 0;

      }

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

}

//子窗口过程

LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message,

 WPARAM wParam, LPARAM lParam)

{

      HDC hdc ;

      PAINTSTRUCT ps ;

      RECT rect ;

     switch (message)

      {

      case WM_CREATE :

           SetWindowLong (hwnd, 0, 0) ; // on/off flag 保存在cbWndExtra空间

           return 0 ;

     case WM_KEYDOWN:

           //  将大多数按键发送到父窗口

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

           {

               //回车空格键除外的消息返回给父窗口

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

               return 0 ;

           }

           //return 0;

      // 通过翻转来切换正方形

        //回车,空格等同于鼠标左键

      case WM_LBUTTONDOWN :

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

           SetFocus(hwnd);//设置输入焦点

           InvalidateRect(hwnd, NULL, FALSE);//使窗口无效以便重新绘制

           return 0;     

      case WM_SETFOCUS: //获得键盘焦点消息

           idFocus = GetWindowLong (hwnd, GWL_ID) ; //获取焦点窗口ID

           // 继续执行

      case WM_KILLFOCUS: //在失去键盘焦点之前立即发送到窗口

           InvalidateRect (hwnd, NULL, TRUE) ;

           return 0 ;

      case WM_PAINT : //子窗口处理空格和回车消息

           hdc = BeginPaint (hwnd, &ps) ;

           GetClientRect (hwnd, &rect) ;

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

           // 绘制对角线 

           if (GetWindowLong (hwnd, 0))

           {

                MoveToEx(hdc, 0, 0, NULL);

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

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

                LineTo(hdc, rect.right, 0);

           }

           // 绘制焦点矩形--用虚线框表示焦点窗口

           if (hwnd == GetFocus ())

           {

                rect.left += rect.right / 10;

                rect.right -= rect.left;

                rect.top += rect.bottom / 10;

                rect.bottom -= rect.top;

                SelectObject(hdc, GetStockObject(NULL_BRUSH));

                SelectObject(hdc, CreatePen(PS_DASHDOT, 0, 0));//虚线画笔

                Rectangle(hdc, rect.left, rect.top, rect.right,

                     rect.bottom);

                DeleteObject(SelectObject(hdc, GetStockObject

                (BLACK_PEN)));

           }

           EndPaint (hwnd, &ps) ;

           return 0 ;

      }

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

}

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

WM_SETFOCUS消息:获得键盘焦点后发送到窗口

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

WM_KILLFOCUS消息:在失去键盘焦点之前立即发送到窗口

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

SetFocus函数:对指定的窗口设置键盘焦点

HWND SetFocus(

  HWND hWnd

);

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

GetDlgItem函数:在指定的对话框中检索控件的句柄

HWND GetDlgItem(

  HWND hDlg,

  int  nIDDlgItem//要检索的控件的标识符

);

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

GetParent函数:检索指定窗口的父级或所有者的句柄

HWND GetParent(

  HWND hWnd

);

*/

       运行结果:

图6-5 鼠标击中测试4

 

总结

       实例CHECKER4.C在CHECKER3.C的基础上增加了键盘接口。这里了的关键是焦点窗口在主窗口与子窗口之间的切换。

       ●当我们处理鼠标消息时,只需要判断鼠标的坐标位置落在哪个窗口客户区内,就可以将窗口焦点切换到该窗口客户区。

       ●当我们处理键盘消息时,键盘消息只能被送入当前具有输入焦点的窗口,因此,需要我们先转移输入焦点至我们想要获得键盘输入的窗口才可以。

       ●主窗口过程:

       主窗口过程在处理M_SETFOCUS消息时,调用GetDlgItem (hwnd, idFocus)获取之前具有输入焦点的子窗口句柄,然后再调用SetFocus函数将焦点还给该子窗口。

       主窗口过程处理WM_KEYDOWN消息时,说明主窗口当前获取了输入焦点,否则也不可能获取按键消息。在处理WM_KEYDOWN消息时,分别处理上下左右和HOME、END按键,重置坐标x和y的值。【注意】重置坐标后,还需要调用SetFocus函数再次将输入焦点还给之前具有输入焦点的子窗口。

       ●子窗口过程:

       子窗口过程在处理M_KEYDOWN消息时,只负责处理回车和空格键,其他按键消息调用SendMessage函数将其返还给主窗口。

       如果是回车和空格按键消息或者是WM_LBUTTONDOWN鼠标左键消息,则重置窗口额外空间存储的标记值,然后调用SetFocus函数让当前窗口获取输入焦点。(不要返回)接着处理WM_KILLFOCUS消息,在当前子窗口失去焦点时重绘子窗口。

       ●实例新增两个函数

       1.GetDlgItem函数:在指定的对话框中检索控件的句柄。

GetDlgItem 函数的函数原型:

HWND GetDlgItem(

  HWND hDlg,

  int  nIDDlgItem

);

其中,参数说明如下:

hDlg:对话框的句柄,即包含目标控件的对话框窗口。

nIDDlgItem:控件的标识符(ID),它是在对话框模板中为每个控件分配的唯一标识符。

GetDlgItem 函数会根据指定的对话框句柄和控件标识符,在对话框中查找对应控件的句柄,并返回该句柄。

通过获取控件的句柄,应用程序可以进一步操作和控制该控件,例如修改其属性、获取或设置其文本内容、发送消息给控件等。

       2.GetParent函数:检索指定窗口的父级或所有者的句柄。

       GetParent 函数的函数原型:

HWND GetParent(

  HWND hWnd

);

其中,参数说明如下:

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

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

通过获取父窗口句柄,应用程序可以对父窗口及其子窗口进行操作和控制,例如修改父窗口的属性、发送消息给父窗口或子窗口等。

需要注意的是,父窗口并不一定是直接的父子关系,可能存在多层嵌套的窗口结构。在多层嵌套的情况下,GetParent 函数仅返回指定窗口的直接父窗口句柄。

另外,顶级窗口(没有父窗口的窗口)的父窗口句柄通常是桌面窗口的句柄。

6.4.5 第40练:捕获鼠标消息1

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

040  WIN32 API 每日一练

     第40个例子BLOKOUT1.C:捕获鼠标消息1

     SetROP2函数

     SetCursor函数

缺陷:无法捕捉客户区外的鼠标消息

(c) www.bcdaren.com, 2020

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

#include <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

 PSTR szCmdLine, int iCmdShow)

{

     static TCHAR szAppName[] = TEXT("BlokOut1");

    (略)

     return msg.wParam;

}

//绘制矩形图

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

{

     HDC hdc;

     hdc = GetDC(hwnd);

     SetROP2(hdc, R2_NOT);//颜色取反。可删除旧的边框,新边框颜色为黑色。

     SelectObject(hdc, GetStockObject(NULL_BRUSH));//空笔刷

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

     ReleaseDC(hwnd, hdc);

}

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

lParam)

{

      static BOOL fBlocking, fValidBox ;

      static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;

      HDC hdc ;

      PAINTSTRUCT ps ;

      switch (message)

      {

      case WM_LBUTTONDOWN :

          //获取鼠标位置信息

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

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

          //绘制矩形(0,0,0,0)

           DrawBoxOutline (hwnd, ptBeg, ptEnd) ;

          //捕获鼠标,设置鼠标形状

           SetCursor (LoadCursor (NULL, IDC_CROSS)) ;

          //标记值

           fBlocking = TRUE ; //阻塞

           return 0 ;

      case WM_MOUSEMOVE :

           if (fBlocking)

           {

               //捕获鼠标,设置鼠标形状

                SetCursor(LoadCursor(NULL, IDC_CROSS));

               //删除旧边框,颜色取反

                DrawBoxOutline(hwnd, ptBeg, ptEnd);

               //获取鼠标位置信息

                ptEnd.x = LOWORD(lParam);

                ptEnd.y = HIWORD(lParam);

               //绘制新边框,颜色再次取反

                DrawBoxOutline(hwnd, ptBeg, ptEnd);

           }

           return 0 ;

      case WM_LBUTTONUP : //释放鼠标左键

           if (fBlocking) //按下鼠标左键并绘制矩形

           {

               //删除旧矩形

                DrawBoxOutline(hwnd, ptBeg, ptEnd);

               //获取鼠标位置信息

                ptBoxBeg = ptBeg;

                ptBoxEnd.x = LOWORD(lParam);

                ptBoxEnd.y = HIWORD(lParam);

               //捕获鼠标,设置鼠标位图

                SetCursor(LoadCursor(NULL, IDC_ARROW));

                 fBlocking = FALSE;//标记没有按下鼠标左键

                fValidBox = TRUE;//标记已释放鼠标左键

               //重绘窗口客户区

                InvalidateRect(hwnd, NULL, TRUE);

           }

           return 0;

      case WM_CHAR :

            // Escape键 & fBlocking,否则将不断切换显示与隐藏边框

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

           {

                DrawBoxOutline(hwnd, ptBeg, ptEnd);

                SetCursor(LoadCursor(NULL, IDC_ARROW));

                fBlocking = FALSE;

           }

           return 0 ;

      case WM_PAINT :

           hdc = BeginPaint (hwnd, &ps) ;

           if (fValidBox) //捕获到WM_LBUTTONUP消息时

           {

               //填充矩形

                SelectObject(hdc, GetStockObject(BLACK_BRUSH));

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

                     ptBoxEnd.x, ptBoxEnd.y);

           } 

           EndPaint (hwnd, &ps) ;

           return 0 ;

      case WM_DESTROY :

           PostQuitMessage (0) ;

           return 0 ;

      }

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

}

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

SetROP2函数:设置当前的前景混合模式。

GDI使用前景混合模式将笔和填充对象的内部与屏幕上已经存在的颜色结合起来。

前景混合模式定义如何将画笔或笔中的颜色与现有图像中的颜色进行组合。

int SetROP2(

  HDC hdc,//设备上下文的句柄

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

);

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

SetCursor函数:设置鼠标位图。

HCURSOR SetCursor(

  HCURSOR hCursor   //光标句柄

);

光标的句柄。游标必须已经由CreateCursor函数创建或已由LoadCursor或LoadImage函数加载。

如果此参数为NULL,则将光标从屏幕上移开。

*/

       运行结果:

图6-6 捕获鼠标消息1

     总结

  1.实例BLOKOUT1.C自定义了一个绘图函数DrawBoxOutline。先将绘图二元光栅操作模式设置为颜色取反,然后选入空画刷填充矩形背景,调用Rectangle绘制矩形。

       2.在窗口过程中,首先处理WM_LBUTTONDOWN消息,由消息参数lParam获取鼠标位置,接着调用DrawBoxOutline函数绘制矩形,并调用SetCursor将鼠标位图设置为十字形。将标记变量fBlocking设为TRUE,表示已按下鼠标左键并绘制矩形。

       3.接着处理鼠标移动消息WM_MOUSEMOVE。调用SetCursor捕获鼠标并将鼠标位图设置为十字。通过lParam参数获取移动鼠标的当前坐标。

       【注意】这里两次调用DrawBoxOutline函数,第一次擦掉原来的矩形,第二次绘制新坐标位置的矩形。

       4.接着处理WM_LBUTTONUP消息,当释放鼠标左键时,调用DrawBoxOutline函数删除旧的矩形。通过lParam参数获取当前鼠标坐标信息。调用SetCursor函数捕获鼠标并将鼠标位图重新设置为箭头。接着把标记变量fBlocking设为FALSE,表示没有按下鼠标左键,把标记变量fValidBox设置为TRUE,表示已释放鼠标左键。最后调用InvalidateRect重绘窗口客户区并擦除背景。

       5.处理WM_CHAR消息时,当按下ESC键并且按下鼠标左键时,调用DrawBoxOutline函数擦除矩形,并将鼠标位图改为箭头。标记变量fBlocking设为FALSE。

       6.处理WM_PAINT消息,当捕获释放鼠标左键时,选入黑色画刷,填充由Rectangle绘制的矩形。

       【注意】该实例无法捕捉窗口客户区之外的鼠标,因此,当鼠标移动到窗口客户区之外时,无法正常绘制矩形。我们将在下一个实例中修正。

6.4.6 第41练:捕获鼠标消息2

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

041  WIN32 API 每日一练

     第41个例子BLOKOUT2.C:捕获鼠标消息2

     SetCapture函数

     ReleaseCapture函数

修正:无法捕捉客户区外的鼠标消息

(c) www.bcdaren.com, 2020

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

#include <windows.h>

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

 PSTR szCmdLine, int iCmdShow)

{

     static TCHAR szAppName[] = TEXT("BlokOut2");

    (略)

     return msg.wParam;

}

//绘图函数

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

{

    (略)

}

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

{

      static BOOL fBlocking, fValidBox ;

      static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd ;

      HDC hdc ;

      PAINTSTRUCT ps ;

      switch (message)

      {

      case WM_LBUTTONDOWN :

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

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

           DrawBoxOutline(hwnd, ptBeg, ptEnd);

          //新增代码1

           SetCapture(hwnd);//将鼠标捕获设置为属于当前线程的指定窗口

           SetCursor(LoadCursor(NULL, IDC_CROSS));

           fBlocking = TRUE;

           return 0;

      case WM_MOUSEMOVE :

            (略)

           return 0;

      case WM_LBUTTONUP :

           if (fBlocking)

           {

                DrawBoxOutline(hwnd, ptBeg, ptEnd);

                ptBoxBeg = ptBeg;

                ptBoxEnd.x = LOWORD(lParam);

                ptBoxEnd.y = HIWORD(lParam);

                //新增代码2

//从当前线程的窗口中释放鼠标捕获,并恢复正常的鼠标输入处理

                ReleaseCapture();

                SetCursor(LoadCursor(NULL, IDC_ARROW));

                fBlocking = FALSE;

                fValidBox = TRUE;

                InvalidateRect(hwnd, NULL, TRUE);

           }

           return 0;

      case WM_CHAR :

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

           {

                DrawBoxOutline(hwnd, ptBeg, ptEnd);

               //新增代码3

//从当前线程的窗口中释放鼠标捕获,并恢复正常的鼠标输入处理

                ReleaseCapture();

                SetCursor(LoadCursor(NULL, IDC_ARROW));

                fBlocking = FALSE;

           }

           return 0 ;

      case WM_PAINT :

           hdc = BeginPaint(hwnd, &ps);

            (略)     

EndPaint(hwnd, &ps);

           return 0;

      case WM_DESTROY :

           PostQuitMessage(0);

           return 0;

      }

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

}

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

SetCapture函数:将鼠标捕获设置为属于当前线程的指定窗口。

当鼠标悬停在捕获窗口上方时,或者当鼠标悬停在捕获窗口上方,

按下鼠标按钮时,SetCapture捕获鼠标输入。一次只能捕获一个窗口。

如果鼠标光标位于另一个线程创建的窗口上,则仅当按下鼠标按钮时,系统才会将鼠标输入定向到指定的窗口。

HWND SetCapture(

  HWND hWnd    //当前线程中要捕获鼠标的窗口的句柄。

);

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

ReleaseCapture函数:从当前线程的窗口中释放鼠标捕获,并恢复正常的鼠标输入处理。

捕获光标的窗口将接收所有鼠标输入,而与光标的位置无关,除非在光标热点位于另一个线程的窗口中时单击鼠标按钮。

BOOL ReleaseCapture();

*/

       运行结果:

                    

图6-7 捕获鼠标消息2

 

总结

       实例BLOKOUT2.C新增了三处代码:

       1.处理WM_LBUTTONDOWN消息时,调用SetCapture函数。

SetCapture(hwnd);//将鼠标捕获设置为属于当前线程的指定窗口

       2.处理WM_LBUTTONUP消息时,调用函数ReleaseCapture。

ReleaseCapture();//从当前线程的窗口中释放鼠标捕获,并恢复正常的鼠标输入处理

       3.处理WM_CHAR消息时,调用函数ReleaseCapture。

ReleaseCapture();//从当前线程的窗口中释放鼠标捕获,并恢复正常的鼠标输入处理

       这样窗口就可以捕捉和释放客户区之外的鼠标坐标位置信息,即使鼠标移动到客户区之外,也可以正常绘制矩形了。

       SetCapture函数:用于设置指定窗口捕获鼠标输入。以下是 SetCapture 函数的函数原型:

HWND SetCapture(

  HWND hWnd    //要捕获鼠标输入的窗口句柄

);

4.SetCapture 函数用于将鼠标输入的捕获设置到指定的窗口。一旦窗口捕获了鼠标输入,无论鼠标是否在窗口的客户区内,窗口都将收到鼠标消息。通常情况下,只有在特定的情况下才需要使用 SetCapture 函数。

以下是一些常见的使用情况:

实现拖拽操作:在开始拖拽操作时,调用 SetCapture 函数将鼠标输入捕获到拖拽的窗口,这样即使鼠标移出窗口的客户区,窗口也能持续接收鼠标消息,直到松开鼠标按钮。

自定义鼠标操作:在某些特殊的应用场景中,可能需要自定义鼠标操作,例如绘制自定义的鼠标形状或处理特定的鼠标事件。通过调用 SetCapture 函数,可以捕获鼠标输入并自行处理相应的鼠标消息。

需要注意的是,使用 SetCapture 函数后,必须在适当的时候调用 ReleaseCapture 函数来释放对鼠标输入的捕获。这样可以确保在不需要捕获鼠标输入时,将鼠标输入的控制权交还给系统。

       5.ReleaseCapture 函数:用于释放对鼠标输入的捕获。以下是 ReleaseCapture 函数的函数原型:

BOOL ReleaseCapture();

ReleaseCapture 函数用于释放先前使用 SetCapture 函数设置的鼠标输入捕获。一旦调用 ReleaseCapture 函数,窗口将不再捕获鼠标输入,鼠标输入将返回给系统。

通常情况下,与 SetCapture 函数配对使用,在不需要继续捕获鼠标输入时调用 ReleaseCapture 函数。

6.4.7 第42练:获取系统信息—增加鼠标滚轮

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

042  WIN32 API 每日一练

     第42个例子SYSMETS.C:获取系统配置信息No.2—增加鼠标滚轮

     WM_SETTINGCHANGE消息

     WM_MOUSEWHEEL消息

     SystemParametersInfo函数

(c) www.bcdaren.com, 2020

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

#include <windows.h>

#include "sysmets.h"

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    PSTR szCmdLine, int iCmdShow)

{

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

    (略)

    return msg.wParam;

}

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

{

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

    HDC         hdc;

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

    PAINTSTRUCT ps;

    SCROLLINFO  si;

    TCHAR       szBuffer[10];

    TEXTMETRIC  tm;

    ULONG ulScrollLines;//鼠标滚动行数

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

    switch (message)

    {

    case WM_CREATE:

    (略)

        return 0;

//鼠标滚轮消息,在用户操作鼠标滚轮时发送给窗口,以通知窗口发生了滚轮滚动事件

        //wParam

        //高序位字指示滑轮旋转的距离,以滑轮增量的倍数或间隔表示,即120。

        //正值表示滚轮向前旋转,远离用户; 负值表示滑轮向后旋转,朝向用户。

        //低序位字指示各种虚拟键是否已关闭。

        //lParam

        //低序位字指定指针的 x 坐标,相对于屏幕的左上角。

        //高序位字指定指针的 y 坐标(相对于屏幕左上角)。

    case WM_MOUSEWHEEL:

        if (iDeltaPerLine == 0) break;

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

        //通过该循环,将iAccumDelta由120变为0

        while (iAccumDelta >= iDeltaPerLine)

        {

            SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);

            iAccumDelta -= iDeltaPerLine;

        }

        //通过该循环,将iAccumDelta由-120变为0

        while (iAccumDelta <= -iDeltaPerLine) //

        {

            SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);

            iAccumDelta += iDeltaPerLine;

        }

        return 0;

    case WM_KEYDOWN: //处理键盘消息

        (略)

        return 0;

    case WM_SIZE:

        (略)

        return 0;

    case WM_VSCROLL:

        (略)

        return 0;

    case WM_HSCROLL:

        (略)

        return 0;

    case WM_PAINT:

        hdc = BeginPaint(hwnd, &ps);

        (略)

        EndPaint(hwnd, &ps);

        return 0;

    case WM_DESTROY:

        PostQuitMessage(0);

        return 0;

    }

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

}

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

WM_SETTINGCHANGE消息:当SystemParametersInfo函数更改系统范围的设置或更改策略设置时,发送到所有顶级窗口的消息。

更改系统参数后,应用程序应将WM_SETTINGCHANGE发送给所有顶级窗口。(此消息不能直接发送到窗口。)

WM_SETTINGCHANGE消息发送到所有顶级窗口,请使用SendMessageTimeout函数,并将hwnd参数设置为HWND_BROADCAST。

窗口通过其WindowProc函数接收此消息。

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

WM_MOUSEWHEEL消息:旋转鼠标滚轮时发送到焦点窗口。

DefWindowProc会将消息沿父链传播,直到找到处理该消息的窗口为止。

消息不应进行内部转发.

窗口通过其WindowProc函数接收此消息。

wParam

高序位字指示滑轮旋转的距离,以滑轮增量的倍数或间隔表示,即120。

正值表示滚轮向前旋转,远离用户;负值表示滑轮向后旋转,朝向用户。

低序位字指示各种虚拟键是否已关闭。

lParam

低序位字指定指针的 x 坐标,相对于屏幕的左上角。

高序位字指定指针的 y 坐标(相对于屏幕左上角)。

返回值

如果应用程序处理此消息,则它应返回零。

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

SystemParametersInfo函数:查询或设置系统级参数。

该函数也可以在设置参数中更新用户配置文件,这个函数还有很多其它功能,比如获取桌面工作区的大小。

BOOL SystemParametersInfoA(

  UINT  uiAction,//要检索或设置的系统范围参数。

  UINT  uiParam,//参数的用法和格式取决于要查询或设置的系统参数。

  PVOID pvParam,//参数的用法和格式取决于要查询或设置的系统参数。

  UINT  fWinIni//如果正在设置系统参数,则指定是否要更新用户配置文件,

//如果要更新,则是否将WM_SETTINGCHANGE消息广播到所有顶级窗口以将更改通知他们。             //如果您不想更新用户配置文件或广播WM_SETTINGCHANGE消息,

               //则此参数可以为零,也可以为以下值中的一个或多个。

);

*/

       运行结果:

图6-8 获取系统信息2

总结

       实例SYSMETS.C:获取系统配置信息No.2在第三章获取系统配置信息No.1版本的基础上增加了对鼠标滚轮消息的处理

       1.WM_SETTINGCHANGE消息用于通知应用程序系统设置的更改,是由系统发送给顶级窗口(Top-level Window)以通知它们系统设置的更改,例如显示设置、输入设置、语言设置等。

当系统设置发生更改时,Windows 将发送 WM_SETTINGCHANGE 消息给所有顶级窗口,以便它们可以更新并适应新的设置。应用程序可以通过处理这个消息来获取有关系统设置更改的通知,并相应地更新其用户界面或执行其他操作。

以下是 WM_SETTINGCHANGE 消息的消息参数:

WM_SETTINGCHANGE

    WPARAM wParam;

LPARAM lParam;

其中,wParam 和 lParam 参数的含义取决于具体的设置更改。通常情况下,lParam 参数是一个指向以 NULL 结尾的字符串的指针,该字符串包含有关所做更改的信息。应用程序可以通过检查 lParam 参数来确定具体的设置更改类型。

2.本实例在处理WM_SETTINGCHANGE消息时,先调用SystemParametersInfo函数获取鼠标滚轮每次滚动的行数,预设值一般为3。如果每次滚动的行数ulScrollLines为0,则iDeltaPerLine = 0;每次滚动一行需要0个止动器值。如果每次滚动的行数ulScrollLines为3,则iDeltaPerLine = 40;每次滚动一行需要40个止动器值。

WM_MOUSEWHEEL 是 Windows 消息中的一个消息代码,用于通知应用程序鼠标滚轮的滚动事件。

3.WM_MOUSEWHEEL 消息在用户操作鼠标滚轮时发送给窗口,以通知窗口发生了滚轮滚动事件。这个消息提供了有关滚轮滚动的信息,例如滚动的距离和滚动的方向。

以下是 WM_MOUSEWHEEL 消息的消息参数:

WM_MOUSEWHEEL

    WPARAM wParam;

    LPARAM lParam;

其中,wParam 参数包含了关于滚轮滚动的信息,主要包括以下内容:

高位字(16位):表示鼠标滚轮滚动的距离,单位为 WHEEL_DELTA(通常为 120)。正值表示向前滚动,负值表示向后滚动。

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

lParam 参数包含了关于鼠标滚轮滚动事件发生时的鼠标位置信息。

4.本实例处理WM_MOUSEWHEEL 消息:

当累加增量iAccumDelta等于+120时,调用SendMessage函数向垂直滚动条发送WM_VSCROLL消息,向上滚动一行,直至累积增量为0。

当累加增量iAccumDelta等于-120时,调用SendMessage函数向垂直滚动条发送WM_VSCROLL消息,向下滚动一行,直至累积增量为0。

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

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

相关文章

vite-ts-cesium项目集成mars3d修改相关的包和配置参考

如果vite技术栈下使用原生cesium&#xff0c;请参考下面文件的包和配置修改&#xff0c;想用原生创建的viewer结合我们mars3d的功能的话。 1. package.json文件 "dependencies": {"cesium": "^1.103.0","mars3d": "^3.7.18&quo…

深度学习 - Transformer 组成详解

整体结构 1. 嵌入层&#xff08;Embedding Layer&#xff09; 生活中的例子&#xff1a;字典查找 想象你在读一本书&#xff0c;你不认识某个单词&#xff0c;于是你查阅字典。字典为每个单词提供了一个解释&#xff0c;帮助你理解这个单词的意思。嵌入层就像这个字典&#xf…

Micrometer+ZipKin分布式链路追踪

目录 背景MicrometerMicrometer与ZipKin之间的关系专业术语分布式链路追踪原理 ZipKin安装下载 MicrometerZipKin 案例演示相关文献 背景 一个系统页面上的按钮点击到结果反馈&#xff0c;在微服务框架里&#xff0c;是由N个服务组成返回结果&#xff0c;中间可能经过a->b-…

【Electron】Electron入门实现

Electron 学习笔记 Electron 是一个开源框架&#xff0c;允许开发者使用网页技术&#xff08;HTML、CSS 和 JavaScript&#xff09;来构建跨平台的桌面应用程序。它由 GitHub 开发并维护&#xff0c;最初是为了支持开发 Atom 编辑器。Electron 结合了 Chromium&#xff08;用于…

密码学及其应用 —— 对称加密技术

1. 对称加密、流加密和块加密 1.1 对称加密 对称加密&#xff08;也称为密钥加密&#xff09;是一种加密方式&#xff0c;其中加密和解密使用相同的密钥。这种加密方法基于二进制层面的操作&#xff0c;如XOR&#xff08;异或&#xff09;、SHIFT&#xff08;位移&#xff09;…

Redis Stream Redisson Stream

目录 一、Redis Stream1.1 场景1&#xff1a;多个客户端可以同时接收到消息1.1.1 XADD - 向stream添加Entry&#xff08;发消息 &#xff09;1.1.2 XREAD - 从stream中读取Entry&#xff08;收消息&#xff09;1.1.3 XRANGE - 从stream指定区间读取Entry&#xff08;收消息&…

【DevExpress】WPF DevExpressMVVM 24.1版本开发指南

DevExpressMVVM WPF 环境安装 前言重要Bug&#xff08;必看&#xff09;环境安装控件目录Theme 主题LoginWindow 登陆窗口INavigationService 导航服务DockLayout Dock类型的画面布局TreeView 树状列表注意引用类型的时候ImageSource是PresentationCore程序集的博主找了好久&am…

Navicat 外网连接 mysql (1、通过SSH方式内网访问 2、对外开放3306端口)

1、通过SSH方式内网访问 直接常规方式使用IP、账号密码连接&#xff0c;失败 SSH方式&#xff1a; 常规 选项卡中&#xff1a;localhost录入数据库账号密码 SSH 选项卡中&#xff1a;勾选使用SSH&#xff0c;输入服务器IP、账号、密码 如果出现该错误&#xff0c;可能是服务器…

Windows下activemq开启jmx

1.activemq版本信息 activemq&#xff1a;apache-activemq-5.18.4 2.Windows下activemq开启jmx 1.进入activemq conf目录&#xff0c;备份activemq.xml文件 2.编辑activemq.xml文件&#xff0c;在broker节点增加useJmx"true" <broker xmlns"http://active…

无线通讯几种常规天线类别简介

天线对于无线模块来说至关重要&#xff0c;合适的天线可以优化通信网络&#xff0c;增加其通信的范围和可靠性。天线的选型对最后的模块通信影响很大&#xff0c;不合适的天线会导致通信质量下降。针对不同的市场应用&#xff0c;天线的材质、安置方式、性能也大不一样。下面简…

基于Vue 3.x与TypeScript的PPTIST本地部署与无公网IP远程演示文稿

文章目录 前言1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统环境本地部署开源在线演示文稿应用PPTist&#xff0c;并结合cpolar内网穿透工具实现随时随地远程访问与使用该项目。 PPTist …

基于STM32的智能水质监测系统

目录 引言环境准备智能水质监测系统基础代码实现&#xff1a;实现智能水质监测系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;水质管理与优化问题解决方案与优化收尾与总结 1. 引言 智能水质监测系统通过使用STM32嵌…

RISC-V知识总结 —— 向量(扩展)指令集

资源1:晏明 - RISC-V向量扩展指令架构及LLVM自动向量化支持 - 202112118 - 第13届开源开发工具大会&#xff08;OSDTConf2021&#xff09;_哔哩哔哩_bilibili资源2:张先轶 - 基于RISC-V向量指令集优化基础计算软件生态【第12届开源开发工具大会&#xff08;OSDT2020&#xff09…

研导智能科技——AI辅助科研产品开发

人工智能&#xff08;AI&#xff09;技术的飞速发展为科研领域带来了革命性的变化。本公司致力于开发基于人工智能的科研辅助产品&#xff0c;旨在通过智能化手段提高科研人员的工作效率和研究质量。目前&#xff0c;我们成功开发了研导学术平台&#xff08;www.zhiyanxueshu.c…

Linux运维:MySQL数据库(1)

1.信息与数据&#xff1a; 数据是信息的载体&#xff0c;信息是数据的内涵。数据库就是存储数据的仓库&#xff0c;并长期存储在计算机磁盘中&#xff0c;可由多个用户和应用程序共享的数据集合&#xff0c;就是数据库。 2.数据库中的数据的特点&#xff1a; 2.1.数据是按照某…

RuleApp1.4.6文章社区客户端 广告联盟支持Docx导入

支持编译为安卓&#xff0c;苹果&#xff0c;小程序&#xff0c;H5网页的社区客户端代码&#xff0c;包括文章模块&#xff0c;用户模块&#xff0c;动态模块&#xff0c;支付模块&#xff0c;聊天模块&#xff0c;广告模块&#xff0c;商城模块等基础功能&#xff0c;包含VIP会…

10位时间戳、13位时间戳、17位时间戳,以及在JavaScript中的格式转换

一、介绍 1、10位时间戳 2、13位时间戳 3、17位时间戳 4、时间戳转换工具 二、13位时间戳的转换 1、转标准日期 2、转格式化日期 三、10位时间戳的转换 1、转标准日期 2、转格式化日期 四、17位时间戳的转换 1、解析思路 2、解析过程 &#xff08;1&#xff09;统…

C++系统编程篇——Linux第一个小程序--进度条

&#xff08;1&#xff09;先引入一个概念&#xff1a;行缓冲区 \r和\n \r表示回车 \n表示回车并换行 ①代码一 #include<stdio.h> #include<unistd.h> int main()…

django学习入门系列之第三点《伪类简单了解》

文章目录 hover&#xff08;伪类&#xff09;after&#xff08;伪类&#xff09;往期回顾 hover&#xff08;伪类&#xff09; 伪类指的是用冒号加的 hover样式指的是&#xff0c;当用户光标移动到设定区域后&#xff0c;所执行的用法 如&#xff1a; <!DOCTYPE html>…

通过代理从ARDUINO IDE直接下载开发板包

使用免费代理 实现ARDUINO IDE2.3.2 下载ESP8266/ESP32包 免费代理 列表 测试代理是否可用的 网站 有时&#xff0c;代理是可用的&#xff0c;但依然有可能找不到开发板管理器的资料包。 可以多换几个代理试试。 代理的配置 文件 -> 首选项 -> 网络 进入后做如下配置…