【问题标题】:.NET ToolStrip control bug: leave event not fired until the ToolStrip is disposed.NET ToolStrip 控件错误:直到 ToolStrip 被释放后才触发 leave 事件
【发布时间】:2013-12-02 14:41:28
【问题描述】:

要重现此错误,您需要使用ToolStripControlHost 创建自定义ToolStripItem。 就我而言,我制作了ToolStripDateTimePicker 控件(如在许多优秀教程中所见)。然而,该控件的行为与常规的 DateTimePicker 略有不同。

ESC 处于活动状态时按下普通模式会产生默认的 Windows 铃声。 ToolStrip 托管控件以更明智的方式对按下 ESC 作出反应。控件变为非活动状态,没有蜂鸣声。

这是错误部分:当通过单击聚焦另一个控件时 - 常规 DateTimePicker 触发 Leave 事件。正如预期的那样。 ToolStrip 托管控件不会触发任何事件!

是的,我已经尝试过KeyDown 事件——它不会在按下ESC 键时发送,而是在按下任何其他键时发送。

我相信这是 .NET 本身的错误。

这样做的后果是破坏了包含ToolStrip 控件的表单的焦点行为。进入ToolStrip托管控件后,表单无法再次聚焦。

但这是一种解决方法:您可以聚焦另一个表单(甚至可能是另一个控件),然后是目标表单 - 它适用于我。

但是我希望它自动完成 - 用户退出托管控件的那一刻。问题是我没有这方面的活动。有什么想法吗?

奇怪的是,Leave 事件最终会在托管控件被释放时触发 - 这显然是一个错误,因为该事件在那里完全没用。

这里:sample application illustrating the problem 我已将其替换为示例解决方法,以查看问题注释掉 OnGotFocus()OnLostFocus() 覆盖。

在我将 FlowLayoutPanel TabIndex 更改为 0 之前,它运行良好(并且无法重现错误),因此在应用程序启动时 DateTimePicker 不活动。

【问题讨论】:

  • “我相信这是 .NET 本身的错误” - 那么创建一个显示错误行为的(非常)小项目不会有任何问题。你可以尝试这样做吗?根据您的描述,它听起来应该可以正常工作,您如何确定没有触发事件?
  • 好的,我已经创建了一个示例应用程序。我没有时间让它更详细,我正在使用实际应用程序解决这个问题。截止日期... ;)
  • 对我有用 - 只要在同一表单上有 另一个 控件。 :) 当无法控制焦点时,我认为没有引发 Leave 事件。
  • 如果是错误,请报告给 Microsoft,而不是 Stack Overflow。报告时,请务必说明问题发生在哪个 .NET 版本和 OS 版本上。
  • 问题是我不确定它是否是一个错误。也许我做错了什么。我发现我可以公开托管控件的 TabStop 和 TabIndex 属性,但现在它什么都不做。当我完成后,有人将能够从这次经历中受益。或者不是;)

标签: c# .net datetimepicker toolstrip toolstripcontrolhost


【解决方案1】:

这是我学到的:

问题不完全在于ToolStripControlHost 类,而在于DateTimePicker 控件本身,更具体地说——它与FlowLayoutPanels 以及可能的其他类似控件的交互。而且我不确定这是一个错误还是预期的行为,但看起来更像是一个错误。

它的工作原理如下: 如果有另一个可以明显激活的控件(例如TextBox),则离开DatePickerControl 意味着激活另一个控件。但是,如果另一个控件是空容器,或者 没有可以激活的控件的容器 - 尽管 DateTimePicker 控件不再处于活动状态,但不会触发 Leave 事件。

为什么要让没有活动控件的容器本身处于活动状态?我使用FlowLayoutPanel 生成只读、不可编辑的报告。它不可编辑,但我希望它可滚动,并且我不希望 DateTimePicker 控件从 FlowLayoutPanel 窃取焦点 - 所以在这种情况下 FlowLayoutPanel 一个活动控件。 p>

当然不能这样。要实现这种行为还需要更多解决方法,例如从包含表单接收鼠标事件,但正确的 Focus/LeaveLeave 行为是一个好的开始。

所以事不宜迟,我完美的ToolsStripDateTimePicker 控件,已修复焦点故障:

DesignerToolStripControlHost类:

namespace System.Windows.Forms {
    /// <summary>
    /// Fixes ToolStripControlHost broken designer behavior
    /// </summary>
    public class DesignerToolStripControlHost : ToolStripControlHost {
        /// <summary>
        /// Fixes designer bug by creating a constructor allowing to create ToolStripControlHost
        /// without parameter
        /// </summary>
        public DesignerToolStripControlHost() : base(new UserControl()) { }
        /// <summary>
        /// Initializes a new instance of the System.Windows.Forms.DesignerToolStripControlHost
        /// class that hosts the specified control
        /// </summary>
        /// <param name="c"></param>
        public DesignerToolStripControlHost(Control c) : base(c) { }
        /// <summary>
        /// Initializes a new instance of the System.Windows.Forms.DesignerToolStripControlHost
        /// class that hosts the specified control and that has the specified name
        /// </summary>
        /// <param name="c"></param>
        /// <param name="name"></param>
        public DesignerToolStripControlHost(Control c, string name) : base(c, name) { }
    }
}

