Click here to Skip to main content
Click here to Skip to main content

Windows Forms, Custom Controls Part 1: Mastering DragDrop

, 16 Aug 2003
Rate this:
Please Sign up or sign in to vote.
The first in a series of articles, this one covering DragDrop

Disclaimer

First I apologize to all those using VS.NET 2002. The source code solution is in .NET 2003: you should still be able to extract it and open the source in your own VS.NET 2002 solution though. It wasn't my intent to alienate anyone, I just don't have VS.NET 2002!

Also this is my first article. I've read Marc Clifton's A Guide to writing articles for Code Project, and what a great article that is... but I ask you to please bear with me, and I hope you like my style!

Prereq

Before reading, it would help if you had some knowledge of Windows Forms, Controls, and the usage of the app.config file. If not, you should be able to follow along, but it might get a little rough. Be warned, this is a long article...!

Series Introduction

I found myself writing this article because I couldn't find a better one on this subject online! (joke)

Seriously, there isn't a great lot of detail on using Windows Forms Drag and Drop online. What you can find usually doesn't cut it. Most of the Drag and Drop online is centered around files being dragged to your application, or ListViewItems, TreeNode, etc... but nothing if you want to drag a whole control to another Form/Panel or move it on the same Form/Panel.

I have humbly taken it upon myself to change that and hopefully help others learn more about Windows Forms, Drag and Drop and Custom Controls in general.

I am planning several articles in a series on Windows Forms Custom Controls and how to create them (in my humble opinion). This first one, as mentioned, starts with Drag and Drop. I start here, because I find it's easier to create a control when the basic control functionality that all GUI users expect, already exists. This allows you to plug (I hesitate to use that buzz word) your control into an existing UI with little work.

The Source

The source code included with this article is a solution, containing a class library, and the Windows Forms Application to test it. The Class Library (the focus of this article) is heavily commented, while the test application (only about 15 lines) is not.

The solution and the Class Library are all under the LAND namespace. In case you ask it stands for Late-At-Night Developer (thought it was fitting!) I plan on using the Library professionally when it is more robust, so I ask if you wish to use it in your code you simply change the namespace please!

Drag and Drop Basics

Drag and Drop, in its conception and implementation, is actually very easy. The problem stems from needing to customize the data being dragged/dropped.

In order to use Drag and Drop at all, one parent surface (usually a Form or Panel) must have the AllowDrop property set to true. If you do not perform that step, no amount of coding at all will allow you to perform a DragDrop!

Next, you need a way to signify that a Control is being dragged. This is done in the Control's MouseDown event. Assuming you have a Control called MyButton, in the event, you would place code that looks like this:

MyButton.DoDragDrop((Button)MyButton, DragDropEffects.Move);
What this does is tell Windows that a Drag and Drop is beginning, using a Button (named MyButton), and using the DragDropEffects.Move enumeration. DragDropEffects specifies what type of Drag and Drop operation this control is capable of. You can Copy, Move, Link, etc... but I find myself using Move all the time when it comes to a complete control, unlike just the Text of a ListViewItem. When the DoDragDrop call is made, your control is saved in an IDataObject object that we'll look at next.

The next step would be to let the parent surface know a Control is being dragged to it. This is done in the parent Control's DragOver event. The code would look something like the example below, except you can't do it that way, and that's where my Class Library comes in.

// DragOver event (e.Data is the IDataObject I eluded too in 
// the paragraph above.)

if ( e.Data.GetDataPresent(typeof(Button)) )
{
    e.Effect = DragDropEffects.Move;
}
else
{
    e.Effect = DragDropEffects.None;
}

The reason this won't work is because of the typeof(Button) statement. GetDataPresent tests to see if the data being dragged is of a type this parent Control can accept. If it is, we allow the data to be moved on the parent. If not, we disable dropping of the control by setting the DragDropEffects to None. The problem: GetDataPresent only accepts a variable that is a type of DataFormats.Format. But guess what, there is no DataFormats.Format for System.Windows.Forms.Button... or any other control!!!

