C#: 将派生类作为一个泛型参数传递

3

最近我开始学习有关事件/委托与类的扩展。

通过添加一个名为 SetDraggable()扩展方法到 Windows 窗体控件,我想把我所学的应用到实践中。该方法使用 MouseDownMouseMove 事件来移动控件。

一切都很顺利,但问题是它只适用于特定的控件 - 在我的情况下,是一个 Button

namespace Form_Extensions
{
    public static class Extensions
    {
        private static System.Windows.Forms.Button StubButton;
        private static Point MouseDownLocation;
        public static void SetDraggable(this System.Windows.Forms.Button b)
        {
            b.MouseDown += b_MouseDown;
            b.MouseMove += b_MouseMove;
            StubButton = b;
        }

        private static void b_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                MouseDownLocation = e.Location;
            }
        }

        static void b_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                StubButton.Left = e.X + StubButton.Left - MouseDownLocation.X;
                StubButton.Top = e.Y + StubButton.Top - MouseDownLocation.Y;
            }
        }

    }
}

如下所示,我需要特定的控件来调用鼠标事件——我不能从父类System.Windows.Forms中访问这些事件。
所以我的问题仍然存在——有没有一种概念允许程序员通用地传递所有派生类作为参数。我基本上正在尝试避免为每个控件复制粘贴以下代码,并希望将其泛化为所有从System.Windows.Forms派生的类。
据我所知,这个思路的主要缺陷是我假设所有派生类都将具有我需要的事件;然而,由于委托在函数形式上存在类似的事情,我希望有人能够权衡涉及对象或参数的情况。

1
父类不是 System.Windows.Forms,那只是命名空间。父类是 Control,你当然可以使用它 :) 使用泛型方法也是可能的,但并不是必要的。理想情况下,你还应该避免使用那些静态字段,因为可能会有多个并发的可拖动对象;在 SetControlDraggable 方法中使用闭包会更好。或者直接使用 sender,这才是它的真正用途 :) - Luaan
4个回答

6
父类不是System.Windows.Forms,那只是命名空间。实际的父类是Control,你当然可以使用它 :) 使用通用方法也是可能的,但并不是必要的。
理想情况下,你也应该避免静态字段,因为可能会有多个并发的可拖动项;在SetControlDraggable方法中使用闭包将更加有效:
public static void SetControlDraggable(this Control control)
{
  Point mouseDownLocation = Point.Empty;

  control.MouseDown += (s, e) =>
    {
      if (e.Button == MouseButtons.Left) mouseDownLocation = e.Location;
    }
  control.MouseUp += (s, e) =>
    {
      if (e.Button == MouseButtons.Left)
      {
        control.Left = e.X + control.Left - mouseDownLocation.X;
        control.Top = e.Y + control.Top - mouseDownLocation.Y;
      }
    }
}

闭包的想法很好。我曾经考虑过避免静态点的方法,但是想不出这么优雅的解决方案,然后认为在实践中这不会成为问题。但当然这个解决方案更安全。 - René Vogt
非常感谢您的澄清,对于名称空间的混淆我感到抱歉。同时也感谢您通过消除静态字段来避免我未来可能遇到的麻烦! - AAS.N

3
这里不需要使用泛型。你可以简单地使用Control基类,这样你的扩展就可以与所有从Control派生的类一起工作。
public static void SetDraggable(this Control c)
{
        c.MouseDown += c_MouseDown;
        c.MouseMove += c_MouseMove;
        control = c;
}

您无需使用静态成员来保留控件的引用,因为此引用会作为事件处理程序的sender参数传递:

private static void c_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
    Control control = (Control)sender;
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        control.Left = e.X + control.Left - MouseDownLocation.X;
        controlTop = e.Y + control.Top - MouseDownLocation.Y;
    }
}

1

有两种可能性:

  public static class Extensions
{
    private static System.Windows.Forms.Control StubButton;
    private static Point MouseDownLocation;

    public static void SetControlDraggable(this System.Windows.Forms.Control b)
    {
        b.MouseDown += b_MouseDown;
        b.MouseMove += b_MouseMove;
        StubButton = b;
    }

    public static void SetDraggable<T>(this T b)
        where T:System.Windows.Forms.Control
    {
        b.MouseDown += b_MouseDown;
        b.MouseMove += b_MouseMove;
        StubButton = b;
    }

    private static void b_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            MouseDownLocation = e.Location;
        }
    }

    private static void b_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            StubButton.Left = e.X + StubButton.Left - MouseDownLocation.X;
            StubButton.Top = e.Y + StubButton.Top - MouseDownLocation.Y;
        }
    }
}

1
关于通用问题,其他帖子已经帮助您解决了该问题。您不需要使方法成为通用的,使用Control作为方法类型就足够了,如果出于任何原因您想要它成为通用的,只需添加一个where从句,说明类型应该派生自Control类。
扩展提供程序组件
对于Windows Forms,向控件添加此类扩展的好方法是创建一个扩展程序组件,它为您带来了设计时支持。您可以创建一个扩展程序组件并向其他组件添加一个EnableDrag属性,然后您可以将其设置为true或false以使它们可拖动。
扩展提供程序提供的属性实际上驻留在扩展提供程序对象本身中,因此不是它修改的组件的真正属性。但在设计时,为扩展的控件在属性窗口中显示该属性。此外,在运行时,您无法通过调用扩展程序组件的getter和setter方法来访问该属性。
示例
在这个例子中,我创建了一个名为DraggableExtender的扩展组件。当您将此组件实例拖放到表单上时,所有控件都将有一个额外的属性,名称为EnableDrag on draggableExtender1,您可以在设计时将其设置为truefalse。然后,如果您将其设置为true,则该控件在运行时可拖动。
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[ProvideProperty("EnableDrag", typeof(Control))]
public class DraggableExtender : Component, IExtenderProvider
{
    private Dictionary<Control, bool> EnableDragValues = 
        new Dictionary<Control, bool>();
    public bool CanExtend(object extendee)
    {
        //You can limit the type of extendee here
        if (extendee is Control)
            return true;
        return false;
    }
    public bool GetEnableDrag(Control control)
    {
        if (EnableDragValues.ContainsKey(control))
            return EnableDragValues[control];
        return false;
    }
    public void SetEnableDrag(Control control, bool value)
    {
        EnableDragValues[control] = value;
        {
            if (value)
            {
                control.MouseDown += MouseDown;
                control.MouseMove += MouseMove;
                control.Cursor = Cursors.SizeAll;
            }
            else
            {
                control.MouseDown -= MouseDown;
                control.MouseMove -= MouseMove;
                control.Cursor = Cursors.Default;
            }
        }
    }

    Point p1;
    private void MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
            p1 = Cursor.Position;
    }
    private void MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            var control = (Control)sender;
            var p = control.Location;
            p.Offset(Cursor.Position.X - p1.X, Cursor.Position.Y - p1.Y);
            control.Location = p;
            p1 = Cursor.Position;
        }
    }
}

了解 Extender 提供程序的更多信息


这里也涉及到了泛型参数:如何为表单控件创建模板函数?,但不要忘记扩展提供程序选项 :) - Reza Aghaei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接