Windows下模拟鼠标与键盘输入,及其在LabVIEW中的运用

前两天,我正在开开心心地撸着我的python,“领导”(没有其他合适的称谓了 )突然找到我说,他需要一个dll(动态链接库),功能是实现鼠标在指定的位置实现单击、双击、右击以及键盘输入,因为人家的软件是成品,没有可提供的接口使用。然后他就走了,剩下一脸懵逼的我。

跑起来

windows编程什么的我从来都没有接触过,确切的说,我连VS都没有用过。硬着头皮上,好在windows官方的支持文档十分充足,比如这个。最开始的时候,就算有完整的代码,我都不知道怎么让它跑起来,最后还求助了我室友,要不说我就服我的室友呢。果然很快就解决了“跑起来”的问题。在VS新建VC++桌面应用程序,然后代码粘贴,关键的是我们需要在“解决方案资源管理器”右键->属性->常规->公共语言运行时支持->添加公共语言运行时支持。然后我们就能在“引用”中的框架下顺利添加我们需要的依赖框架。然后程序就能顺利的跑起来了。如果要跑出效果,我们还要祭出spy++大杀器:在“工具”->spy++->搜索->查找窗口,我们就通过移动那个“瞄准器”来获得我们关心的窗口的“类名”和“名称”了,这方便我们在编程针对关心的窗口进行编程,比如针对FindWindow()这种函数,spy++灰常好用。

跑向目的地

我们已经通过官方的example知道怎么跑起来了,现在我们要让程序跑出我们需要的效果。但其实本小节和上小节没什么太大的关系。因为上一节的example是VC++的,这一节我们用的C#。

怎么说呢?c#这东西很像c艹,又很像java,刚用起来的时候有点怪怪的感觉。windows中c#编程,我们需要先指明运用的命名空间,也就是通过指明框架中各种命名空间来达到引用的效果,有点类似于c++中的include效果。当需要使用链接库中函数的时候,我们需要先声明,但声明的形式应与链接库中函数的相同。具体如下

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int SetCursorPos(int x, int y);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
[DllImport("User32.dll")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,IntPtr hwndChildAfter,string lpszClass,string lpszWindows);

这里的private是指定函数为私有成员。这里的关键就是SetCursorPos和mouse_event两个函数,一个是设置鼠标的位置,一个是产生鼠标事件。网上有很多光于两个函数的释义,这里就不过多说明了。然后,我这里给出整个有效的代码,其中包含了一些其他的窗口函数,将来对windows下窗口编程也许用得着,就mark一下吧:

using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;

namespace SimulateKeyPress
{
    class Form1 : Form
    {
        const int MOUSEEVENTF_MOVE = 0x0001;
        const int MOUSEEVENTF_LEFTDOWN = 0x0002; 
        const int MOUSEEVENTF_LEFTUP = 0x0004; 
        const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
        const int MOUSEEVENTF_RIGHTUP = 0x0010; 
        const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
        const int MOUSEEVENTF_MIDDLEUP = 0x0040; 
        const int MOUSEEVENTF_ABSOLUTE = 0x8000;