Your next question should be... well what is that? You didn't say anything about a data format! I have a feeling you'll be presently surprised at how easy it is to create... but using my Class Library it gets even easier. Let's take a look...

Lets get down to it!

One of the best features (I think) of .NET is the ease in which a seemingly unrelated class can modify a Control (or another class) through delegates and interfaces. That is the basis for my articles. Each class or Control we develop will modify another class or Control, and do it in just a few lines of code. The end result is a Library that provides functionality to many controls without the hassle of writing the same code over and over. Just imagine... the code above is very small, but if you had to write it over and over again, for each app that needed to support DragDrop... boring at best, and not very efficient!

In order to fix this, I created a DragDropHandler class. It is a static class, so requires no actual instance for it to work.

(I should take a minute here to inform you that Drag and Drop can not be in an MTA Thread Apartment, so making the handler static won't hurt us in that respect.)

The DragDropHandler will allow us to drag and drop controls with almost no extra development at all once it is done.

The first thing, some terminology: simple and custom controls. Simple controls are the name I have given to standard controls that come with Windows Forms. (ex: Button). A Custom control is just what it sounds like, a control we created. But it has one special element, in order for a custom control to work with the handler, it must implement the IDragDropEnabled interface that we'll create in a little bit. The reason for this is that there are some nice things that can be done by the handler during the Drag and Drop, but need custom methods to do so. Since you can't change the Button class, we'll have to not perform the "nice things" on it, but we don't want to stop those controls from being dragged with our handler, so we need to also be able to distinguish between the two.

Lets take a look at the IDragDropEnabled interface:

/// Provides an interface for controls that are handled
/// by the DragDropHandler and require additonal functionality.
public interface IDragDropEnabled
{
    // Indicates the control is dropping
    void DropComplete(bool _successfull);

    // Tells the control to store its Location (Left, Top)
    void StoreLocation();

    // Gets / Sets if the control is being dragged
    bool IsDragging { get; set; }
}

It is very straight forward. In fact, in the test app, I don't actually do anything in the StoreLocation or DropComplete methods. I figured I'd leave that up to you to play with! The most important part of the interface is IsDragging as this lets us actually report to the control that it is being dragged.

Next let's look at the custom control we use to test this interface in the test app, just so you can see how little code is involved once you use the handler.

public class TestControl : System.Windows.Forms.Control, 
    LAND.ClassLib.DragDrop.IDragDropEnabled
{
    bool m_IsDragging = false;

    /// Required designer variable.
    private System.ComponentModel.Container components = null;

    public TestControl()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();

        this.Width = 32;
        this.Height = 32;
    }

    /// Gets / Sets if the control is being dragged
    public bool IsDragging
    {
        get { return m_IsDragging; }
        set { m_IsDragging = value; }
    }

    /// Unused but in the interface
    public void StoreLocation()
    {
    }

    /// Unused but in the interface
    public void DropComplete(bool _successfull)
    {
    }

    /// Clean up any resources being used.
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if( components != null )
                components.Dispose();
        }
    base.Dispose( disposing );
    }

    Component Designer generated code

    protected override void OnPaint(PaintEventArgs pe)
    {
        if ( ! this.IsDragging )
        {
            SolidBrush brush = new SolidBrush(Color.DarkBlue);
            pe.Graphics.FillRectangle(brush, 0, 0, this.Width - 1,
                this.Height - 1);
        }

        // Calling the base class OnPaint
        base.OnPaint(pe);
    }
}

The only real work done here is the OnPaint method, where we only perform the draw of the control if it isn't being dragged. (I'll show you why later!). In fact, notice how there aren't even any MouseDown events here as I indicated in the beginning you needed!? Remember this line:

MyButton.DoDragDrop((Button)MyButton, DragDropEffects.Move);
you don't see that do you?