ToolStripDateTimePicker类:

using System.ComponentModel;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace System.Windows.Controls {

    [ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.All)]
    public partial class ToolStripDateTimePicker : DesignerToolStripControlHost, IComponent {

        public ToolStripDateTimePicker() : base(new DateTimePicker() { Margin = new Padding(0, 0, 0, 0), Width = 150, Value = DateTime.Now.Date }) { }

        #region Properties

        [Browsable(true)]
        [Category("Design")]
        [Description("Internal ToolStrip hosted control.")]
        public DateTimePicker DateTimePickerControl { get { return Control as DateTimePicker; } }

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [Description("Gets or sets the tab order of the control within its container.")]
        public int TabIndex { get { return DateTimePickerControl.TabIndex; } set { DateTimePickerControl.TabIndex = value; } }

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [Description("Gets or sets a value indicating whether the user can give the focus to this control using the TAB key.")]
        public bool TabStop { get { return DateTimePickerControl.TabStop; } set { DateTimePickerControl.TabStop = value; } }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [Description("This property is ignored.")]
        public override string Text { get { return DateTimePickerControl.Value.ToString(); } set { DateTimePickerControl.Value = DateTime.Parse(value); } }

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [Description("The current date/time value for this control.")]
        public DateTime Value { get { return DateTimePickerControl.Value; } set { DateTimePickerControl.Value = value; } }

        #endregion

        #region Events

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Focus")]
        [Description("Occurs when the input focus enters the control.")]
        public new EventHandler Enter;

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Focus")]
        [Description("Occurs when the input focus leaves the control.")]
        public new EventHandler Leave;

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
        [Category("Behavior")]
        [Description("Occurs when the value of the control changes.")]
        public event EventHandler ValueChanged;

        #endregion

        #region Event Handlers

        protected void OnEnter(object sender, EventArgs e) { EventHandler handler = Enter; if (handler != null) handler(this, e); }

        protected void OnLeave(object sender, EventArgs e) { EventHandler handler = Leave; if (handler != null) handler(this, e); }

        protected override void OnGotFocus(EventArgs e) {
            base.OnGotFocus(e);
            if (Enter != null) {
                if (DateTime.Now.Ticks - _FocusGlitchFix_LastEvent > _FocusGlitchFixTickWindow) Enter.Invoke(this, e);
                _FocusGlitchFix_LastEvent = DateTime.Now.Ticks;
            }
        }

        protected override void OnLostFocus(EventArgs e) {
            base.OnLostFocus(e);
            if (Leave != null) {
                if (DateTime.Now.Ticks - _FocusGlitchFix_LastEvent > _FocusGlitchFixTickWindow) Leave.Invoke(this, e);
                _FocusGlitchFix_LastEvent = DateTime.Now.Ticks;
            }
        }

        protected void OnValueChanged(object sender, EventArgs e) { EventHandler handler = ValueChanged; if (handler != null) handler(this, e); }

        protected override void OnSubscribeControlEvents(Control control) {
            base.OnSubscribeControlEvents(control);
            DateTimePickerControl.ValueChanged += new EventHandler(OnValueChanged);
        }

        protected override void OnUnsubscribeControlEvents(Control control) {
            base.OnUnsubscribeControlEvents(control);
            DateTimePickerControl.ValueChanged -= new EventHandler(OnValueChanged);
        }

        #endregion

        #region Focus Glitch Workaround data

        private long _FocusGlitchFix_LastEvent = 0;
        private readonly long _FocusGlitchFixTickWindow = 100000; // 10ms

        #endregion

    }
}

解决方法说明:

  1. 原始 DTP OnGotFocus()OnLostFocus() 事件处理程序被覆盖以触发我的新控件 EnterLeave 事件。请注意,它们几乎是正确触发的。
  2. 当控件是唯一可以激活的控件时,当控件第一次离开时,它会立即激活和停用 - 这意味着双倍(冗余)EnterLeave 事件。我们不想要这些,所以我检查事件之间的时间,如果它小于 10 毫秒,我会忽略以后的事件。
  3. DesignerToolStripControlHost 用于修复损坏的设计器行为。如果您直接使用ToolStripControlHost,您会在尝试显示控件的设计器视图时遇到异常,因为设计器尝试不带参数实例化此类,而此类没有不带参数的构造函数。所以我的新课就是这样。
  4. 当包含ToolStripDateTimePicker 的表单的设计器视图中断时,您可以通过看到 DTP 消失来判断,只需关闭设计器视图并再次打开它。在您再次编译或调试您的应用程序之前,它将正常工作。

故障修复已在 1 毫秒时间窗口内进行了测试,并且运行良好。所以我选择了 10ms 以确保它在速度较慢或负载更多的机器上工作,但它仍然足够短,可以捕获来自用户交互的任何事件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多