4

MessageBox.Show shows a window, and until the MessageBox window is not closed, clicking the main app will still let the MessageBox window have focus and make the MessageBox window flash. Same behavior is with the TaskDialog API, Form.ShowDialog(IWin32Window) and Window.ShowDialog().

How can I do this for a different process like Process.Start("notepad.exe")?

Elmo
  • 6,409
  • 16
  • 72
  • 140
  • As far as I know, it's not possible because the process that you start can be anything and will not always represent a window. however you can use Process.WaitForExit and Process.HasExited to find out if the process is still running (and you can block your app from doing anything until the called process has been stopped). – Complexity May 12 '14 at 08:38
  • http://social.msdn.microsoft.com/Forums/en-US/b586325b-d61f-4e79-a18d-48894304efe4/exe-as-modal-dialog-using-processstartinfo-in-c have you tried something like this? – Mo Patel May 12 '14 at 08:41
  • @MPatel That blocks the GUI thread, because `WaitForExit()` just makes the thread sleep. Not the behavior the MessageBox provides. Clicking the main app will make nothing happen. – Elmo May 12 '14 at 08:42
  • Indeed, but it doesn't mimic the flash behaviour. – Complexity May 12 '14 at 08:43
  • I hope finally I got it, check my new answer. – Sriram Sakthivel May 12 '14 at 15:58
  • @Complexity Some grotty hack makes [everything possible](http://stackoverflow.com/a/23613632/2530848) :-D – Sriram Sakthivel May 12 '14 at 16:05

4 Answers4

4

This hack may do the trick. but be sure you question yourself twice before using this.

private void DoEvil()
{
    var windowField = typeof(Control).GetField("window", BindingFlags.Instance |
                            BindingFlags.NonPublic);

    Form notepad = new Form();
    NativeWindow window = (NativeWindow)windowField.GetValue(notepad);
    var process = Process.Start("notepad.exe");
    process.EnableRaisingEvents = true;

    while (process.MainWindowHandle == IntPtr.Zero)
    {
        Thread.Sleep(1);
    }

    window.AssignHandle(process.MainWindowHandle);
    Control.CheckForIllegalCrossThreadCalls = false;

    EventHandler handler = (s, ev) => notepad.DialogResult = DialogResult.OK;
    process.Exited += handler;
    notepad.ShowDialog(this);
    process.Exited -= handler;
    Control.CheckForIllegalCrossThreadCalls = true;
}

Warning: Don't try this at home, office or anywhere :p

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • I question any code thrice before it enters production. =p – Elmo May 12 '14 at 16:07
  • Btw, evil notepads are popping up mysteriously even after I closed them. Notepad apocalypse! Seriously though, I suggest everyone not to use this code. – Elmo May 12 '14 at 16:21
  • For me it doesn't behave that way, may be you should check you call this evil code only once. Anyway I had fun, enjoyed your question +1 for you. and would you mind if I ask why this strange requirement? – Sriram Sakthivel May 12 '14 at 16:29
  • I am not running notepad using this, some CMD /K command. I am thinking of making a form just for this so that the CMD output is redirected to the form. I didn't understand your code fully though. – Elmo May 12 '14 at 16:35
1

Ok, so this is what I have come up with, not the greatest or most elegant way to do it nor does it contains any error handling but I think it works,

What I do in my code is create a dummy form (Form2) with a Panel control and assign the parent of EXE to the handle (InPtr) of the panel. I have removed to the border of the form to make it look as flush as possible but there is still a noticeable flicker between the EXE loading and the SetParent method being called.

Form1 Code

private void button4_Click(object sender, EventArgs e)
{
    using (var f2 = new Form2())
    {
        f2.ShowDialog(this);
    }
}

Form2 Code

public partial class Form2 : Form
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
    [DllImport("user32.dll")]
    static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);

    private readonly BackgroundWorker worker = new BackgroundWorker();
    private IntPtr mainHandle;
    private IntPtr processHandle;
    private Panel panel;

    public Form2()
    {
        InitializeComponent();
        worker.DoWork += worker_DoWork;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
        FormBorderStyle = FormBorderStyle.None;
        StartPosition = FormStartPosition.CenterParent;
        AddPanel();
    }

    private void AddPanel()
    {
        panel = new Panel {Dock = DockStyle.Fill};
        mainHandle = panel.Handle;
        Controls.Add(panel);
    }

    private void Form2_Load(object sender, EventArgs e)
    {
        worker.RunWorkerAsync();
    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Invoke((MethodInvoker) Close);
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        var process = Process.Start("cmd.exe", "/k echo echo");
        if (process != null)
        {
            while (process.MainWindowHandle == IntPtr.Zero)
            {
                // Add some sort of timeout here, infintite loops are bad!!!
            }

            processHandle = process.MainWindowHandle;

            // Get the size of the EXE window and apply it to this form.
            var size = GetSize(processHandle);
            Invoke((MethodInvoker) delegate { Size = new Size(size.Width, size.Height);});

            // Hook the parent of the EXE window to this form
            SetHandle(processHandle);

            // Make sure the windows is positions at location x = 0, y = 0 of this form
            SetWindowPos(processHandle, IntPtr.Zero, 0, 0, size.Width, size.Height, SetWindowPosFlags.SWP_ASYNCWINDOWPOS);

            // wait for the EXE to terminate
            process.WaitForExit();

            // Unhook the closed process window
            SetParent(processHandle, IntPtr.Zero);
        }
    }

    private void SetHandle(IntPtr ptr)
    {
        if (ptr != IntPtr.Zero)
            SetParent(processHandle, mainHandle);
    }

    private static Size GetSize(IntPtr hWnd)
    {
        RECT pRect;
        var size = new Size();
        GetWindowRect(hWnd, out pRect);
        size.Width = pRect.Right - pRect.Left;
        size.Height = pRect.Bottom - pRect.Top;
        return size;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [Flags]
    private enum SetWindowPosFlags : uint
    {
        SWP_ASYNCWINDOWPOS = 0x4000,
        SWP_DEFERERASE = 0x2000,
        SWP_DRAWFRAME = 0x0020,
        SWP_FRAMECHANGED = 0x0020,
        SWP_HIDEWINDOW = 0x0080,
        SWP_NOACTIVATE = 0x0010,
        SWP_NOCOPYBITS = 0x0100,
        SWP_NOMOVE = 0x0002,
        SWP_NOOWNERZORDER = 0x0200,
        SWP_NOREDRAW = 0x0008,
        SWP_NOREPOSITION = 0x0200,
        SWP_NOSENDCHANGING = 0x0400,
        SWP_NOSIZE = 0x0001,
        SWP_NOZORDER = 0x0004,
        SWP_SHOWWINDOW = 0x0040,
    }
}
Mo Patel
  • 2,321
  • 4
  • 22
  • 37
  • Thanks for the effort but that's crazy `:)` (try resizing/moving the child window). There must be something simple... something similar to how ShowDialog works – Elmo May 12 '14 at 14:03
  • @Zuck, agreed but I don't know what and where it is yet. The moment I find it i'll update the code – Mo Patel May 12 '14 at 14:04
  • I am trying to understand the ShowDialog function in dotPeek/Reflector/JustDecompile. – Elmo May 12 '14 at 14:06
0

Look here: Wait till a process ends

In short, you can either use "process.WaitForExit();" to pause execution till the process exits, but I don't think this will flash the window or auto-focus to that process.

If you want the fancier UI stuff you're going to have to do something like this:

while (!process.HasExited)
{
    //update UI
}

For switching the focus to another process, this should get you started.

EDIT: Here's more info on flashing the window.

Community
  • 1
  • 1
Dante
  • 69
  • 2
-2

Ok, here's a solution that might work:

Start the process with the following code:

Process p = new Process("cmd");
p.Start();
p.WaitForExit();

Now, in the event handler for activating your application (active), set the focus on the process again. You can use process.HasExited to verify that you process is still running or not.

Note: I haven't tested it, but I think it should bring you close.

Complexity
  • 5,682
  • 6
  • 41
  • 84