Search code examples
c#wpfwinformsmouseup

WindowsForm MouseUp fires twice when using Process.Start()


In my Windows Forms Application I have to open a new explorer window with a specific folder if one of the items is clicked. I am listening for the MouseUp event (because i already have some hit detection for which i need the click coordinates) now if i open a new explorer window by

private void listView1_MouseUp(object sender, MouseEventArgs e)
    {
        Process.Start(@"C:\");
    }

it opens the explorer window twice. It has definitly something to do with the Process.Start(@"C:\"); because when i switch the line to a normal console output it is just executed once.

Is there some way to mark the event as already handled or just ignore the second execution?


Solution

  • This is a re-entrancy bug, very similar to the kind of bugs you get when you use DoEvents() in your code. But it this case the bug is built into the OS. Instrumental for this problem is Explorer, it has a very unusual activation mode in this code. Something you can see when you look at the return value of Process.Start(), it is null. Happens when Process.Start() does not actually start a process.

    You can see the bug in action by using a conditional break point that hits only the second time. With unmanaged debugging and the symbol server enabled, the call stack looks like this:

    WindowsFormsApp1.exe!WindowsFormsApp1.Form1.listView1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) Line 19 C#
    System.Windows.Forms.dll!System.Windows.Forms.Control.OnMouseUp(System.Windows.Forms.MouseEventArgs e) Line 9140    C#
    System.Windows.Forms.dll!System.Windows.Forms.ListView.WndProc(ref System.Windows.Forms.Message m) Line 6298    C#
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Line 14236  C#
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Line 14291    C#
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) Line 780 C#
    [Native to Managed Transition]  
    user32.dll!__InternalCallWinProc@20()  Unknown
    user32.dll!UserCallWinProcCheckWow()    Unknown
    user32.dll!CallWindowProcW()    Unknown
    comctl32.dll!_CallNextSubclassProc@20()    Unknown
    comctl32.dll!_CallNextSubclassProc@20()    Unknown
    comctl32.dll!_MasterSubclassProc@16()  Unknown
    user32.dll!__InternalCallWinProc@20()  Unknown
    user32.dll!UserCallWinProcCheckWow()    Unknown
    user32.dll!DispatchMessageWorker()  Unknown
    user32.dll!_DispatchMessageW@4()   Unknown
    shell32.dll!SHProcessMessagesUntilEventsEx(struct HWND__ *,void * *,unsigned long,unsigned long,unsigned long,unsigned long)    Unknown
    shell32.dll!CShellExecute::_RunThreadMaybeWait(bool)    Unknown
    shell32.dll!CShellExecute::ExecuteNormal(struct _SHELLEXECUTEINFOW *)   Unknown
    shell32.dll!ShellExecuteNormal(struct _SHELLEXECUTEINFOW *) Unknown
    shell32.dll!_ShellExecuteExW@4()   Unknown
    System.ni.dll!71db9903()    Unknown
    [Frames below may be incorrect and/or missing, native debugger attempting to walk managed call stack]   
    [Managed to Native Transition]  
    System.dll!System.Diagnostics.ShellExecuteHelper.ShellExecuteFunction() Unknown
    System.dll!System.Diagnostics.ShellExecuteHelper.ShellExecuteOnSTAThread()  Unknown
    System.dll!System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo startInfo)    Unknown
    System.dll!System.Diagnostics.Process.Start()   Unknown
    System.dll!System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo startInfo)  Unknown
    System.dll!System.Diagnostics.Process.Start(string fileName)    Unknown
    WindowsFormsApp1.exe!WindowsFormsApp1.Form1.listView1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) Line 19 C#
    

    It is the SHProcessMessagesUntilEventsEx() function that is the evil-doer, the long way to spell "DoEvents" and implements the "maybe wait", it causes the dispatcher loop of your Winforms app to be re-entered. Detecting the MouseUp condition again and re-triggering the event handler.

    You can't fix the OS but the workaround is the universal one for event handlers that cause re-entrancy problems, you can cleanly delay the tricky code with BeginInvoke() so that it runs immediately after the event is handled. Like this:

    private void listView1_MouseUp(object sender, MouseEventArgs e) {
        this.BeginInvoke(new Action(() => {
            Process.Start(@"C:\");
        }));
    }