        private Button button1 = new Button();
        private Button button2 = new Button();
        private TextBox textbox1 = new TextBox();
        private TextBox textbox2 = new TextBox();
        private Cursor cursor = new Cursor(Cursor.Current.Handle);
        private Point ptold;
        private const int WM_SETTEXT = 0x000C;
        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new Form1());
        }

        public Form1()
        {
            button1.Location = new Point(10, 10);
            button1.TabIndex = 0;
            button1.Text = "button1";
            button1.AutoSize = true;
            button1.MouseUp += new MouseEventHandler(button1_Click);

            button2.Location = new Point(10, 40);
            button2.TabIndex = 1;
            button2.Text = "button2";
            button2.AutoSize = true;
            button2.MouseUp += new MouseEventHandler(button2_Click);

            
            textbox1.Location = new Point(10, 70);
            textbox1.TabIndex = 3;  //set the tabindex greater than the textbox2
            textbox1.Text = "textbox1";
            textbox1.AutoSize = true;

            textbox2.Location = new Point(10, 100);
            textbox2.TabIndex = 2;
            textbox2.Text = "textbox2";
            textbox2.AutoSize = true;
            //textbox2.MouseUp += new MouseEventHandler(button2_Click);

            this.DoubleClick += new EventHandler(Form1_DoubleClick);
            this.Controls.Add(button1);
            this.Controls.Add(button2);
            this.Controls.Add(textbox1);
            this.Controls.Add(textbox2);

        }

        // Get a handle to an application window.
        [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
        public static extern IntPtr FindWindow(string lpClassName,string lpWindowName);
        [DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
        public static extern IntPtr GetWindow(IntPtr hwndParent, int uCmd);

        //[DllImport("USER32.DLL")]
        //public static extern bool EnumChildWindows(IntPtr hWndParent, bool lpEnumFunc, string lParam);

        // Activate an application window.
        [DllImport("User32.dll")]
        private static extern IntPtr FindWindowEx(IntPtr hwndParent,IntPtr hwndChildAfter,string lpszClass,string lpszWindows);
        [DllImport("User32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
        [DllImport("USER32.DLL", CharSet = CharSet.Auto, SetLastError = false)]
        public static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, string lParam);
        [DllImport("USER32.DLL")]
        public static extern Int32 SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetCursorPos(int x, int y);
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool GetCursorPos(Point lpPoint);

        // Send a series of key presses to the Calculator application.
        private void button1_Click(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            { 
                MessageBox.Show("button1_Left");
                SetCursorPos(100, 120);
                mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                Thread.Sleep(1000);
                SendKeys.SendWait("123");
            }
            else if (e.Button == MouseButtons.Right)
                MessageBox.Show("button1_Right");

            /*
            IntPtr myHwnd = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r9_ad1", null);

            if (myHwnd != IntPtr.Zero)
            {
                IntPtr myEditHwnd = FindWindowEx(myHwnd, IntPtr.Zero,null, "button1");
                if (myEditHwnd != IntPtr.Zero)
                {
                    //SendMessage(myEditHwnd, 0x0021, IntPtr.Zero, null);
                    //IntPtr myEditHwnd2 = GetWindow(myEditHwnd, 2);//the number reprent the Cmd GW_HWNDNEXT
                    //if(myEditHwnd2 != IntPtr.Zero)
                    //{
                        //SendMessage(myEditHwnd2, 0x0021, IntPtr.Zero, null);
                    //}
                }
                else MessageBox.Show("not find the button");
            }
           else MessageBox.Show("not find the window");
 
            // Get a handle to the Calculator application. The window class
            // and window name were obtained using the Spy++ tool.
            IntPtr windowHandl = FindWindow("CalcFrame", "Calculator");
            // Verify that Calculator is a running process.
            if (windowHandl == IntPtr.Zero)
            {
                //MessageBox.Show("Calculator is not running.");
                return;
            }
            // Make Calculator the foreground application and send it 
            // a set of calculations.
            SetForegroundWindow(windowHandl);
            SendKeys.SendWait("123");
            */
        }

        MouseEventArgs fake_event;
        private void button2_Click(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                fake_event = new MouseEventArgs(MouseButtons.Left, 1, 0, 0, 0);
            else if (e.Button == MouseButtons.Right)
                fake_event = new MouseEventArgs(MouseButtons.Right, 1, 0, 0, 0);

            //IntPtr myTestHandle = new IntPtr(0x004506F8);
            //HandleRef hrefHwndTarget = new HandleRef(null, myHwnd);
            //SendMessage(hrefHwndTarget, WM_SETTEXT, IntPtr.Zero, "mmp");

            IntPtr myHwnd = FindWindow("WindowsForms10.Window.8.app.0.141b42a_r9_ad1", null);

            if (myHwnd != IntPtr.Zero)
            {
                IntPtr myEditHwnd = FindWindowEx(myHwnd, IntPtr.Zero, "WindowsForms10.EDIT.app.0.141b42a_r9_ad1", null);
                if (myEditHwnd != IntPtr.Zero)
                {
                    SendMessage(myEditHwnd, WM_SETTEXT, IntPtr.Zero, "mmp");
                    IntPtr myEditHwnd2 = GetWindow(myEditHwnd, 2);//the number reprent the Cmd GW_HWNDNEXT
                    if(myEditHwnd2 != IntPtr.Zero)
                    {
                        SendMessage(myEditHwnd2, WM_SETTEXT, IntPtr.Zero, "mmp");
                    }
                }
                else MessageBox.Show("not find the textbox");
            }
           else MessageBox.Show("not find the window");
           button1_Click(this, fake_event);

        }
        // Send a key to the button when the user double-clicks anywhere 
        // on the form.
        private void Form1_DoubleClick(object sender, EventArgs e)
        {
            // Send the enter key to the button, which raises the click 
            // event for the button. This works because the tab stop of 
            // the button is 0.
            SendKeys.Send("{ENTER}");
        }
    }
}

单击button1,将给出提示信息,其后,鼠标自动移至(100,120)的位置,然后双击。然后睡眠100ms,最后输入123。如果我们在这个位置是一个txt文件,那么这个demo就会打开txt,然后输入123。到这里,我们就基本完成了功能。

LabVIEW调用

虽然在窗口编程下,我们达到了效果。但是要实现最最最开始的业务需求,我们应该编译成dll,并在LabVIEW中调用成功才算数。VS->文件->新建->项目->.NET类库,输入下面的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace CTRL
{
    public class Ms_ctrl
    {
        const int MOUSEEVENTF_MOVE = 0x0001;
        const int MOUSEEVENTF_LEFTDOWN = 0x0002;
        const int MOUSEEVENTF_LEFTUP = 0x0004;
        const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
        const int MOUSEEVENTF_RIGHTUP = 0x0010;
        const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
        const int MOUSEEVENTF_MIDDLEUP = 0x0040;
        const int MOUSEEVENTF_ABSOLUTE = 0x8000;

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetCursorPos(int x, int y);
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

        public int Input_xyc(int x, int y, string c)
        {
            SetCursorPos(x, y);
            mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            mouse_event(MOUSEEVENTF_LEFTUP | MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
            Thread.Sleep(1000);
            SendKeys.SendWait(c);
            SendKeys.SendWait("{ENTER}");
            return (x+y);
        }
    }
}

这里我们的return类型故意写成int类型,是为了检验效果,最好的应该是bool类型。然后生成->重新生成解决方案。我们就在相应的文件下生成了dll。

然后我们打开LabVIEW->创建项目->VI->前面板->“添加两个数字输入控件,一个字符串输入控件,一个数字显示控件”->后面板->互连接口-> .NET->构造器节点->浏览到我们刚才生成的dll->确定->右键->创建->***类的方法->找到我们需要的函数(在这里是Input_xyc(int x, int y, string c))->将两者的引用关系连线->数字输入、显示控件和字符串控件连线,然后我们输入合适的数字和字符串,点击运行,就能看到效果了。

当然还是在相应的坐标出双击,然后输入字符串。最后显示控件为(x+y)。

吐槽

windows的编程还真是博大精深,分为API和MSDN两大类,c++API是更底层的借口函数,更灵活,MSDN是他们的高级封装,使用更简便。但往往很多时候,我找到了一个好用的函数,却发现他是另一个框架下的,我的内心是是跟拒绝的。

刚开始的,我开始浏览文档的时候,一会儿c++一会儿C#一会儿API一会儿MSDN,脑壳都给我整痛了。不过说到底,还是自己没有理顺理好。其实,这个解决方案我最早还用VC++的API写了一下,,发现并不是很方便。有些控件的handle是不容易获得的,在windows的消息机制下就不好处理了。

最后的最后,以上的所有文字都是我看windows编程两三天得出的心得感受,好不夸张地说,其中存在非常非常多的错误,望各位读至此的朋友不吝指正,万望海涵。

发表评论

电子邮件地址不会被公开。 必填项已用*标注