AAS.N
AAS.N

Reputation: 313

C#:Passing derived classes as one generic parameter

I recently started learning more about events/delegates in conjunction with extensions to a class.

I wanted to put what I learned into practice by adding an extension method to Windows Form controls called SetDraggable(), which in turn uses a MouseDown and MouseMove events to move the control around.

All works fine, except for the idea that it only applies to a specific control -- in my case, a 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;
            }
        }

    }
}

As can be seen, I need the specific control in order to call the Mouse events -- I cannot access those events from the parent class System.Windows.Forms.

So my question remains -- is there a concept that allows programmers to generically pass all derived classes as an argument. I am basically trying to avoid copy pasting the below code for every single control and would like to generalize it for all classes derived from System.Windows.Forms.

As far as I can tell, the main flaw in that idea is the fact that I am ASSUMING that all derived classes will have the event I need; however, since a similar thing exists for functions in the form of Delegates, I was hoping someone could weigh in for cases involving objects or parameters.

Upvotes: 3

Views: 240

Answers (4)

Reza Aghaei
Reza Aghaei

Reputation: 125227

About the generic problem, other posts help you to solve the problem. You don't need to make the method to be generic and using Control as type of method is enough, also if for any reason you want it to be generic, it's enough to add a where clause that states the type should be derived form Control class.

Extender Provider Components

For Windows Forms, a good solution for adding such extensions to controls is creating an extender component which brings design time support for you. You can create an extender component and add an EnableDrag property to other components then you can set it to true or false to make them draggable.

The property provided by the extender provider actually resides in the extender provider object itself and therefore is not a true property of the component it modifies. But at design time, the property appears in property window for extended controls. Also at run time, you cannot access the property by calling getter and setter methods of extender component.

Example

In this example, I've created a DraggableExtender extender component. When you drop an instance of this component on your form, all controls will have an additional property named EnableDrag on draggableExtender1 which you can set it to true or false at design time. Then the control will be draggable at run-time if you set it to 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;
        }
    }
}

Learn More about Extender Provider

Upvotes: 1

Luaan
Luaan

Reputation: 63752

The parent class isn't System.Windows.Forms, that's just the namespace. The actual parent class is Control, and you can certainly use that :) Using a generic method is also possible, but not really necessary.

Ideally, you'd also want to avoid those static fields, since it's possible to have multiple concurrent draggables; a closure in the SetControlDraggable method will work a lot better:

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;
      }
    }
}

Upvotes: 6

Ren&#233; Vogt
Ren&#233; Vogt

Reputation: 43896

There is no need for generics here. You can simply use the Control base class and your extension will work with all classes derived from Control.

public static void SetDraggable(this Control c)
{
        c.MouseDown += c_MouseDown;
        c.MouseMove += c_MouseMove;
        control = c;
}

And you don't need a static member to keep the control's reference as this is passed as your event handler's sender argument:

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;
    }
}

Upvotes: 3

Andriy Kizym
Andriy Kizym

Reputation: 1796

There are two possibilities:

  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;
        }
    }
}

Upvotes: 1

Related Questions