Simulate a mouse click in MSPaint

I have a console application that should draw a random image in MSPaint (mouse down β†’ let the cursor randomly draw something β†’ mouse up. This is what I still have (I added comments to the Main method to better understand what I want to achieve):

 [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))); } 

I got this click simulation code from here .

A click is simulated but draws nothing. Click seems to not work inside MSPaint. The cursor changes to the "cross" of MSPaint, but, as I said, the click does not work.

FindWindow sets hwndMain to 0. Changing the mspaint parameter to MSPaintApp does not change anything. The value of hwndMain remains 0.

If this helps, here is my OpenPaint() method:

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

What am I doing wrong?

+5
source share
2 answers

As promised, I myself tested it yesterday - frankly, my cursor just moved, but not out the window, and without any consequences - as I debugged, I saw that var hwndMain = FindWindow("mspaint ", null); has a value of 0 . I, although this is a problem, so I looked at another stackoverflow topic from which you got your code. I realized that the solution uses a different window name that they were looking for in FindWindow() , so I tried.

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

After changing the method method, this worked for me, although - after moving MsPaint the cursor was still in its original open position - you can think about it and ask a window for that position. Perhaps the name was changed using Win7 / 8/10?

Edit:

In Windows 10, the name for the paint seems to be changed - so, I think, you still have problems getting the handle to the right window - this was incorrectly proved by Hans Passant, who perfectly explained that the problem was with the handler (link below), One way to solve of this question is to get the handler from the process itself, and not to get it from FindWindow()

I suggest you modify your OpenPaint() as follows:

  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 description link for an explanation of why Thread.Sleep () is just a bad idea.

After this call:

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

Thus, you should be fine to get the right window handle, and your code should work, no matter what Microsoft called it in win10

-1
source
 IntPtr hwndMain = FindWindow("mspaint", null); 

This is not enough. A common mistake in pinvoke code, C # programmers tend to rely too much on an exception to jump off the screen and hit them in the face to tell them that something went wrong .. The .NET Framework does this unusually well. But this does not work the same way when you use C based api, for example winapi. C is a dinosaur language and generally does not support exceptions. This is still not the case. You will only get an exception if pinvoke failed, usually due to a bad [DllImport] declaration or missing DLL. It does not say when the function completed successfully, but returns a failure return code.

This makes it completely your own job to detect and report a failure. Just go to the MSDN documentation , it always tells you how the winapi function indicates failure. Not fully consistent, so you need to search, in which case FindWindow returns null when the window cannot be found. Therefore, always code it like this:

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

Do this for all other pinwaxes. Now you can go ahead, you are reliably getting an exception, instead of plowing with bad data. Which, as is often the case with bad data, is not bad enough. NULL is actually a valid window handle; the operating system assumes that you meant the desktop window. Uch. You are automating a completely wrong process.


Understanding why FindWindow () fails requires a bit of understanding, this is not very intuitive, but good reporting of errors is critical to getting it. The Process.Start () method only ensures that the program is running, it in no way waits for the initialization process to complete. And in this case, he does not wait until he creates the main window. Thus, the call to FindWindow () is executed after about a couple of tens of milliseconds too soon. It’s an urgent puzzle, as it works great when debugging and executing code once.

You may recognize this kind of failure; it is a mistake in the streaming race. The most vile kind of programming errors. Notorious for failing consistently and very difficult to debug, because races are time-dependent.

I hope you understand that the proposed solution in the accepted answer is also not enough. Arbitrarily adding Thread.Sleep (500) simply improves the odds you now expect long enough before calling FindWindow (). But how do you know that 500 is good enough? Is it always good?

No. Thread.Sleep () is never the right solution for streaming errors. If the user machine is slow or too busy with a lack of available raw RAM, then a few milliseconds turn into seconds. You have to deal with the worst case, and this is really the worst, only ~ 10 seconds - this is, in general, the minimum you need to consider when the car starts to beat. This is becoming very impractical.

Blocking this reliably is such a common need that the OS has a heuristic for it. You need to be heuristic and not call WaitOne () on the synchronization object, since the process itself does not interact at all. You can usually assume that the GUI program has progressed enough when it starts to request notifications. "Pumping up the message pipeline" on Windows. Heuristics also fell into the Process class. Fix:

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

I would refuse if I didn’t notice that for this you should use the built-in api. Called UI Automation, well wrapped in the System.Windows.Automation namespace. Takes into account all these unpleasant small details, such as slicing races and turning error codes into good exceptions. The most suitable tutorial is possible here .

+8
source

Source: https://habr.com/ru/post/1260709/


All Articles