如何强制自定义事件处理程序正常工作
Posted
技术标签:
【中文标题】如何强制自定义事件处理程序正常工作【英文标题】:How to force custom event handlers to work properly 【发布时间】:2021-12-12 08:16:41 【问题描述】:您好,我的代码有问题。我在表单上添加了 44 个标签,并尝试以这种方式为每个标签制作 2 个事件处理程序:
/// <summary>
/// Adds event handler methods to all Label controls on current form using foreach loop,
/// these handlers handle MouseDown and MouseMove events
/// </summary>
public void AddEventToLabels()
foreach (Control c in this.Controls)
if (c.GetType() == typeof(Label))
c.MouseDown += new MouseEventHandler(this.Label_MouseDown);
c.MouseMove += new MouseEventHandler(this.Label_MouseMove);
/// <summary>
/// Executes when button of mouse is pressed by user and cursor is over the control
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">Provides data for the MouseUp, MouseDown, and MouseMove events</param>
public void Label_MouseDown(object sender, MouseEventArgs e)
md.StoreMouseLocation(e); //Just a method to store location of cursor
/// <summary>
/// Executes when user moves mouse and cursos is over the control
/// </summary>
/// <param name="sender">The source of the event</param>
/// <param name="e">Provides data for the MouseUp, MouseDown, and MouseMove events</param>
public void Label_MouseMove(object sender, MouseEventArgs e)
var label = sender as Label; //Casts sender object to control on current form
md.MoveControl(e, label); //Moves label with cursor
但是这些处理程序不起作用,我不明白为什么。那么,我怎样才能让它正常工作?
【问题讨论】:
foreach (var c in this.Controls.OfType<Label>()) ...
。确保标签确实由 Form
托管,而不是其他容器。
是的,你是对的
【参考方案1】:
每个Control
(表单、用户控件,还有按钮、文本框等)都有零个或多个子控件。可以通过属性Control.Controls访问它们
您想为表单中的所有标签订阅事件 MouseDown 和 MouseMove。
当然你想按照正确的面向对象的思想工作,所以你Separate your Concerns。除其他外,您可以通过创建只有一项任务的小方法来做到这一点。这样,您的方法将更易于理解、更好地重用、更易于维护、更改和单元测试。
所以你给你的表单几个属性:
public IEnumerable<Control> SubControls => this.Controls.Cast<Control>();
IEnumerable<Lable> Labels => this.SubControls.OfType<Label>();
为了提高效率,您可以更改最后一个:
IEnumerable<Lable> Labels => this.Controls.OfType<Label>();
但我假设您不会每秒调用该方法十次,所以我不会打扰:可维护性是这里的关键。
在我看来,您想实现拖放标签。
鼠标按下:开始拖动标签:保存必须移动的标签;保存鼠标按下位置 鼠标移动时:如果我们正在拖动标签,请将标签定位在鼠标位置 鼠标向上:停止拖动标签。拖动时标签的位置不是鼠标位置,而是鼠标位置+鼠标按下时的偏移量。
Size DragLabelOffset get; set; = Size.Empty;
在拖动开始时,DragLabelOffset 将是要拖动的标签的位置与拖动开始时鼠标位置之间的差异。拖动时,标签的Location为当前鼠标位置+DragLableOffset。
void StartDragDropLabel(Label label, Position mouseDownPosition)
this.DraggedLabelOffset = new Size(mouseDownPosition.X - label.Location.X,
mouseDownPosition.Y - label.Location.Y);
void DragLabel(Label label, Position mousePosition)
label.Location = mousePosition + this.DraggedLabelOffset;
void StopDragLabel(Label label, Position mousePosition)
// not sure if this is needed: set final drag position
this.DragLabel(label, mousePosition);
this.DragLabelOffset = Size.Empty; // indicate: no drag is active
事件处理程序:
void Label_MouseDown(object sender, MouseEventArgs e)
Label label = (Label)sender;
this.StartDragDropLabel(label, e.Location);
void Label_MoveMove(object sender, MouseEventArgs e)
Label label = (Label)sender;
this.DragLabel(label, e.Location);
void Label_MouseUp(object sender, MouseEventArgs e)
Label label = (Label)sender;
this.StopDragLabel(label, e.Location);
订阅鼠标事件:
void SubscribeLabelsToMouseEvents()
foreach (Label label in this.Labels)
label.MouseDown += this.Label_MouseDown;
label.MouseMove += this.Label_MouseMove;
label.MouseUp += this.Label_MouseUp
虽然旧代码用于创建new EventHandler(...)
,但没有必要这样做,现代编译器无需 EventHandler 即可理解这一点。
【讨论】:
感谢您的回答我会记住的,但在我看来 ControlCollection 类没有 Cast 方法:docs.microsoft.com/en-us/dotnet/api/… 你是对的,类ControlCollection
没有方法Cast
。幸运的是,ControlCollection 实现了IEnumerable
,如果你包含System.Linq,你将拥有IEnumerable 的扩展方法,其中Enumerable.Cast。所以请记住:每当你看到一个类实现了类似 Collection 的接口时,请记住,在使用 Cast<MyType>
之后,你总是可以使用 LINQ
顺便说一句,在拖放期间更改光标可能是个好主意。也可以考虑阅读this article about Drag & Drop【参考方案2】:
问题在于控制的父级。我需要在指定容器中迭代所有这些控件,因为它是所有需要控件的父级。所以我使用foreach (Control c in this.<ParentName>.Controls)
而不是foreach (Control c in this.Controls)
【讨论】:
以上是关于如何强制自定义事件处理程序正常工作的主要内容,如果未能解决你的问题,请参考以下文章