Introduction
This is a demo application that shows how to implement a drag and drop solution that gives you more positioning control. As long as you move an object within its container, there is no need to perform a true drag and drop operation. You simply change its location. This application shows how to automatically switch between the two methods.
To do some exact positioning, you might like to see the outline of the object while moving it. This application shows how to do this, as well. It's also possible to move the object with the arrow keys. To align objects, you would most likely need some grid to snap against. This, too, is included with the application. To give this demo more value, the drag-drop object is also resizable via a corner grip.
This has all been done in Visual Studio 2005 and the .NET 2.0 Framework.
Background
I started to create an application containing a toolbox with different objects that the user would drag to a form or to other container objects. At first I tried to only use true drag-drop, but this approach gave me a very rough and simple application. Something more sophisticated was needed in order to give the user some more control.
How to get started
- Download the latest SmartDragDrop demo application to get a feeling for what this is!
- Download the latest SmartDragDrop source project to have a look at the implementation and to copy the code parts that you like into your own project.
- Read the messages at the end of this page!
Using the code
The project consists of four classes: MyForm.cs, MyUserControl.cs, Outline.cs and the standard Program.cs. The Program
class creates two MyForm
windows to be used as a playground. One MyUserControl
is then created for use as a drag-drop object. It is placed in the second window. When we move or resize the the drag-drop object, an Outline
object is used to show the mouse movements.
The MyForm class implements
- Accept drops of
MyUserControl
objects. First we have to set the Form property AllowDrop
to true
to make our Form accept any drop object at all. We then add the two events DragEnter
and DragDrop
. The DragEnter
event is used to reject objects we don't support. The DragDrop
event is used to recreate and add our MyUserControl
object at the correct location. - A grid for exact positioning of
MyUserControl
objects is needed. The method DisplayOrHideGrid
draws a grid on our MyForm
window if the grid checkbox on the Form is checked. The Graphics
class is used to draw the grid on our Form. The public method SnapToGrid
is used both locally and by the MyUserControl
class to recalculate the location to snap to the grid.
The MyUserControl class implements
- Resize by corner grip:
UserControl
s don't have any resize possibilities like SizeGripStyle
for Forms. To obtain this feature, a transparent corner grip picture is used with PictureBox
. The MouseDown
and MouseUp
events are used to simply resize our MyUserControl
object. - Both fake and real drag-drop while moving: By fake drag-drop, I mean simply using the
MouseDown
and MouseUp
events to move our MyUserControl
object. The method IsCursorInside
is used in the MouseMove
event to check if the mouse cursor leaves our MyForm
. If so, a true drag-drop operation is started. The public property MouseDownStartPoint
is used in MyForm
to preserve the mouse pointer's location when the drop operation is done. That is, if I start to drag at the center, I expect to find the cursor at the center when I drop my object. - High resolution move via the arrow keys: To give the user the ability to align an object, the arrow keys will move the
MyUserControl
object one pixel. We need to override the IsInputKey
event to be able to catch arrow key events in a UserControl
object. - Show outline of itself during move or resize: The static methods in Outline.cs create a cursor object that is used during a move. A transparent form is used. This gives the user better control during a move or resize.
- The possibility to snap to a parent grid: Whenever a
MyUserControl
object is moved (not dropped), the MyForm
method SnapToGrid
is called to recalculate the location to snap to grid.
The Outline class implements
- A transparent form used as cursor: All methods used to manipulate the
Outline
object in this class are static. This makes the use simple and ensures that only one cursor object is used. Since we only have one mouse and can only perform one move or resize at the time, there is no need to create several cursor objects. This technique saves a lot of memory and CPU compared with the previous solution of creating a true cursor.
Discussion
This is all implemented as a sample application. How can this be made useful in any application? We could inherit from our two classes MyForm
and MyUserControl
. The drawback is that then we would be stuck using only Form
as a container and UserControl
as a drag object. We could also create a special SmartDragDrop
class to be used in the UserControl
class. However, it's not that easy to make this a clean solution either.
Furthermore, how should mouse events be handled? There are some not-so-nice connections between our MyForm
and MyUserControl
classes, as well. The child should not need to know of its parent, but this is the case when SnapToGrid
is called. How can this be made better? Use interfaces, perhaps?
Please improve the code and share your ideas!
Points of interest
These questions are addressed in this article:
- How to drag-drop user-defined classes
- How to show the outline of some objects during move or resize
- How to create a resize grip for the
UserControl
class - How to draw a grid on a Form
History
- 2007-06-24: Submitted this article and code 1.0. Please don't forget to vote for this article; I really like to know and learn!
- 2007-07-13: Bug corrected: In the event method
userControl_MouseUp
in MyUserControl.cs, the MouseMove
event is now unregistered. Dispose of cursor object is improved. - 2007-07-16: New cursor solution: We now use a transparent form, Outline.cs, instead of creating a true cursor object. This saves a lot of memory and CPU, especially if large objects are moved. The cursor is now also used at resize. There is no fault on the previous release 1.1. You can get it here; it might be as good as this in some applications.