Let me take one more moment to break topic real quick... all the controls you will see me create are derived from System.Windows.Forms.Control. I know you can create UserControls or Inherited controls, but in my humble opinion, the best one is a straight Custom Control, because you can do almost anything you want with very few limitations. If you can bear to stick with me, you'll see what I mean in this and future articles. (end rant Smile | :) )

The actual DragDropHandler

From here, we'll develope the actuall handler. Each section will be one method of the handler, until you've seen the whole thing. Lets start with the class members:

// The sectionGroup(s)/section(s) that contain DragDropHandler mappings
private const string m_szDFSection = 
                          "LANDClassLib/DragDrop/DataFormats";
private const string m_szDFPSection = 
                          "LANDClassLib/DragDrop/DataFormatParents";

// Collections of DataFormats and DataFormatParents
private static Hashtable m_htDataFormats = new Hashtable(5);
private static Hashtable m_htDFParents = new Hashtable(2);

private static bool m_Initialized = false;   // Are we initialized yet?
private static bool m_IsSimpleDrag = false;   // Is this a simple drag?

The first thing you should notice are the two strings that refer to "mappings". In order to make this handler as robust as possible, I allow for DataFormats and Drag and Drop Parents to be configured through the app.config file. An example is shown below.

<configSections>
  <sectionGroup name="LANDClassLib">
    <sectionGroup name="DragDrop">
      <section name="DataFormats" 
               type="System.Configuration.NameValueSectionHandler" />
      <section name="DataFormatParents"
               type="System.Configuration.NameValueSectionHandler" />
    </sectionGroup>
  </sectionGroup>
</configSections>

<LANDClassLib>
  <DragDrop>
    <DataFormats>
      <add key="Test.TestControl" value="LAND_DF_TESTCTRL" />
      <add key="System.Windows.Forms.Button" value="LAND_DF_BUTTON" />
    </DataFormats>
    <DataFormatParents>
      <add key="Test.Form1" value="LAND_DF_TESTCTRL,LAND_DF_BUTTON" />
      <add key="System.Windows.Forms.Panel" value="LAND_DF_TESTCTRL" />
    </DataFormatParents>
  </DragDrop>
</LANDClassLib>

First the DataFormats section. This is where you create the DataFormats.Format we talked about earlier. Simply specifiy the fully qualified type name, for a control type you wish to allow the handler to provide DragDrop to, as the key; and then assign any string literal you would like to that object as the value. Note: the value for two different objects could be the same thing if you wanted them handled the same way and didn't want any clear distinction between them.

The DataFormatsParent section maps all of the parent Controls that have AllowDrop set to true (as we talked about in the beginning), to the controls we just defined in the DataFormats section above. You can see here that a Form will take either a Test.TestControl or a System.Windows.Forms.Button. The Panel will only allow the Test.TestControl to be dropped on it. I'm sure you can see the power here... once the handler is complete, you can easily configure DragDrop customizations accross your applications with ease!

Now back to the final members of the handler:

// Collections of DataFormats and DataFormatParents
private static Hashtable m_htDataFormats = new Hashtable(5);
private static Hashtable m_htDFParents = new Hashtable(2);

private static bool m_Initialized = false;   // Are we initialized yet?
private static bool m_IsSimpleDrag = false;   // Is this a simple drag?

