繁体   English   中英

.NET ToolStrip控制错误:直到处置ToolStrip时才触发离开事件

[英].NET ToolStrip control bug: leave event not fired until the ToolStrip is disposed

要重现这个bug,你需要创建自定义ToolStripItem ,使用ToolStripControlHost 就我而言,我制作了ToolStripDateTimePicker控件(在许多不错的教程中都可以看到)。 但是,该控件的行为与常规DateTimePicker略有不同。

正常运行时,按ESC时会产生默认的Windows铃声。 ToolStrip托管控件以更明智的方式对按下的ESC做出反应。 控件变为非活动状态,无提示音。

这是Bug的一部分:单击以使另一个控件集中时-常规DateTimePicker触发Leave事件。 如预期的那样。 ToolStrip托管控件不会触发任何事件!

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

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

这样做的结果是包含ToolStrip控件的窗体的焦点行为被破坏。 进入ToolStrip托管控件后,表单无法再次获得焦点。

但这是一种解决方法:您可以将焦点放在其他表单(甚至可能是另一个控件)上,然后聚焦目标表单-它对我有用。

但是,我希望自动完成-用户退出托管控件时。 问题是我没有活动。 有任何想法吗?

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

此处: 演示问题的示例应用程序 我已用示例解决方法替换了该问题,以在OnGotFocus()OnLostFocus()替代项中查看问题注释。

在将FlowLayoutPanel TabIndex更改为0之前,它工作得很好(并且无法重现该错误),因此在应用程序启动时DateTimePicker不处于活动状态。

这是我学到的:

问题不完全与ToolStripControlHost类有关,而是与DateTimePicker控件本身有关,并且更具体地说-与FlowLayoutPanel和其他可能的类似控件的交互有关。 而且我不确定这是错误还是预期的行为,但看起来更像是错误。

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

您为什么要拥有一个没有活动控件的容器? 我使用FlowLayoutPanel生成一个只读的,不可编辑的报告。 它是不可编辑的,但是我希望它可滚动,并且我不希望DateTimePicker控件从FlowLayoutPanel窃取焦点-因此在这种情况下FlowLayoutPanel 一个活动控件。

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

因此, ToolsStripDateTimePicker ,我固定了焦点毛刺的完善的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以确保它在速度较慢或负载较高的计算机上都能正常工作,但它仍然足够短,无法捕获用户交互中的任何事件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM