2016-12-02 8 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의 "십자가"로 바뀌지 만 언급 한대로 ... 클릭하지 않습니다 ' 작동하지 않는 것 같습니다.

FindWindowhwndMain의 값을 0으로 설정합니다. mspaintMSPaintApp으로 변경해도 아무 것도 변경되지 않습니다. hwndMain의 값은 도움이된다면 여기에, 0

을 유지 내 OpenPaint() 방법 :

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

내가 잘못 뭐하는 거지?

+0

1 단계 : 그림판 이외의 다른 응용 프로그램에서 더 잘 작동하는지보고 한 다음보고하십시오! – TaW

+0

안녕하세요! 나는이 질문을 좋아하고 그것에 대해 궁금해합니다. 이미 답변을 찾았습니까? 아니면 아직도 열려 있습니까? 아직 답을 찾지 못했다면, 오늘 저녁에 직접 해보겠습니다. – TripleEEE

+0

@ TripleEEE 아직 답변을 찾지 못했습니다. 내가 기분이 좋지 않기 때문에 나는 확인할 수 없다. –

답변

-1

약속대로 나는 어제 나 자신을 테스트했다. 내 커서가 방금 움직 였지만 아무런 영향도 미치지 않았다. 디버그에서 나는 var hwndMain = FindWindow("mspaint ", null);이 값인 0임을 알았다. 나는 이것이 문제가되어야하지만 다른 stackoverflow 주제를 살펴 보았다. 코드를 가져왔다. 나는 해결책이 FindWindow()에 그들이 보았던 다른 창 이름을 사용하고 있다는 것을 알았다. 그래서 나는 시도했다.

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

가 날 위해 일한 methodCall을 변경 후 -하지만 - 당신은 그것에 대해 생각하고 위치가 어쩌면의에 대한 창을 요청 할 수 있습니다 - 커서가 원래 열려있는 위치에 아직도 거기 MSPAINT를 이동 한 후. 이름이 Win7/8/10에서 변경 되었습니까?

편집 : 윈도우 10에

페인트의 이름이 변경 될 것으로 보인다 - 그래서 당신은 여전히 ​​오른쪽 창 핸들을 얻는에 문제가있는 것 같아요 -이 멋지게 무슨 설명 한스 옆모습에 의해 잘못 입증되었다 핸들러 문제 (아래 링크). Hans Passant decription

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

링크를 explaination에 대한 이유를 스레드 :이 문제를 해결하는 한 가지 방법은 당신이 당신의 OpenPaint() 다음과 같이 변경 제안 FindWindow()

에서 그것을 얻는 대신 프로세스 자체에서 당신 핸들러를 취득하는 것입니다. 수면은 나쁜 생각입니다. 호출에 의해 이어

:

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

오른쪽 windowhandle를 점점 잘해야하고, 코드에 상관없이 마이크로 소프트가

+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를 여는 메소드 - 아마도 약간의 오류가있을 수 있습니다. Btw : FindWindow가 핸들을 다시 얻었는지 또는 0인지 여부를 디버그 했습니까? – TripleEEE

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

win10에서 호출하는 방법, 작업해서는 안이 방법은 그 충분하지 않습니다.일반적인 코드 실수로 C# 프로그래머는 예외를 너무 많이 사용하여 화면을 뛰어 넘고 얼굴을 때리면 뭔가 잘못되었다고 말하는 경향이 있습니다. .NET Framework는 일반적으로 잘 수행합니다. 하지만 이 아니라 Winapi와 같은 C 언어를 기반으로하는 API를 사용할 때와 같은 방식으로 작동합니다. C는 공룡 언어이며 예외를 전혀 지원하지 않았습니다. 아직도 그렇지 않습니다. 일반적으로 잘못된 [DllImport] 선언 또는 DLL 누락으로 인해 pinvoke 배관이 실패했을 때만 예외가 발생합니다. 함수가 성공적으로 실행되었지만 실패 반환 코드를 반환하면이를 알려주지 않습니다.

이렇게하면 실패를 감지하고보고하는 일은 전적으로 귀하의 일입니다. MSDN documentation으로 가면 winapi 함수가 어떻게 잘못인지를 알 수 있습니다. 완전하게 일관성이 없기 때문에보아야합니다.이 경우 FindWindow는 창을 찾을 수 없을 때 null을 반환합니다. 그래서 항상 다음과 같이 코딩하십시오 :

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

다른 모든 pinvokes에도이 작업을 수행하십시오. 이제는 앞을 내다 볼 수 있습니다. 잘못된 데이터를 사용하지 않고 안정적으로 예외를 얻을 수 있습니다. 너무 자주 나쁜 데이터가있는 경우처럼 꽤 나쁘지 않습니다. NULL은 실제로 유효한 윈도우 핸들이며, OS는 데스크탑 창을 의미한다고 가정합니다. 아야. 완전히 잘못된 프로세스를 자동화하고 있습니다. 를 FindWindow()는 통찰력의 비트를 필요로 않습니다 실패하는 이유를 이해


, 그것은 매우 직관적 아니지만, 좋은 오류보고는 거기에 도착하는 것이 중요하다. Process.Start() 메서드는 프로그램이 시작되었는지 만 확인하고 프로세스가 초기화를 완료 할 때까지 기다리지 않습니다. 그리고이 경우 메인 윈도우가 생성 될 때까지 기다리지 않습니다. 따라서 FindWindow() 호출은 너무 일찍 약 2 ~ 30 밀리 초 정도 실행됩니다. 코드를 디버깅하고 단일 단계 만 수행하면 문제가 없으므로 혼란 스러울 수 있습니다.

아마도 이런 종류의 사고를 인식 할 수 있습니다. 스레딩 레이스 버그입니다. 가장 이상한 종류의 프로그래밍 버그. 인종이 타이밍에 따라 다르기 때문에 지속적으로 오류를 일으키지 않고 디버그하기가 어렵 기 때문에 악명이 높습니다.

바라 건데 당신은 받아 들여진 대답에서 제안 된 해결책이 충분하지 않다는 것을 알기를 바랍니다. 임의로 Thread.Sleep (500)을 추가하면 FindWindow()를 호출하기 전에 충분히 기다릴 확률이 향상됩니다. 그러나 500이 충분하다는 것을 어떻게 알 수 있습니까? 항상으로 충분합니까?

아니요. Thread.Sleep()은 이 아닙니다. 스레딩 레이스 버그에 대한 올바른 해결책입니다. 사용자의 컴퓨터가 느리거나 사용할 수없는 매핑되지 않은 RAM이 너무 많이로드 된 경우 몇 밀리 초가 초로 변합니다. 최악의 경우을 처리해야합니다. 실제로 최악의 경우는 일반적으로 시스템이 스래 싱을 시작할 때 고려해야 할 최소 10 초입니다. 그것은 매우 비현실적입니다.

이 interlocking this 은 안정적으로입니다. OS에는 휴리스틱이 필요합니다. 프로세스 자체가 전혀 협력하지 않기 때문에 동기화 객체에서 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입니다.

관련 문제