Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple Drag And Drop How To Example

0.00/5 (No votes)
10 Dec 2004 1  
The basics, along with some things I learned along the way.

Introduction

Believe it or not, I've never written an application that's needed to use drag & drop. I'm working on something now where I'd like to use that feature, and so I went searching for a simple example of how d&d works. Amazingly, I couldn't find one--all the examples I found seemed to deal with tree controls or didn't deal specifically with how to use d&d. While simple, there are some things that I discovered when writing a little demonstration program, so I decided to write a beginner's tutorial on d&d.

Creating A Simple Image Viewer

I've decided to illustrate d&d with a simple, single image viewer capable of viewing JPG, BMP, and PNG files.

The STAThread Attribute

The very first step is to ensure that your Main method is designated with the [STAThread] attribute. You won't be able to do drag and drop operations if this is missing (in fact, the program will display an error message if you try to run it--not by the program's doing, but by .NET's).

[STAThread]
static void Main() 
{
  Application.Run(new Form1());
}

The AllowDrop Property

The first step is to identify the control for which you want to enable d&d and set the AllowDrop property to true. This is usually the application's Form control. For a Form, this is a simple matter of setting the AllowDrop property in the designer to true:

But what if you want to enable d&d for a particular control, say a PictureBox? Oddly enough, this property, defined in the base Control class, isn't available in the properties of the PictureBox:

Nor, if you have:

PictureBox pb=new PictureBox();

and you start typing:

pb.Allow

will Intellisense auto-complete. Yet, this doesn't mean that you can't manually assign this property! It seems like the PictureBox is the only control that doesn't expose this property, for no reason that I found.

In any case, since the demonstration application doesn't do anything else besides display an image, it makes sense in this case to enable d&d for the form rather than just the PictureBox.

The Drag And Drop Events

The next step is to wire-up the d&d events (note that the PictureBox control doesn't show these events either in the designer, but yes, they are there and you can use them).

Since we're adding d&d capability to the form, we can use the designer to add our methods for the four events we're almost always interested in:

  • DragEnter
  • DragOver
  • DragDrop
  • DragLeave

The DragEnter Event

This is simple enough--this event fires when the mouse enters a control in which d&d is enabled. It gives you the opportunity to:

  • inspect the DragEventArgs to see the DataObject being dragged onto your control is something you can accept.
  • perform any initialization you may need to handle the data.
  • set the Effect property to indicate whether the item will be copied or moved to the control.
DragEventArgs

The DragEventArgs contains several properties that we are interested in:

  • AllowedEffect - The operations that are allowed by the source of the drag event. What we're primarily interested in is whether the source allows copying or moving.
  • Data - The IDataObject that actually contains the data in which we are interested.
  • Effect - the target effect that we want to set for this object.
  • X, Y - the mouse position. This is in screen coordinates.
  • KeyState - can be used to inspect the state of the SHIFT, CTRL, and ALT keys as well as mouse buttons.

In my example, I'm interested in two things:

  • can the object be copied into my application?
  • is the data in a format that my application accepts?

and in particular, the application accepts images dragged from Explorer, so I'm interested in the filename. As a result, the OnDragEnter method starts like this:

private void OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
  Debug.WriteLine("OnDragEnter");
  string filename;
  validData=GetFilename(out filename, e);

where all the real work is done in the GetFilename method:

protected bool GetFilename(out string filename, DragEventArgs e)
{
  bool ret=false;
  filename=String.Empty;

  if ( (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy)
  {
    Array data=((IDataObject)e.Data).GetData("FileName") as Array;
    if (data != null)
    {
      if ( (data.Length ==1) && (data.GetValue(0) is String) )
      {
        filename=((string[])data)[0];
        string ext=Path.GetExtension(filename).ToLower();
        if ( (ext==".jpg") || (ext==".png") || (ext==".bmp") )
        {
          ret=true;
        }
      }
    }
  }
  return ret;
}

First, I test whether the source allows copying of the data. Second, I get the data based on the filename. When dropping an image, there are several formats that the data supports, which can be inspected in the debugger:

We see that "FileName" is one of the acceptable formats, so I'm going to request that data in the FileName format. However, we're not done yet. The object returned is actually an Array, as evident in the watch window:

So, what I want to do is:

verify that there is only one datum being dropped (rather than a collection of filenames) and, while probably overkill, I want to verify that the data type of this one and only one entry is a string. Once I've done all this verification, I can get the filename, extract the extension, and confirm that it's either ".jpg", ".bmp", or ".png". If all is well, the method returns true, indicating success, along with the filename.

Managing The Data

Now that we have the filename, what do we do with it? In my application, I'd like to display a small image to give the user some feedback, showing the picture about to be dropped onto the application. This is a good example that illustrates that sometimes you'll have to manage the data intelligently. I wanted to deal with two problems:

  • what if the image being dragged is already the same image the application is displaying?
  • loading the image into the application is a time consuming process, and I don't want to bog the application's main thread down by loading it.

The rest of the OnDragEnter method handles this logic, in addition to determining whether we set the drag effect to Copy. Here's the complete method:

private void OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
  Debug.WriteLine("OnDragEnter");
  string filename;
  validData=GetFilename(out filename, e);
  if (validData)
  {
    if (lastFilename != filename)
    {
      thumbnail.Image=null;
      thumbnail.Visible=false;
      lastFilename=filename;
      getImageThread=new Thread(new ThreadStart(LoadImage));
      getImageThread.Start();
    }
    else
    {
      thumbnail.Visible=true;
    }
    e.Effect=DragDropEffects.Copy;
  }
  else
  {
  e.Effect=DragDropEffects.None;
  }
}

Notice that I first check whether image being displayed is the one again being dragged onto the application. If so, we have the image and we're all set to display the thumbnail (there's a potentially small bug in this code--can you figure it out?)