The last two are self-explainatory. The Hashtables we use are for storing the information we get from app.config, after we modify it a little. m_htDataFormats holds all of the mappings in the DataFormats section, but instead of storing the type and the name listed in the app.config, we store the type as the key, and then use DataFormats to generate a new name from the one in app.config. (We'll see that later)

m_htDFParents holds all of the mappings in the DataFormatsParent section. Instead of storing the value as a ',' delimited string, we use split to store a string[] as the value in the Hashtable. (which we will also see later)

Method #1: Initialization

Time for some actual code! Here is the Initialization function:

public static void Initialize()
{
    if ( m_Initialized )
        return;

    int idx = 0;

    // Collection of DataFormats from the app.config file
    NameValueCollection nvc = 
        (NameValueCollection)ConfigurationSettings.GetConfig(m_szDFSection);

    if ( nvc == null )
        throw new Exception(
                      "Invalid section requested during Initialization."
                  );

    // Store the collection in our internal collection
    for ( idx = 0; idx < nvc.Count; idx++ )
    {
        // Get the custom string for our new DataFormat
        // and generate a new DataFormat for it
        DataFormats.Format frmt = DataFormats.GetFormat(nvc[idx]);

        // Store the new DataFormat by object type
        m_htDataFormats.Add(nvc.GetKey(idx), frmt);
    }

    // Collection of DataFormatParents from the app.config file
    nvc = (NameValueCollection) ConfigurationSettings.GetConfig(
                                    m_szDFPSection
                                );

    // Store the collection in our internal collection
    for ( idx = 0; idx < nvc.Count; idx++ )
    {
        // Store the DataFormatParent mappins by parent object type
        // The value is a string[] of DataFormats that can be DragDropped
        // on the parent
        m_htDFParents.Add(nvc.GetKey(idx), 
            ((string)nvc[idx]).Split(new Char[] {','}));
    }

    m_Initialized = true;
}

Most of that is straight forward, but the interesting bit is the line:

DataFormats.Format frmt = DataFormats.GetFormat(nvc[idx]);
This is the part I reffered to when I said we would use the value from app.config DataFormats to generate a new DataFormats.Format and use that as the value for our hashtable, since that is what GetDataPresent requires, not a string.

Method #2, #3, #4 GetDataFormat

The next set of methods provide the lookup into our hashtable for a DataFormats.Format based on the type of Control specified. They are overridden in case the user knows wants to pass a type instead of an actual control. I'll just show you the one that they all eventually call.

private static string GetDataFormat(string _szType)
{
    string szRetVal = "";

    // If our custom configuration maps the specified type, return the 
    // DataFormat, otherwise return ""
    if ( m_htDataFormats.ContainsKey(_szType) )
    {
        // Get the Name of the DataFormat for the specified object Type
        szRetVal = ((DataFormats.Format)m_htDataFormats[_szType]).Name;
    }

    // If the custom DataFormat doesn't exist, throw an exception
    if ( szRetVal == "" )
        throw new DataFormatException(

    string.Format(
        "Unable to get DataFormat:\n\nan unmapped control type:\n
        {0}\nwas specified.",
        _szType));

    return szRetVal;
}
The string _szType is determined in the overridden methods by doing the following:
string szType = _ctrl.GetType().ToString();

where _ctrl was the control passed to the overloaded method. This method set is basically using the control's type to search the hashtable for the generated DataFormats.Format. If none of this make sense, I apologize for my bad writing style, and suggest you may want to re-read some of the previous sections before going into the good stuff below!

Method #5: DeriveUnknownFormat

If you examine the e parameter in the DragDrop or DragOver events, specifically the Data member, you'll notice it's an IDataObject, which I mentioned before is how DragDrop saves your control. Since we don't know what type of control we have at the time, and the only way to get the control using GetDataPresent is to know the type, we need to write a method that can determine the type from the IDataObject.

That method can be found below:

private static string DeriveUnknownFormat(IDataObject _ido)
{
    string szDataFormat = "";

    // Determine if the data is a valid type by scanning our 
    // custom mapped DataFormats and asserting them into
    // GetDataPresent until GetDataPresent returns true 
    // or we exhaust the list
    foreach ( object _obj in m_htDataFormats.Values )
    {
        szDataFormat = ((DataFormats.Format)_obj).Name;
        if ( _ido.GetDataPresent(szDataFormat) )
        {
            break;
        }
    }

    return szDataFormat;
}
Since as you'll see later, the only way to store a control for Drag and Drop in the DoDragDrop method we talked about, is to use its DataFormats.Format, we can safely go through our hashtable of DataFormat.Formats and see if GetDataPresent recognizes any of them. If it does, we get the key and we now know the type. If not, this isn't a supported type, so return nothing.

Method #6: StoreControl

This one is short and sweet. Given a control, use GetDataFormat (method's 2->4 above). to get the format for the control if there is one. If so, get a reference to the control as an IDataObject, which we know we need for DoDragDrop. The bool _simple_drag is a flag for a later method letting it know this isn't an IDragDropEnabled control. (I used the bool instead of doing a is check in some places because I thought it read better.)

private static DataObject StoreControl(Control _ctrl, bool _simple_drag)
{
    // This is a full DragDrop
    m_IsSimpleDrag = _simple_drag;

    // Get the custom DataFormat
    string szDF = GetDataFormat(_ctrl);

    // "Convert" the control to a DataObject using the custom DataFormat
    DataObject doControlToDrag = new DataObject(szDF, _ctrl);

    return doControlToDrag;
}

Method #7 and #8: BeginDrag and BeginSimpleDrag: Finally getting somewhere!

Here is the BeginDrag method (I took the exception handling out to shorten it):

public static DataObject BeginDrag(Control _ctrl, bool _invalidate)
{
    DataObject doControlToDrag = StoreControl(_ctrl, false);

    // Inform the control that it has begun a drag operation
    ((IDragDropEnabled)_ctrl).StoreLocation();
    ((IDragDropEnabled)_ctrl).IsDragging = true;

    // Set the control up to be in the foreground and invalidate it
    // incase it needs redrawing
    if ( _invalidate )
    {
        _ctrl.BringToFront();
        _ctrl.Invalidate();
    }

    // return the converted control as a DataObject
    return doControlToDrag;
}

And here is the BeginSimpleDrag method:

public static DataObject BeginSimpleDrag(Control _ctrl)
{
    return StoreControl(_ctrl, true);
}

Not much to either of them huh? The only difference is that BeginDrag has calls to the IDragDropEnabled interface we created earlier. I also bring the control to the front and invalidate it (you'll see why later.).

By now I'm sure you're either very confused, or very upset. I have yet to actually show you the elusive line:

MyButton.DoDragDrop((Button)MyButton, DragDropEffects.Move);
I assure you it's coming, and the rewards of doing it this way will become very clear! Thanks for putting up with me so far Smile | :) !

Method #9: RegisterControl: The heart (or better, "the brains") of the operation

As I've done above, here is the method, again exceptions were removed. I'll break it up into pieces to explain it better after you get the initial view:

public static void RegisterControl(Control _ctrl, bool _parent, bool _simple)
{
    // If this is a dragable control and not a parent...
    if ( ! _parent )
    {
        // If the control was registered as a non-simple control but
        // does not support IDragDropEnabled, throw an exception
        if ( ! _simple && ! (_ctrl is IDragDropEnabled) )
            // throw exception here

        // Try to get the format of the control
        GetDataFormat(_ctrl);
    }

    // Add the appropriate methods to the control
    if ( ! _parent )
    {
        if ( ! _simple )
        {
            _ctrl.MouseDown += new MouseEventHandler(_ctrl_MouseDown);
            _ctrl.Paint += new PaintEventHandler(_ctrl_Paint);
        }
        else
        {
            _ctrl.MouseDown += new MouseEventHandler(_ctrlSimple_MouseDown);
        }
    }
    else
    {
        // If the parent mappings contain this parent, then
        // add the appropriate methods and set the AllowDrop property
        if ( m_htDFParents.ContainsKey(_ctrl.GetType().ToString()) )
        {
            _ctrl.AllowDrop = true;
            _ctrl.DragOver += new DragEventHandler(_ctrlParent_DragOver);
            _ctrl.DragDrop += new DragEventHandler(_ctrlParent_DragDrop);
        }
        // Otherwise throw an exception
        else
        {
            // throw exception here
        }
    }
}

Section 1:

public static void RegisterControl(Control _ctrl, bool _parent, bool _simple)
The Control _ctrl is obviously the control to register. What is registering...? I'll tell you in a second. The bool _parent indicates if the _ctrl is specified in the DataFormatsParent section of the app.config or not: false means this control is defined in the DataFormats section only. bool _simple is one of the places I thought it read better to use the bool instead of the is check.

Section 2:

// If this is a dragable control and not a parent...
if ( ! _parent )
{
    // If the control was registered as a non-simple control but
    // does not support IDragDropEnabled, throw an exception
    if ( ! _simple && ! (_ctrl is IDragDropEnabled) )
        // throw exception here

    // Try to get the format of the control
    GetDataFormat(_ctrl);
}

If the bool parameters to the function were correct, then we use GetDataFormat to verify the control passed in is a mapped control. If not GetDataFormat throws an exception that will take us out of this method and back to the caller.

Section 3: Here is where things get exciting:

// Add the appropriate methods to the control
if ( ! _parent )
{
    if ( ! _simple )
    {
        _ctrl.MouseDown += new MouseEventHandler(_ctrl_MouseDown);
        _ctrl.Paint += new PaintEventHandler(_ctrl_Paint);
    }
    else
    {
        _ctrl.MouseDown += new MouseEventHandler(_ctrlSimple_MouseDown);
    }
}
else
{
    // If the parent mappings contain this parent, then
    // add the appropriate methods and set the AllowDrop property
    if ( m_htDFParents.ContainsKey(_ctrl.GetType().ToString()) )
    {
        _ctrl.AllowDrop = true;
        _ctrl.DragOver += new DragEventHandler(_ctrlParent_DragOver);
        _ctrl.DragDrop += new DragEventHandler(_ctrlParent_DragDrop);
    }
    // Otherwise throw an exception
    else
    {
        // throw exception here
    }
}

This is registering (promised I'd tell you). When a control is registered, depending on its type, event handlers are added to the control. Custom controls get the Mouse and Paint event handlers while simple controls only get the Mouse event handler. You'll see these events later. But look at the parent control... DragDropHandler will set AutoDrop to true for you, as well as implement the DragDrop and DragOver methods! This is the true power of the DragDropHandler. The basic functionality of the DragDrop is added to a control as it is registered, and is all controlled through dynamic binding in the app.config file!

Method #10: CanDropHere

I'll show you this one but we won't talk about it. It's very straight forward: Check the IDataObject being dragged over our parent control, and see if it is one of the mapped controls in app.config that can be dropped on the parent. If so return true otherwise return false.

internal static bool CanDropHere(Control _parent, IDataObject _ido)
{
    string szDataFormat = ""; // The data format of the control being dragged
    string szFoundDF = "";  // The data format that was accepted

    szDataFormat = DeriveUnknownFormat(_ido);

    // Couldn't find the data in the mappings?... return false
    if ( szDataFormat == "" )
        return false;

    try
    {
        // Attempt to map the parent type to an allowed DataFormat list
        // if it is null or fails, throw an exception
        string[] aszAllowedDF = 
            (string[])m_htDFParents[_parent.GetType().ToString()];

        if ( aszAllowedDF != null )
        {
            // Check each string in the Allowed DataFormats list
            // and if it matches the DataFormat from the IDataObject
            // then store it and break out of the search
            foreach ( string _szDF in aszAllowedDF )
            {
                if ( _szDF == szDataFormat )
                {
                    szFoundDF = _szDF;
                    break;
                }
            }
        }
    }
    catch 
    {
         // throw exception here
    }

    // If we found the DataFormat return true, this control can be dropped
    // here
    if ( szFoundDF == szDataFormat )
        return true;
    // DataFormat was not found so this parent can not host the IDataObject
    else
        return false;
}

Method #11: GetControl: The last one!

public static Control GetControl(IDataObject _ido, bool _dropping,
                                 bool _success)
{
    // Get the control using the custom DataFormat
    Control ctrl = null;
    string szDataFormat = DeriveUnknownFormat(_ido);

    if ( szDataFormat != "" )
    {
        ctrl = (Control)_ido.GetData(szDataFormat);

        // If dropping... alert the control unless this is a simple DragDrop
        if ( ! m_IsSimpleDrag )
        {
            if ( _dropping )
            {
                ((IDragDropEnabled)ctrl).IsDragging = false;
                ((IDragDropEnabled)ctrl).DropComplete(_success);
                ctrl.Invalidate();
            }
        }
    }

    // Return the control being DragDropped
    return ctrl;
}

This method gets the control from the IDataObject and will be called from the DragDrop event handler we'll see later. This is the final method. If bool _dropping is true, and this isn't a simple control, we call the IDragDropEnabled methods that indicate the end of a DragDrop operation. We send the success of the operation in case the control will need to react if the DragDrop failed. This usually only happens if the user canels the DragDrop. If dropping is false, then we assume the user was simply checking the data (as I will do shortly in the event handlers).

Event handlers

All of the events that are added to controls with a call to RegisterControl have been listed below. I'm not going to talk about them as they are straight forward, but I will say this... in the _ctrl_MouseDown you finally get to see this (only without the references to Button):

MyButton.DoDragDrop((Button)MyButton, DragDropEffects.Move);

about time huh?

private static void _ctrl_MouseDown(object sender, MouseEventArgs e)
{
    if ( e.Button == MouseButtons.Left )
        ((Control)sender).DoDragDrop(
            DragDropHandler.BeginDrag((Control)sender, true), 
            DragDropEffects.Move
        );
}
 
private static void _ctrlSimple_MouseDown(object sender, MouseEventArgs e)
{
    if ( e.Button == MouseButtons.Left )
        ((Control)sender).DoDragDrop(
            DragDropHandler.BeginSimpleDrag((Control)sender), 
            DragDropEffects.Move
        );
}
 
private static void _ctrl_Paint(object sender, PaintEventArgs e)
{
    Control ctrl = (Control)sender;

    // If we are dragging, then draw a bounding box around the control
    if ( ((IDragDropEnabled)ctrl).IsDragging )
    {
        Graphics grfx = e.Graphics;
        Pen pen = new Pen(Color.Black, 1);
        pen.DashStyle = DashStyle.Dot;

        grfx.DrawRectangle(pen, 0, 0, ctrl.Width - 1, ctrl.Height - 1);

        pen = null;
        grfx = null;
    }
}
 
private static void _ctrlParent_DragOver(object sender, DragEventArgs e)
{
    // If this is a valid control for this parent, allow it to move
    // freely over, otherwise disallow DragDrop operations
    if ( DragDropHandler.CanDropHere((Control)sender, e.Data) )
    {
        // cthis... this as a Control
        Control cthis = (Control)sender;

        // Set the DragDropEffect and get the control we are dragging
        e.Effect = DragDropEffects.Move;
        Control ctrl = DragDropHandler.GetControl(e.Data, false, true);

        // If this isn't an IDragDropEnabled control don't worry about
        // showing it's position
        if ( ! (ctrl is IDragDropEnabled) )
        {
            return;
        }

        // If this control is not part of the current "parent"
        // remove it from it's original parent and place it in
        // the current parent control collection
        if ( ! cthis.Controls.Contains(ctrl) )
        {
            ctrl.Parent.Controls.Remove(ctrl);
            ctrl.Parent = cthis;
            cthis.Controls.Add(ctrl);
        }

        // Set the control being dragged to be positioned to where we 
        // are dragging
        Point NewLocation = cthis.PointToClient(new Point(e.X, e.Y));
        ctrl.Left = NewLocation.X + 2;
        ctrl.Top = NewLocation.Y + 2;
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}
 
private static void _ctrlParent_DragDrop(object sender, DragEventArgs e)
{
    // Get the control being dropped
    Control ctrl = DragDropHandler.GetControl(e.Data, true, true);

    // Set the control being dropped to be positioned to where we 
    // did the drop
    Point NewLocation = ((Control)sender).PointToClient(new Point(e.X, e.Y));
    ctrl.Left = NewLocation.X + 2;
    ctrl.Top = NewLocation.Y + 2;
}

Wrap up

The only thing left is implementation of the DragDropHandler. All that work and this is all you'll have to do to use it (taken from the test app):

DragDropHandler.Initialize();
DragDropHandler.RegisterControl(this, true, true);
DragDropHandler.RegisterControl(panel1, true, true);
DragDropHandler.RegisterControl(NewCtrl, false, false);
DragDropHandler.RegisterControl(button1, false, true);

Assuming this was done in a form's Load event, the lines mean the following:

DragDropHandler.Initialize();
Initialize the DragDropHandler
DragDropHandler.RegisterControl(this, true, true);
Register the form as a parent control
DragDropHandler.RegisterControl(panel1, true, true);
Register the panel as a parent control
DragDropHandler.RegisterControl(NewCtrl, false, false);
Register the NewCtrl as a custom control
DragDropHandler.RegisterControl(button1, false, true);
Register the Button as a simple control

Start the test app, and you can drag and drop the two controls with just the code you see above.

Done

Thanks for taking the time to read my article. It's been a long road... I hope it was a helpful journey. I'll start working on my second article soon.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Ian Giffen
Web Developer
United States United States
Been programming for 16 years (I'm 23 now) mostly in Visual C++ and Windows (although there is some UNIX background).
 
Only been doing C# and .NET for the last 9 months.
 
Loves programming and computer games... loves programming computer games Smile | :) .
 
Been working as a healthcare programmer in Windows 98/NT/2000/XP for the last 4 years.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberBharat Mallapur11-Feb-14 18:09 
QuestionAwesome article, my vote of 5! Thanks a ton! :) PinmemberBharat Mallapur11-Feb-14 17:49 
Generalwhere is the part 2? PinmemberSouthmountain24-Jan-14 4:51 
GeneralAvoid flickering of the control [modified] PinmemberRoopaHP7-Jul-09 3:08 
GeneralRe: Avoid flickering of the control [modified] PinmemberBharat Mallapur11-Feb-14 17:09 
GeneralDrag and drop user controls Pinmembermadicemickael21-Apr-08 20:51 
QuestionMoving more then one object once? PinmemberSamliuk19-Dec-07 15:50 
GeneralCool article - write more! PinmemberMatanel Sindilevich18-Apr-06 0:18 
Generalstore location Pinmemberneon_code29-Mar-06 12:32 
AnswerRe: store location PinmemberBharat Mallapur11-Feb-14 17:42 
GeneralRe: store location PinmemberBharat Mallapur11-Feb-14 17:43 
GeneralGood one Pinmemberspvarapu20-May-05 18:56 
GeneralRe: Good one Pinmembercomputerguru9238218-Aug-05 19:21 
GeneralMastering DragDrop PinmemberBharti Ramji11-Feb-05 1:48 
QuestionCan the DataObject created be used for copying to the clipboard. Pinmemberksrajesh10-Feb-05 21:14 
GeneralRequested improvements... Pinmembernew_phoenix2-Dec-04 9:06 
GeneralRe: Requested improvements... PinmemberBharat Mallapur11-Feb-14 17:45 
QuestionMore robust? PinmemberMichael Bøcker-Larsen1-Dec-04 3:09 
GeneralTHAnk god! PinsussAnonymous26-Aug-04 11:55 
GeneralTiny bug PinmemberMOCKBA28-Apr-04 6:18 
GeneralRe: Tiny bug PinmemberKen_OPIS7-Jun-07 5:05 
Generalwrong? wrong. Pinmemberzilch19-Mar-04 2:47 
QuestionNo more click event ??? PinmemberDangerousFab11-Mar-04 2:49 
AnswerRe: No more click event ??? PinmemberBharat Mallapur11-Feb-14 17:48 
GeneralRequest for more info... Pinmembernew_phoenix14-Jan-04 7:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 17 Aug 2003
Article Copyright 2003 by Ian Giffen
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid