【问题标题】:Strange Bug with .NET 4.0/4.5 WinForms MenuStrip Stealing Focus.NET 4.0/4.5 WinForms MenuStrip 窃取焦点的奇怪错误
【发布时间】:2012-12-13 01:15:40
【问题描述】:

我们最近升级到 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
        }
    }
}

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

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

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

EDIT#3:对 spajce 提出的已接受解决方案的评论
显然,在 any ContextMenu 上调用 Show() 将使原来的 MenuStrip 忘记它对焦点的声明。这可以通过某种方式完成,以便虚拟 ContextMenu 甚至不会显示在屏幕上。我找到了在任何 Form 的构造函数中插入以下 sn-p 的最短且最容易实现的方法:

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

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

因此,打开的每个表单都会清除 ToolStripMenu 系统的损坏状态。不妨将此代码放在像 FormHelper.FixToolStripState() 这样的静态方法中,或者将其放在模板 Form 的 OnCreateControl(...) 中并从中继承所有 Forms(无论如何我们幸运地做了什么)。

【问题讨论】:

  • 非常好的复制品。我严重怀疑你会得到答案,工具条类是一个主要的痛苦。我会将此提交给 Microsoft 支持。
  • 查看热修复说明,是完全相反的问题。它没有足够快地删除消息过滤器。只需左键单击表单即可查看问题。工具条管理器中有代码应该在另一个窗口被激活时删除过滤器,这没有发生。希望这个修补程序能尽快更新,这是一个非常严重的错误。
  • 根本原因很难解决,因为不同的贡献部分在源代码中非常分布。因为无论如何我都看不到修复框架代码的方法,所以我只是在某个时候停下来。我很惊讶底层实现有点脏(在我看来),但我并不惊讶这段代码很容易出现此类错误。
  • 此修补程序已包含在 1 月 8 日发布的 .NET 4.5 更新中。知识库文章在这里:support.microsoft.com/kb/2750149

标签: c# .net winforms


【解决方案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
        }
    }

【讨论】:

  • 奇怪的是,这确实有效!我更新了上面的问题以使其更具普遍性(我们是否应该直接编辑您的答案?)。只是一个小评论:由于 Point 是一个结构,它无论如何都会被复制,所以不需要 loc/p2。
  • 我承认我只是一个初学者,所以我给出的技巧只是我的建议,以解决我们遇到的一些小错误,所以我的观点是in programming there's always clean codes to solve the problem and the dirty way is our last weapon我阅读了你的更新问题,这很好,这可能是更好的方法:)
  • 老实说,我起初并不相信您的解决方案会如此有效。我自己尝试了额外的 Show() 调用,发现连续两次调用 Show() 也可以解决问题,但考虑到我们有一个包含数百个菜单的庞大代码库,我没有看到这如何实用和上下文菜单。你提出的解决方案给了我正确的方向。
【解决方案2】:

我也遇到了这个问题。我不想更改代码,因为我觉得我的程序做得对。我在微软网站上找到了修复:

http://support.microsoft.com/kb/2750147

它确实有所作为,我们今天早上刚刚在 2 台用户计算机上安装了它。现在,我们的 IT 人员正在将它安装到所有计算机上,因为它已经过测试并证明可以工作。

【讨论】:

    猜你喜欢
    • 2012-12-23
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多