《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.rhkb.cn/news/362833.html

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

相关文章

Linux Static calls机制

文章目录 前言一、简介二、Background: indirect calls, Spectre, and retpolines2.1 Indirect calls2.2 Spectre (v2)2.3 RetpolinesConsequences 2.4 Static callsHow it works 三、其他参考资料 前言 Linux内核5.10内核版本引入新特性&#xff1a;Static calls。 Static c…

计算机毕业设计hadoop+spark+hive知识图谱医生推荐系统 医生数据分析可视化大屏 医生爬虫 医疗可视化 医生大数据 机器学习 大数据毕业设计

测试过程及结果 本次对于医生推荐系统测试通过手动测试的方式共进行了两轮测试。 &#xff08;1&#xff09;第一轮测试中执行了个20个测试用例&#xff0c;通过16个&#xff0c;失败4个&#xff0c;其中属于严重缺陷的1个&#xff0c;属于一般缺陷的3个。 &#xff08;2&am…

Spark SQL 的总体工作流程

Spark SQL 是 Apache Spark 的一个模块,它提供了处理结构化和半结构化数据的能力。通过 Spark SQL,用户可以使用 SQL 语言或 DataFrame API 来执行数据查询和分析。这个模块允许开发者将 SQL 查询与 Spark 的数据处理能力结合起来,实现高效、优化的数据处理。下面是 Spark S…

Spring Boot中实现定时任务最常用的方法 @Scheduled 注解和 TaskScheduler 接口【包含详情代码】

Spring Boot中实现定时任务最常用的方法 Scheduled 注解和 TaskScheduler 接口【包含详情代码】 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中………

CogMG:用大模型解决知识图谱覆盖不足的问题

CogMG&#xff1a;用大模型解决知识图谱覆盖不足的问题 提出背景知识图谱的作用知识覆盖不完整知识更新不对齐 显式分解知识三元组和补全检索增强生成&#xff08;RAG&#xff09;和知识更新 框架设计1. 查询知识图谱2. 处理结果3. 知识图谱演化 CogMG 实现3.1 模型和组件问题分…

.NET 漏洞分析 | 某ERP系统存在SQL注入

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

c++智能指针shared_ptr

文章目录 概念1.shared_ptr1.基本使用2.如何获取原始指针3. 指定删除器 2 使用shared_ptr要注意的问题2.1不要用一个原始指针初始化多个shared_ptr2.2. 避免循环引用 小结 概念 C程序设计中使用堆内存是非常频繁的操作&#xff0c;堆内存的申请和释放都由程序员自己管理。内存…

安装 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…

Python 语法基础二

7.常用内置函数 执行这个命令可以查看所有内置函数和内置对象&#xff08;两个下划线&#xff09; >>>dir(__builtins__) [__class__, __contains__, __delattr__, __delitem__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __gt…

深入剖析 Android 网络开源库 Retrofit 的源码详解

文章目录 概述一、Retrofit 简介Android主流网络请求库 二、Retrofit 源码剖析1. Retrofit 网络请求过程2. Retrofit 实例构建2.1 Retrofit.java2.2 Retrofit.Builder()2.2.1 Platform.get()2.2.2 Android 平台 2.3 Retrofit.Builder().baseUrl()2.4 Retrofit.Builder.client()…

OpenAI穿着「皇帝的新衣」;扒了数万条帖子汇总100种AIGC玩法;北美出海的财务避坑指南;我创业「如」有CTO | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; 1. 我扒了 Reddit 论坛数万条帖子&#xff0c;汇总了 GenAI 的 100 种玩法 ChatGPT 已经问世一年半了。这期间诞生了很多大语言模型和生成式人工智能…

备份和还原

stai和dnta snat&#xff1a;源地址转换 内网---外网 内网ip转换成可以访问外网的ip 内网的多个主机可以使用一个有效的公网ip地址访问外部网络 DNAT&#xff1a;目的地址转发 外部用户&#xff0c;可以通过一个公网地址访问服务内部的私网服务。 私网的ip和公网ip做一个…

【JavaEE进阶】Spring AOP使用篇

目录 1.AOP概述 2.SpringAOP快速入门 2.1 引入AOP依赖 2.2 编写AOP程序 3. Spring AOP详解 3.1 Spring AOP 核心概念 3.1.1切点(Pointcut) 3.1.2 连接点 (Join Point) 3.1.3 通知(Advice) 3.1.4 切面(Aspect) 3.2 通知类型 3.3PointCut 3.4 切面优先级 3.5 切点表…

「51媒体」政企活动媒体宣发如何做?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体宣传加速季&#xff0c;100万补贴享不停&#xff0c;一手媒体资源&#xff0c;全国100城线下落地执行。详情请联系胡老师。 政企活动媒体宣发是一个系统性的过程&#xff0c;需要明确…

使用Scala爬取安居客房产信息并存入CSV文件

使用Scala爬取安居客房产信息并存入CSV文件 本篇博客中&#xff0c;我们将介绍如何使用Scala语言编写一个简单的程序&#xff0c;来爬取安居客&#xff08;Anjuke&#xff09;网站上的房产信息&#xff0c;并将这些信息存储到CSV文件中。这个示例将涵盖HTTP请求、HTML解析、数…

麒麟系统安装MySQL

搞了一整天&#xff0c;终于搞定了&#xff0c;记录一下。 一、背景 项目的原因&#xff0c;基于JeecgBoot开发的系统需要国产化支持&#xff0c;这就需要在电脑上安装MySQL等支撑软件。 国产化项目的操作系统多是麒麟系统&#xff0c;我的系统如下&#xff1a; arm64架构。…

详细分析Oracle修改默认的时间格式(四种方式)

目录 前言1. 会话级别2. 系统级别3. 环境配置4. 函数格式化5. 总结 前言 默认的日期和时间格式由参数NLS_DATE_FORMAT控制 如果需要修改默认的时间格式&#xff0c;可以通过修改会话级别或系统级别的参数来实现 1. 会话级别 在当前会话中设置日期格式&#xff0c;这只会影响…

CCSP自考攻略+经验总结

备考攻略 备考攻略准备阶段通读阶段精度阶段总复习阶段刷题阶段命运审判 写到最后 备考攻略 趁着对ssp知识点的理解还在&#xff0c;开始ccsp的考证之路&#xff0c;文章结构还是按照cissp备考篇的结构梳理。本次备考和cissp的离职在家备考不同&#xff0c;ccsp是在职利用非工…

2018年全国大学生数学建模竞赛A题高温服装设计(含word论文和源代码资源)

文章目录 一、部分题目二、部分论文三、部分Matlab源代码问题11 求解h1h22 已知h1h2求解温度分布 问题21 求解第二层最佳厚度 四、完整word版论文和源代码&#xff08;两种获取方式&#xff09; 一、部分题目 2018 年高教社杯全国大学生数学建模竞赛题目 A 题 高温作业专用服…

【C语言】字符/字符串+内存函数

目录 Ⅰ、字符函数和字符串函数 1 .strlen 2.strcpy 3.strcat 4.strcmp 5.strncpy 6.strncat 7.strncmp 8.strstr 9.strtok 10.strerror 11.字符函数 12. 字符转换函数 Ⅱ、内存函数 1 .memcpy 2.memmove 3.memcmp 4.memset Ⅰ、字符函数和字符串函数 1 .strlen 函数原型&…