2016-12-02 138 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