如何强制自定义事件处理程序正常工作

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&lt;Label&gt;()) ... 。确保标签确实由 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&lt;MyType&gt; 之后,你总是可以使用 LINQ 顺便说一句,在拖放期间更改光标可能是个好主意。也可以考虑阅读this article about Drag & Drop【参考方案2】:

问题在于控制的父级。我需要在指定容器中迭代所有这些控件,因为它是所有需要控件的父级。所以我使用foreach (Control c in this.&lt;ParentName&gt;.Controls) 而不是foreach (Control c in this.Controls)

【讨论】:

以上是关于如何强制自定义事件处理程序正常工作的主要内容,如果未能解决你的问题,请参考以下文章

Cloudwatch 自定义事件 SQS 无法正常工作

如何在 Blazor 中编写自定义值更改事件处理程序?

如何对自定义 AJAX 事件进行单元测试

如何发出和处理自定义事件?

Windows 工作流服务 - 自定义服务主机。处理工作流事件

如何在 iOS 开发中处理自定义事件?