这是我学到的:
问题不完全在于ToolStripControlHost
类,而在于DateTimePicker
控件本身,更具体地说 - 它与FlowLayoutPanel
s 以及可能的其他类似控件的交互。而且我不确定这是错误还是预期的行为,但似乎更像是错误。
它是这样工作的:如果有另一个可以明显激活的控件(如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
}
}
解决方法解释:
- 原始 DTP
OnGotFocus()
和OnLostFocus()
事件处理程序被覆盖以触发我的新控件Enter
和Leave
事件。请注意,它们几乎是正确触发的。
- 当控件是唯一可以激活的控件时,当控件第一次离开时,它会立即激活和停用 - 这意味着加倍(冗余)
Enter
和Leave
事件。我们不想要这些,所以我检查事件之间的时间,如果它小于 10 毫秒,我会忽略以后的事件。
DesignerToolStripControlHost
用于修复损坏的设计器行为。如果你ToolStripControlHost
直接使用,你会得到一个试图显示控件的设计器视图的异常,因为设计器试图实例化这个没有参数的类,而这个类没有一个没有参数的构造函数。所以我的新课就是这样。
- 当包含
ToolStripDateTimePicker
中断的表单的设计器视图(您可以通过查看 DTP 来判断)消失时,只需关闭设计器视图并再次打开它。在您再次编译或调试您的应用程序之前,它将正常工作。
故障修复在 1ms 的时间窗口内进行了测试并且工作正常。所以我选择了 10ms 以确保它可以在速度较慢或负载更多的机器上工作,但它仍然足够短,可以捕获来自用户交互的任何事件。