5

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?

MatzeBrei
  • 63
  • 6
  • Try stepping through the code and see if it runs the code twice? – Bali C Apr 04 '17 at 14:36
  • Hi Bali C, just tried that. If i just have a Console.WriteLine(e.Button); i get the output twice. If i set a breakpoint inside of the method than it is only executed once. Perhaps the focus shifts to the newly opened explorer window and back really fast? But that is just speculation. – MatzeBrei Apr 04 '17 at 14:42
  • Hmm, strange. Have you tried using the MouseClick event, rather than MouseUp, just a thought... – Bali C Apr 04 '17 at 14:58
  • It looks like it might be a bug with the multiselect property https://social.msdn.microsoft.com/Forums/windows/en-US/18766b26-a86e-4da2-871f-5eff2a261a78/listview-control-mouseup-event-fires-twice-once-mouseup-and-then-on-mousehover?forum=winforms – Bali C Apr 04 '17 at 15:00
  • Yes, with the MouseClick it does work and only triggeres once. But in the real application i would really like need to use the MouseUp because of some stuff thats happening when users click and do stuff inside the listbox. But that would be the fallback solution if there is no other way. – MatzeBrei Apr 04 '17 at 15:01

2 Answers2

12

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:\");
    }));
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks a lot for the great in depth explanation and the working solution! The code is working as intended now and i learned something ;-) – MatzeBrei Apr 05 '17 at 10:34
1

After hours of debugging I found indeed out the MouseUp() is called twice and found this article. It is easy to reproduce.

Create a WinForm project, this will give you Form1. Create a custom control with OnMouseUp override (see code). Put the control on the form, set the background color to any color, press F5. Click the control, nothing happens. Either drag the form, or move the focus to another application, and you get a 2nd MouseUp event for free :(

Invoking is indeed the solution.

class CustomControl : Control
{
    public static int mouseUps = 0;
    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (mouseUps == 1)
            System.Diagnostics.Debugger.Break();

        mouseUps++;
        // Start our own message loop
        while (true)
            Program.ProcessWindowsMessage();

        base.OnMouseUp(e); // <- breakpoint here, will not hit on first mouseUp

    }
}

public static void ProcessWindowsMessage()
{
    if (DllImport.GetMessage(out _windowsMessage, IntPtr.Zero, 0, 0) != 0)
    {
        DllImport.TranslateMessage(ref _windowsMessage);
        DllImport.DispatchMessage(ref _windowsMessage);
    }
}
Rene
  • 59
  • 3