.NET ToolStrip控件错误:在处理ToolStrip之前不会触发事件

时间:2013-12-02 14:41:29

标签: c# .net datetimepicker toolstrip toolstripcontrolhost

要重现此错误,您需要使用ToolStripItem创建自定义ToolStripControlHost。 就我而言,我做了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无效。

1 个答案:

答案 0 :(得分:1)

这是我学到的东西:

问题不在于ToolStripControlHost类,而在于DateTimePicker控件本身,更具体一点 - 它与FlowLayoutPanel和其他类似控件的交互。我不确定这是一个错误或预期的行为,但似乎更像是一个错误。

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

为什么你想要一个没有活动控件的容器?我使用FlowLayoutPanel生成只读,不可编辑的报告。它不可编辑,但我希望它可滚动,我不希望DateTimePicker控件从FlowLayoutPanel窃取焦点 - 所以在这种情况下FlowLayoutPanel 积极的控制。

当然,这种方式不起作用。此类行为需要更多变通方法,例如从包含表单接收鼠标事件,但Focus的正确Leave / ToolStripDatePickerControl行为是一个良好的开端。

所以不用多说,我的完美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事件。我们不想要那些,所以我检查事件之间的时间,如果它小于10ms我只是忽略以后的事件。
  3. DesignerToolStripControlHost用于修复破坏的设计师行为。如果您直接使用ToolStripControlHost,则会尝试显示控件的设计器视图时出现异常,因为设计器尝试在没有参数的情况下实例化此类,并且此类没有不带参数的构造函数。所以我的新班级确实如此。
  4. 当包含ToolStripDateTimePicker的表单的设计者视图中断时,您可以通过看到DTP消失,只需关闭设计器视图并再次打开它。在您再次编译或调试应用程序之前,它将正常工作。
  5. 使用1ms时间窗口测试毛刺修复并且工作正常。因此,我选择了10毫秒来确保它可以在速度较慢或负载较重的机器上运行,但它仍然足够短,可以捕获用户交互中的任何事件。