If it's a new image, I'm going to start up a thread that actually loads the image and puts it into the thumbnail when it's ready. Let's look at that thread and related functions:

public delegate void AssignImageDlgt();

protected void LoadImage()
{
  nextImage=new Bitmap(lastFilename);
  this.Invoke(new AssignImageDlgt(AssignImage));
}

protected void AssignImage()
{
  thumbnail.Width=100;
  // 100 iWidth

  // ---- = ------

  // tHeight iHeight

  thumbnail.Height=nextImage.Height * 100 / nextImage.Width;
  SetThumbnailLocation(this.PointToClient(new Point(lastX, lastY)));
  thumbnail.Image=nextImage;
}

The image is loaded into nextImage. Because we're manipulating a control that is being managed in the application thread, I'm using the Invoke method to execute a delegate so that the actual manipulation of the thumbnail control is done in the application thread rather than our worker thread. This is the "safe" way to manipulate UI objects.

The DragOver Event

Something I didn't realize about the DragOver event is that it fires repeatedly, even if I don't move the mouse. Depending on what our application does, there may be considerable time taking up in updating the visual cues when the user moves the mouse. We certainly don't want to waste CPU time when the user hasn't moved the mouse. Therefore, I'm testing if the position of the mouse has changed:

private void OnDragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
  Debug.WriteLine("OnDragOver");
  if (validData)
  {
    if ( (e.X != lastX) || (e.Y != lastY) )
    {
      SetThumbnailLocation(this.PointToClient(new Point(e.X, e.Y)));
    }
  }
}

Again, we're calling a separate method to actually set the thumbnail position:

protected void SetThumbnailLocation(Point p)
{
  if (thumbnail.Image==null)
  {
    thumbnail.Visible=false;
  }
  else
  {
    p.X-=thumbnail.Width/2;
    p.Y-=thumbnail.Height/2;
    thumbnail.Location=p;
    thumbnail.Visible=true;
  }
}

This method determines the thumbnail position and its visibility, based on whether the nextImage exists (it won't exist until the worker thread has finished loading it).

The DragLeave Event

When the mouse leaves our application, we want to hide any visual cues that are currently being displayed:

private void OnDragLeave(object sender, System.EventArgs e)
{
  Debug.WriteLine("OnDragLeave");
  thumbnail.Visible=false;
}

The DragDrop Event

In this event handler, we want to verify that the data being dropped is valid. This is easy, using the validData flag that was set with the OnDragEnter event handler. But if the worker thread hasn't finished loading the image, we need to wait. And because the worker thread uses the Invoke method, we can't just use the Join method, putting the application in a wait state until the worker thread is complete. Doing so prevents the Invoke method from executing. Therefore, we have to test whether the thread is still alive and tell the application to continue processing events:

private void OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
  Debug.WriteLine("OnDragDrop");
  if (validData)
  {
    while (getImageThread.IsAlive)
    {
      Application.DoEvents();
      Thread.Sleep(0);
    }
    thumbnail.Visible=false;
    image=nextImage;
    AdjustView();
    if ( (pb.Image != null) && (pb.Image != nextImage) )
    {
      pb.Image.Dispose();
    }
    pb.Image=image;
  }
}

After we are ensured that the worker thread has terminated, I'm adjusting the application's PictureBox so that it displays the image in the correct proportions. I also manually dispose of any previous image to free up the image resources now, rather than waiting for garbage collection to do so. But I have to make sure that if the user drags the same image as being viewed into the application, I don't actually dispose of that image, as it's the current image!

Conclusion

While drag and drop is itself simple, some planning has to go into:

  • the visual cues that you provide the user.
  • how to work with those visual cues if they are initially time consuming to set up.
  • how you manage those visual resources during the drag and drop process.
  • how to optimize for repetitive actions.
  • how to clean up after the drop is made.

These are things that I think are useful to bring to a beginner's attention. It's not so simple after all!

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