2016-12-02 136 views
5

我有一个控制台应用程序,应该在MSPaint中绘制一个随机图片(鼠标向下 - >让光标随机画一些东西 - >鼠标向上。这是我迄今为止的内容(我向为了更好的理解Main方法我想实现):在MSPaint中模拟鼠标点击

[DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern void mouse_event(long dwFlags, uint dx, uint dy, long cButtons, long dwExtraInfo); 
private const int MOUSEEVENTF_LEFTDOWN = 0x201; 
private const int MOUSEEVENTF_LEFTUP = 0x202; 
private const uint MK_LBUTTON = 0x0001; 

public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr parameter); 

[DllImport("user32.dll", SetLastError = true)] 
static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 

[DllImport("user32.dll", SetLastError = true)] 
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle); 

[DllImport("user32.dll", CharSet = CharSet.Auto)] 
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); 

[DllImport("user32.dll", SetLastError = true)] 
public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam); 

static IntPtr childWindow; 

private static bool EnumWindow(IntPtr handle, IntPtr pointer) 
{ 
    childWindow = handle; 
    return false; 
} 

public static void Main(string[] args) 
{ 
    OpenPaint(); // Method that opens MSPaint 
    IntPtr hwndMain = FindWindow("mspaint", null); 
    IntPtr hwndView = FindWindowEx(hwndMain, IntPtr.Zero, "MSPaintView", null); 
    // Getting the child windows of MSPaintView because it seems that the class name of the child isn't constant 
    EnumChildWindows(hwndView, new EnumWindowsProc(EnumWindow), IntPtr.Zero); 
    Random random = new Random(); 
    Thread.Sleep(500); 

    // Simulate a left click without releasing it 
    SendMessage(childWindow, MOUSEEVENTF_LEFTDOWN, new IntPtr(MK_LBUTTON), CreateLParam(random.Next(10, 930), random.Next(150, 880))); 
    for (int counter = 0; counter < 50; counter++) 
    { 
     // Change the cursor position to a random point in the paint area 
     Cursor.Position = new Point(random.Next(10, 930), random.Next(150, 880)); 
     Thread.Sleep(100); 
    } 
    // Release the left click 
    SendMessage(childWindow, MOUSEEVENTF_LEFTUP, new IntPtr(MK_LBUTTON), CreateLParam(random.Next(10, 930), random.Next(150, 880))); 
} 

我得到的点击模拟这个代码here

的点击被模拟,但它并没有画什么它似乎。点击在MSPaint中不起作用,光标变为MSPaint的“十字”,但正如我所说的那样......点击不会“ t似乎工作。

FindWindow将值hwndMain设置为0.将参数mspaint更改为MSPaintApp不会改变任何内容。的hwndMain值保持0

如果有帮助,这是我的OpenPaint()方法:

private static void OpenPaint() 
{ 
    Process.process = new Process(); 
    process.StartInfo.FileName = "mspaint.exe"; 
    process.StartInfo.WindowStyle = "ProcessWindowStyle.Maximized; 
    process.Start(); 
} 

我在做什么错?

+0

第一步:尝试使用除Paint之外的其他应用程序的效果,然后报告! – TaW

+0

嗨!我喜欢这个问题,对此很好奇 - 你是否已经找到了答案或者这个问题仍然存在?如果你现在还没有找到答案,我会在今晚自己试试。 – TripleEEE

+0

@TripleEEE我还没有找到答案。我无法检查,因为我生病atm .. –

回答

-1

按照承诺,我昨天自己测试了一下 - 说实话,我的光标移动了,但没有在窗口中,也没有任何影响 - 就像我调试过的那样,我看到var hwndMain = FindWindow("mspaint ", null);的值为0。我虽然这必须是问题,所以我看了看另一个stackoverflow主题,你从你的代码。我意识到解决方案使用了他们在FindWindow()寻找的另一个窗口名 - 所以我确实尝试了。

var hwndMain = FindWindow("MSPaintApp", null); 

改变它的工作对我来说是methodcall后 - 尽管 - 你可能要考虑一下,并询问窗口为它的位置可能 - 移动MSPAINT有光标仍然在原来的打开位置后。 Win7/8/10可能会更改名称吗?

编辑:

在Windows 10油漆的名字似乎被改变 - 所以我猜你仍然有获得正确的窗口句柄的问题 - 这是由汉斯帕桑特,谁好听什么解释证明是错误的处理程序的问题(下面的链接)。要解决这个问题的一种方法是从FindWindow()

从过程本身让你处理程序,而不是得到它的,我建议你改变你的OpenPaint()这样的:

