Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange Bug with .NET 4.0/4.5 WinForms MenuStrip Stealing Focus

Tags:

c#

.net

winforms

We recently upgraded to VS 2012 which is accompanied by an upgrade to .NET Framework 4.5. This introduces a strange and pesky bug in connection with WinForms MenuStrip control. The same bug also occurs in applications targetting .NET Framework 4.0 as 4.5's installer obviously updates parts of 4.0 as well. Therefore, a perfectly working code pattern is now broken just because of the framework upgrade.

Problem description:
I have a Form1 with a MenuStrip. For one of the drop down items, the event handler opens another Form2 (not neccessarily a child, just another Form). If the user right-clicks into the new Form2 or one of its child controls and this triggers a Show() of a ContextMenuStrip, then the original Form1 pops into foreground again.
This happens independent of all previous other UI actions in Form2. One can resize, move, minimize, maximize Form2, switch between controls, enter text etc. Somehow MenuStrip of Form1 seems to remember that it caused opening of Form2 and grabs focus on first right-click.

I was experimenting with different approaches but was unable to find a workaround so far. The constellation is not uncommon and might affect lots of WinForms applications around. Therefore I decided to post it here since a viable solution might be of general interest. I would be very glad if someone knows a workaround or has at least some clues for me.

I was able to distill it's essence in the following code. It should be reproducible on any machine with .NET 4.5 installed and it occurs when targetting 4.0 and 4.5 each - but not on 3.5 or lower.

using System;
using System.Windows.Forms;

namespace RightClickProblem {
    static class Program {
        [STAThread]
        static void Main() {
            // Construct a MenuStrip with one item "Menu" with one dropdown-item "Popup"
            var mainMenu = new MenuStrip();
            var mainItem = new ToolStripMenuItem("Menu");
            mainItem.DropDownItems.Add(new ToolStripMenuItem("Popup", null, Popup));
            mainMenu.Items.Add(mainItem);

            // Create form with MenuStrip and Show
            var form1 = new Form();
            form1.Controls.Add(mainMenu);
            Application.Run(form1);
        }

        private static void Popup(object sender, EventArgs e) {
            // Create a form with a right click handler and show
            var form2 = new Form();
            var contextMenu = new ContextMenuStrip();
            contextMenu.Items.Add("Just an item...");
            form2.ContextMenuStrip = contextMenu;
            form2.Show();
            // Problem: contextMenu.Show() will give focus to form1
        }
    }
}

EDIT: I spent some time stepping through .NET Framework source code and found the root cause to be very likely in System.Windows.Forms.ToolStripManager. There, Microsoft is using a message filter to track window activation which is somehow incorrectly implemented for MenuStrip.
In the meantime I also found that Microsoft already adressed this issue in a hotfix (see http://support.microsoft.com/kb/2769674) and hopefully this will find its way into a future update of .NET Framework 4.5.

Unfortunately it is difficult to enforce this hotfix to be applied on all client machines. Therefore a viable workaround would still be greatly appreciated. I myself have been unable so far to find a practical solution...

EDIT#2: The original KB number is for Win8 but there is similar Hotfix for Win7 & Vista under KB 2756203. If somebody can use it, found the hotfix for download here: http://thehotfixshare.net/board/index.php?autocom=downloads&showfile=15569. We tested it and it really fixes the problem. If we find definitely no workaround soon, we'll go with the hotfix.

EDIT#3: Remarks to the accepted solution proposed by spajce
Obviously calling Show() on any ContextMenu will convince the original MenuStrip to forget about its claim on focus. This can be done in a way so that the dummy ContextMenu is not even shown on screen. I found the shortest and most easy to implement way to insert the following snippet in any Form's constructor:

public partial class Form1 : Form {
    public Form1() {
        InitializeComponent();

        using (var dummyMenu = new ContextMenuStrip()) {
            dummyMenu.Items.Add(new ToolStripMenuItem());
            dummyMenu.Show(Point.Empty);
        }
    }
}

So every Form that opens cleans up the corrupted state of the ToolStripMenu system. One might as well put this code in a static method like FormHelper.FixToolStripState() or put it in OnCreateControl(...) of a template Form and inherit all Forms from that (what is what we luckily do anyway).

like image 360
markus987 Avatar asked Dec 27 '12 16:12

markus987


1 Answers

this is my solution :)

 static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Construct a MenuStrip with one item "Menu" with one dropdown-item "Popup"
            var mainMenu = new MenuStrip();
            var mainItem = new ToolStripMenuItem("Menu");
            mainItem.DropDownItems.Add(new ToolStripMenuItem("Popup", null, Popup));
            mainMenu.Items.Add(mainItem);

            // Create form with MenuStrip and Show
            var form1 = new Form();
            form1.Controls.Add(mainMenu);
            Application.Run(form1);
        }

        private static void Popup(object sender, EventArgs e)
        {
            // Create a form with a right click handler and show
            var form2 = new Form();
            var contextMenu = new ContextMenuStrip();
            contextMenu.Items.Add("Just an item...");
            var loc = form2.Location; //<---- get the location
            var p2 = new Point(loc.X, loc.Y); //<---- get the point of form
            form2.ContextMenuStrip = contextMenu;
            form2.ContextMenuStrip.Show(form2, p2); //<---- just add this code.
            form2.Show();
            // Problem: contextMenu.Show() will give focus to form1
        }
    }
like image 89
spajce Avatar answered Nov 16 '22 22:11

spajce