1.3.1 基于发送消息的模拟

通过前面的介绍,我们已经明白,Windows的应用程序是基于消息机制的,对于鼠标和键盘的操作也会被系统转化为相应的消息。首先来学习如何通过发送消息进行鼠标和键盘的模拟操作。

1.鼠标、键盘按键常用的消息

无论是鼠标指针(或光标)的移动、单击,还是键盘的按键,通常在Windows应用程序中都会转换成相应的消息。在操作鼠标时,使用最多的是移动鼠标和单击鼠标键。比如,在教新手使用计算机时会告诉他,将鼠标指针(或光标)移动到“我的电脑”上,然后单击鼠标右键,在弹出的快捷菜单中用鼠标左键单击选择“属性”对话框。当移动鼠标光标的时候,系统中对应的消息是WM_MOUSEMOVE消息,按下鼠标左键时的对应的消息是WM_LBUTTONDOWN,释放鼠标左键时,对应的消息是WM_LBUTTONUP。在系统中,鼠标的消息有很多。在MSDN中查询到的鼠标消息如图1-8所示。

图1-8 鼠标相关消息

同样,在系统中也定义了键盘的按下与抬起的消息。键盘按下的消息是WM_KEY DOWN,与之对应的键盘抬起的消息是WM_KEYUP。除了这两个消息外,还有一个消息是比较常用的,这个消息在前面介绍消息循环时提到过,就是WM_CHAR消息。键盘的消息相对于鼠标要少很多,在MSDN中查询到的键盘消息如图1-9所示。

图1-9 键盘相关消息

2.PostMessage()函数对键盘按键的模拟

通过前面的介绍,我们已经知道,PostMessage()和SendMessage()这两个函数可以对指定的窗口发送消息。既然鼠标和键盘按键的操作被系统转换为相应的消息,那么就可以使用PostMessage()和SendMessage()通过按鼠标和键盘按键发送的消息来模拟它们的操作。对于模拟键盘按键消息,最好使用PostMessage()而不要使用SendMessage()。在很多情况下,SendMessage()是不会成功的。

现在编写一个简单的小工具,它通过PostMessage()函数模拟键盘发送(发送F5键的消息来模拟网页的刷新)的信息来刷新网页。首先打开VC6.0,创建一个MFC对话框工程,按照图1-10所示设置界面。

图1-10 模拟键盘刷新网页界面布局

按照图1-10所示的界面进行布局,然后为“开始”按钮设置控件变量。这个小程序在“IE浏览器标题”处输入要刷新的页面的标题,在“刷新频率”处输入一个刷新的时间间隔,单位是秒。

当了解程序的功能并且将程序的界面布置好以后,就可以开始编写程序的代码了。程序的代码分为两部分,第一部分是程序要处理“开始”按钮的事件,第二部分是要按照指定的时间间隔对指定的浏览器发送按F5键的消息来刷新网页。

首先来编写响应“开始”按钮事件的代码,双击“开始”按钮来编写它的响应事件。代码如下:

          void CKeyBoardDlg::OnBtnStart()
          {
              // TODO: Add your control notification handler code here
              CString strBtn;
              int nInterval = 0;

              // 获取输入的浏览器标题
              GetDlgItemText(IDC_EDIT_CAPTION, m_StrCaption);
              // 获取输入的刷新频率
              nInterval = GetDlgItemInt(IDC_EDIT_INTERVAL, FALSE, TRUE);

              // 判断输入的值是否非法
              if ( m_StrCaption ==""|| nInterval == 0 )
              {
                return ;
              }

              // 获取按钮的标题
              m_Start.GetWindowText(strBtn);
              if ( strBtn == "开始" )
              {
                // 设置定时器
                SetTimer(1, nInterval * 1000, NULL);
                m_Start.SetWindowText("停止");
                GetDlgItem(IDC_EDIT_CAPTION)->EnableWindow(FALSE);
                GetDlgItem(IDC_EDIT_INTERVAL)->EnableWindow(FALSE);
              }
              else
              {
                // 结束定时器
                KillTimer(1);
                m_Start.SetWindowText("开始");
                GetDlgItem(IDC_EDIT_CAPTION)->EnableWindow(TRUE);
                GetDlgItem(IDC_EDIT_INTERVAL)->EnableWindow(TRUE);
            }
          }

在代码中,首先判断按钮的文本,如果是“开始”,则通过SetTimer()函数设置一个定时器;如果按钮的文本不是“开始”,则通过KillTimer()函数关闭定时器。

这里的SetTimer()和KillTimer()是MFC中CWnd类的两个成员函数,不是API函数。很多MFC中的类成员函数和API函数的写法是一样的,但是它们还是有区别的。比较一下SetTimer()在MFC中的定义和API函数的定义的差别。

MFC中的定义如下:

        UINT SetTimer(
              UINT nIDEvent,
              UINT nElapse,
              void (CALLBACK EXPORT* lpfnTimer)(
                    HWND, UINT, UINT, DWORD) );

API函数的定义如下:

        UINT_PTR SetTimer(
          HWND hWnd,
          UINT_PTR nIDEvent,
          UINT uElapse,
          TIMERPROC lpTimerFunc
        );

从定义中可以看出,MFC中SetTimer()函数的定义比API中SetTimer()函数的定义少了一个参数,即HWND的窗口句柄的参数。在MFC中,窗口相关的成员函数都不需要指定窗口句柄,在MFC的内部已经维护了一个m_hWnd的句柄变量(如果想要查看或使用MFC内部维护的m_hWnd成员变量,可以直接使用它,也可以通过调用GetSafeHwnd()成员函数来得到它,推荐使用第二种方法)。

在按钮事件中添加定时器,那么定时器会按照指定的时间间隔进行相应的处理。定时器部分的代码如下:

        void CKeyBoardDlg::OnTimer(UINT nIDEvent)
        {
            // 在此处添加处理程序代码

            HWND hWnd = ::FindWindow(NULL, m_StrCaption.GetBuffer(0));
            // 发送键盘按下消息
            ::PostMessage(hWnd, WM_KEYDOWN, VK_F5, 1);
            Sleep(50);
            // 发送键盘抬起消息
            ::PostMessage(hWnd, WM_KEYUP, VK_F5, 1);

            CDialog::OnTimer(nIDEvent);
        }

关于定时器的处理非常简单,通过FindWindow()函数得到要刷新窗口的句柄,然后发送WM_KEYDOWN和WM_KEYUP消息来模拟键盘按键即可。其实在模拟的过程中,可以省去WM_KEYUP消息的发送,但是为了模拟效果更接近真实性,建议在模拟时将消息成对发送。

将写好的程序编译连接后运行起来看效果,在“IE浏览器标题”处输入浏览器的标题,这个标题可以通过Spy++获得,然后在“刷新频率”处输入1。然后单击“开始”按钮,观察浏览器每个1秒进行刷新一次。当单击“停止”按钮后,程序不再对浏览器进行刷新按键模拟。

到此,通过PostMessage()函数发送按F5键进行键盘按键模拟的程序就完成了。使用PostMessage()函数的好处是目标窗口可以在后台,而不需要窗口处于激活状态。可以将被刷新的浏览器最小化,然后运行刷新网页的小程序,在任务栏可以看到浏览器仍然在不断刷新。