.NET 4.0/4.5 WinForms的奇怪错误MenuStrip窃取焦点

mar*_*987 13 .net c# winforms

我们最近升级到VS 2012,同时升级到.NET Framework 4.5.这引入了与WinForms MenuStrip控件相关的奇怪且讨厌的错误.在针对.NET Framework 4.0的应用程序中也会出现同样的错误,因为4.5的安装程序显然也会更新4.0的部分内容.因此,由于框架升级,现在打破了完美的代码模式.

问题描述:
我有一个带有MenuStrip的Form1.对于其中一个下拉项,事件处理程序打开另一个Form2(不一定是子项,只是另一个Form).如果用户右键单击新的Form2或其中一个子控件,并触发ContextMenuStrip的Show(),则原始Form1再次弹出前景.
这与Form2中之前的所有其他UI操作无关.一个人可以调整大小,移动,最小化,最大化Form2,在控件之间切换,输入文本等.不知何故,Form1的MenuStrip似乎记得它导致Form2打开并抓住焦点在第一次右键单击.

我正在尝试不同的方法,但到目前为止还无法找到解决方法.这个星座并不罕见,可能会影响到很多WinForms应用程序.因此,我决定将其发布在此处,因为可行的解决方案可能具有普遍意义.如果有人知道解决方法或至少有一些线索给我,我会很高兴.

我能够在下面的代码中提炼出它的本质.它应该可以在安装了.NET 4.5的任何机器上重现,并且在每个目标4.0和4.5时发生 - 但不是在3.5或更低.

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我花了一些时间单步执行.NET Framework源代码,发现根本原因很可能在System.Windows.Forms.ToolStripManager中.在那里,微软正在使用消息过滤器来跟踪窗口激活,这在某种程度上是错误地为MenuStrip实现的.
与此同时,我还发现Microsoft已经在一个修补程序中解决了这个问题(请参阅http://support.microsoft.com/kb/2769674),并希望这将在未来的.NET Framework 4.5更新中找到它的方法.

不幸的是,很难强制将此修补程序应用于所有客户端计算机.因此,仍然会非常感谢可行的解决方法.到目前为止,我自己还无法找到切实可行的解决方案......

编辑#2:原始KB编号适用于Win8,但在KB 2756203下有类似的Win7和Vista修补程序.如果有人可以使用它,请在此处找到下载的修补程序:http://thehotfixshare.net/board/index.php ?autocom = downloads&showfile = 15569.我们测试了它,它确实解决了这个问题.如果我们很快找不到解决方法,我们将使用此修补程序.

编辑#3:对spajce提出的公认解决方案的评论
显然,在任何 ContextMenu 上调用Show()都会说服原始的MenuStrip忘记它对焦点的主张.这可以通过某种方式完成,以便虚拟的ContextMenu甚至不会显示在屏幕上.我找到了在任何Form的构造函数中插入以下代码段的最短且最容易实现的方法:

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

        using (var dummyMenu = new ContextMenuStrip()) {
            dummyMenu.Items.Add(new ToolStripMenuItem());
            dummyMenu.Show(Point.Empty);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,每个打开的Form都会清除ToolStripMenu系统的损坏状态.也许可以将这个代码放在一个静态方法中,比如FormHelper.FixToolStripState(),或者将它放在模板Form的OnCreateControl(...)中,并从那里继承所有的Form(无论如何我们幸运地做了什么).

spa*_*jce 1

这是我的解决方案:)

 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
        }
    }
Run Code Online (Sandbox Code Playgroud)