Search code examples
c#pinvokefindwindow

Click A MessageBox button programmatically


As the title suggests, I'm trying to simulate a button-click in a MessageBox programmatically. I earlier tried to close the MessageBox by finding its handle via its caption, and applying WM_CLOSE or SC_CLOSE in SendMessage(). However, due to the presence of Yes/No buttons, that did not work (the X button is grayed out).

Now I'm trying to click the No button as follows -:

List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
     IntPtr Window_hWnd = CloseMessageBox.FindWindowByCaption("#32770", "LastQuestion"); //Could use null as the first argument too. "#32770" represents classname Dialog.
     CloseMessageBox.EnumChildWindows(Window_hWnd, (hWnd, lParam) =>
     {
          StringBuilder sb = new StringBuilder();
          foreach (var control in GCHandle.FromIntPtr(lParam).Target as List<IntPtr>)
          {
                CloseMessageBox.GetWindowText(control, sb, 250);
                if (sb.Equals("&No"))
                {
                    CloseMessageBox.PostMessage(hWnd, CloseMessageBox.MouseDown, 0, 0);
                    CloseMessageBox.PostMessage(hWnd, CloseMessageBox.MouseUp, 0, 0);
                }
          }
          return false;
     }, GCHandle.ToIntPtr(listHandle));

 }
 catch (Exception e)
 {
     MessageBox.Show(e.ToString());
 }
 finally
 {
     if (listHandle.IsAllocated)
        listHandle.Free();
 }

Having come this far on the advice of someone from IRC, I find that a few edits earlier, I was getting the button handle (only the "&Yes" button) but not all of them. He then suggested this approach, but the control List is not populated and hence it never goes inside the foreach. What do I do to remedy this?


Solution

  • Here you go.

    // A delegate which is used by EnumChildWindows to execute a callback method.
    public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
    
    // This method accepts a string which represents the title name of the window you're looking for the controls on.
    public static void ClickButtonLabeledNo(string windowTitle)
    {
        try
        {
            // Find the main window's handle by the title.
            var windowHWnd = FindWindowByCaption(IntPtr.Zero, windowTitle);
    
            // Loop though the child windows, and execute the EnumChildWindowsCallback method
            EnumChildWindows(windowHWnd, EnumChildWindowsCallback, IntPtr.Zero);
        }
        catch (Exception e)
        {
            MessageBox.Show(e.ToString());
        }
    }
    
    private static bool EnumChildWindowsCallback(IntPtr handle, IntPtr pointer)
    {
        const uint WM_LBUTTONDOWN = 0x0201;
        const uint WM_LBUTTONUP = 0x0202;
    
        var sb = new StringBuilder(256);
        // Get the control's text.
        GetWindowCaption(handle, sb, 256);
        var text = sb.ToString();
    
        // If the text on the control == &No send a left mouse click to the handle.
        if (text == @"&No")
        {
            PostMessage(handle, WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
            PostMessage(handle, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
        }
    
        return true;
    }
    
    [DllImport("user32")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
    
    [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
    private static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
    
    [DllImport("user32.dll", EntryPoint = "GetWindowText", CharSet = CharSet.Auto)]
    private static extern IntPtr GetWindowCaption(IntPtr hwnd, StringBuilder lpString, int maxCount);
    
    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);