Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 862
MatzeBrei Avatar asked Jan 04 '23 05:01

MatzeBrei


1 Answers

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:\");
    }));
}
like image 186
Hans Passant Avatar answered Jan 09 '23 20:01

Hans Passant