private IntPtr OpenPaint() 
{ 
    Process process = new Process(); 
    process.StartInfo.FileName = "mspaint.exe"; 
    process.StartInfo.WindowStyle = ProcessWindowStyle.Maximized; 
    process.Start(); 
    // As suggested by Thread Owner Thread.Sleep so we get no probs with the handle not set yet 
    //Thread.Sleep(500); - bad as suggested by @Hans Passant in his post below, 
    // a much better approach would be WaitForInputIdle() as he describes it in his post.   
    process.WaitForInputIdle(); 
    return process.MainWindowHandle; 
} 

链接Hans Passant decription的交代为什么线程。睡眠()是一个坏主意。

依次为通话:

IntPtr hwndMain = OpenPaint(); // Method that opens MSPaint 

这样,你应该罚款获得正确windowhandle,你的代码应该工作,无论微软是如何把它称为在win10

+0

我已经有过这样的事情,但之后它根本不工作。当使用'Console.WriteLine(process.ProcessName);'输出是“mspaint”。光标在画面中移动,但不会点击... –

+0

但是您不要求进程名称 - 您正在寻找windowName,这是不同的。 *如果lpWindowName参数不为NULL,则FindWindow调用GetWindowText函数来检索窗口名称以进行比较*请参阅:https://msdn.microsoft.com/de-de/library/windows/desktop/ms633499(v=vs。 85).aspx – TripleEEE

+0

@diiN_如果你提供'OpenPaint(); //打开MSPaint的方法 - 可能有一些错误。顺便说一句:你是否调试过FindWindow是否得到处理,或者它是否为零? – TripleEEE

8
IntPtr hwndMain = FindWindow("mspaint", null); 

那不够好。拼写代码中的常见错误,C#程序员倾向于完全依赖异常来跳出屏幕并将它们拍在脸上,以告诉他们出现了问题。 .NET框架确实做得很好。但是,而不是在使用基于C语言的API(如winapi)时的工作方式相同。 C是一种恐龙语言,根本不支持例外。它仍然没有。通常由于[DllImport]声明不正确或缺少DLL,您只会在通信管道失败时才会发生异常。当函数成功执行但不返回失败返回码时,它不会说出来。

这样做完全是您自己的工作来检测和报告失败。只要转到MSDN documentation,它总是会告诉你一个winapi函数如何表示一个不幸事件。不完全一致,所以您必须查看,在这种情况下,FindWindow在无法找到窗口时返回null。所以总是这样编码:

IntPtr hwndMain = FindWindow("mspaint", null); 
if (hwndMain == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(); 

对所有其他pinvokes也这样做。现在你可以超前了,你将可靠地得到一个异常,而不是随着糟糕的数据。对于不好的数据,情况经常是这样,还不够糟糕。 NULL实际上是一个有效的窗口句柄,操作系统将假定你的意思是桌面窗口。哎哟。你正在自动化完全错误的过程。


了解为什么FindWindow()失败需要一点洞察力,它不是非常直观,但良好的错误报告对于实现这一目标至关重要。 Process.Start()方法只确保程序启动,它不会以任何方式等待进程完成其初始化。在这种情况下,它不会等到它创建主窗口。所以FindWindow()调用过早地执行了几十毫秒。额外的困惑,因为它在你调试和单步执行代码时工作得很好。

也许你认识到这种不幸,它是一个线程比赛bug。最拙劣的编程错误。臭名昭着的不会造成一贯的失败并且很难调试,因为比赛依赖于时间。

希望您认识到所提出的解决方案在接受的答案中也不够好。任意添加Thread.Sleep(500)只会提高您在调用FindWindow()之前等待足够长的几率。但你怎么知道500就够了?这是总是不够好?

否Thread.Sleep()是从来没有线程比赛错误的正确解决方案。如果用户的机器速度很慢,或者负载太重,可用的未映射的内存不足,那么几毫秒就会变成几秒钟。您必须处理最坏情况,而且确实是最糟糕的情况,一般情况下,只有大约10秒钟时间,您需要考虑机器何时开始颠簸。这变得非常不切实际。

可靠地联锁这个是一种常见的需求,操作系统对它有启发式。因为进程本身根本不合作,所以需要成为启发式而不是对同步对象的WaitOne()调用。通常,您可以假设GUI程序在开始询问通知时已经充分发展。在Windows白话中“抽取消息循环”。这种启发式方法也将其引入Process类。修复:

private static void OpenPaint() 
{ 
    Process.process = new Process(); 
    process.StartInfo.FileName = "mspaint.exe"; 
    process.StartInfo.WindowStyle = "ProcessWindowStyle.Maximized; 
    process.Start(); 
    process.WaitForInputIdle();   // <=== NOTE: added 
} 

如果我没有指出你应该使用内置的api来做这件事,那我就会失职。称为UI自动化,包装在系统中。Windows.Automation命名空间。照顾所有那些令人讨厌的细节,比如线程竞赛和将错误代码转化为良好的例外。最相关的教程是probably here