Windows Forms, Custom Controls Part 1: Mastering DragDrop






4.90/5 (55 votes)
Aug 17, 2003
15 min read

186913

3115
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 :) )
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 Hashtable
s 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 :)!
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.