【问题标题】: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 托管,而不是其他容器。
  • 是的,你是对的

标签: c# winforms event-handling controls mouseevent


【解决方案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)

【讨论】:

    猜你喜欢
    • 2016-09-08
    • 2014-07-27
    • 2013-03-25
    • 1970-01-01
    • 2019-06-06
    • 2010-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多