Drag and Drop


No matter what kind of controls you're using or building, often you'd like to enable the user to drag data from one to another. This communication protocol, known as drag and drop , has long been standardized and is fully supported in WinForms for both targets and sources.

The Drop Target

Adding drag and drop to your application involves two elements: the drop target and the drop source. First, you must have a control that supports having things dragged and dropped onto it. This kind of control is known as the drop target . You designate your target by setting the AllowDrop property of the control to true.

Next, you make the target control subscribe to one or more of the drag-and-drop events:

  • DragEnter is fired when the mouse enters the area of a control containing drag-and-drop data. It is used by the target to indicate whether it can accept the data.

  • DragOver is called as the user hovers the mouse over the target.

  • DragLeave is called when the mouse leaves the area of a control containing the drag-and-drop data.

  • DragDrop is called when the user drops the data onto the target.

All target controls must implement the DragEnter event, or else they won't be able to accept any dropped data. The DragEnter event comes along with an instance of the DragEventArgs class, which gives the target information about the data:

 class DragEventArgs : EventArgs {   // Properties  public DragDropEffects AllowedEffect { get; }   public IDataObject Data { get; }   public DragDropEffects Effect { get; set; }  public int KeyState { get; }   public int X { get; }   public int Y { get; } } 

A target control's DragEnter event handler checks the Data property to see whether it can be accepted when dropped. The object returned from the Data property implements IDataObject to make that determination possible:

 interface IDataObject {   // Methods   public virtual     object GetData(string format, bool autoConvert);   public virtual object GetData(string format);   public virtual object GetData(Type format);   public virtual void     SetData(string format, bool autoConvert, object data);   public virtual void SetData(string format, object data);   public virtual void SetData(Type format, object data);   public virtual void SetData(object data);   public virtual     bool GetDataPresent(string format, bool autoConvert);   public virtual bool GetDataPresent(string format);   public virtual bool GetDataPresent(Type format);   public virtual string[] GetFormats(bool autoConvert);   public virtual string[] GetFormats(); } 

The IDataObject interface is actually defined from .NET's Component Object Model (COM) cousin, where drag and drop was born. WinForms continues to work with the COM-based protocol so that managed and unmanaged applications can participate in drag-and-drop operations between each other.

Furthermore, the COM-based protocol itself is based on the Windows convention for the way the Clipboard works. All data passed around using drag and drop is represented in Clipboard formats. Some Clipboard formats are customized for your own application, and others are well known to allow Clipboard and drag-and-drop operations between applications. The format strings used to specify the well-known formats are predefined as static fields of the DataFormats class:

 class DataFormats {   // Fields   public static readonly string Bitmap;   public static readonly string CommaSeparatedValue;   public static readonly string Dib;   public static readonly string Dif;   public static readonly string EnhancedMetafile;   public static readonly string FileDrop;   public static readonly string Html;   public static readonly string Locale;   public static readonly string MetafilePict;   public static readonly string OemText;   public static readonly string Palette;   public static readonly string PenData;   public static readonly string Riff;   public static readonly string Rtf;   public static readonly string Serializable;   public static readonly string StringFormat;   public static readonly string SymbolicLink;   public static readonly string Text;   public static readonly string Tiff;   public static readonly string UnicodeText;   public static readonly string WaveAudio;   // Methods   public static DataFormats.Format GetFormat(string format);   public static DataFormats.Format GetFormat(int id); } 

In addition to support for well-known data formats, .NET provides a conversion from some .NET types, such as String, to a corresponding format string, such as DataFormats.Text. Using a format string and the GetDataPresent method of the IDataObject, the target can determine whether the type of data being dragged is acceptable for a drop:

 void textBox3_DragEnter(object sender, DragEventArgs e) {  // Could check against DataFormats.Text as well  if(  e.Data.GetDataPresent(typeof(string))  ) {  e.Effect = DragDropEffects.Copy;  }   else {  e.Effect = DragDropEffects.None;  } } 

GetDataPresent checks the format of the data to see whether it matches the Clipboard format (or a .NET type converted to a Clipboard format). To find out whether the data is in a convertible format, you can call the GetFormats() function, which returns an array of formats. Calling any of the IDataObject methods with the autoConvert parameter set to false will disable anything except a direct match of data types.

If the data is acceptable, the DragEnter event handler must set the Effect property of the DragEffectArgs object to one or more flags indicating what the control is willing to do with the data if it's dropped, as determined by the flags in the DragDropEffects enumeration:

 enum DragDropEffects {   Copy, // Take a copy of the data   Move, // Take ownership of the data   Link, // Link to the data   Scroll, // Scrolling is happening in the target   All, // All of the above   None, // Reject the data } 

If a drop is allowed and it happens while the mouse is over the target, the target control will receive the DragDrop event:

 void textBox3_DragDrop(object sender, DragEventArgs e) {   textBox3.Text = (string)  e.Data.GetData(typeof(string))  ; } 

When you implement the DragDrop handler, the Effect property of the DragEventArgs will be the effect that the source and target agreed on, should multiple effects be allowed. Retrieving the data is a matter of calling GetData ”using either a DataFormat format string or a .NET Type object ”and casting the result.

Drop Targets and COM

When you enable a control as a target, you open yourself up to the possibility that the user will receive the cryptic message shown in Figure 8.24.

Figure 8.24. Cryptic Drag-and-Drop Error Message

Because drag and drop is a feature provided using COM, COM must be initialized on the UI thread for drag and drop to work. Although .NET is smart enough to lazily initialize COM on the running thread as needed, for reasons of efficiency it picks the UI- hostile Multi-Threaded Apartment (MTA) for the thread to join unless told to do otherwise . Unfortunately, for drag and drop, the UI thread must join the Single-Threaded Apartment (STA). To ensure that that's the case, always double-check that the Main function on all your WinForms applications is marked with STAThreadAttribute:

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

(Note that STAThread is a C# shortcut for STAThreadAttribute.)

By default, any VS.NET-generated code will contain this attribute on the Main function (even Console applications), but just in case it somehow goes missing, this is the first thing to check when you see the message box from Figure 8.24.

The Drop Source

With the target implemented, what's left is initiating a drag-and-drop operation from the drop source using the DoDragDrop method of the Control class. DoDragDrop is almost always placed in the handler for a MouseDown event:

 void button3_MouseDown(object sender, MouseEventArgs e) {  // Start a drag-and-drop operation   DoDragDrop(button3.Text, DragDropEffects.Copy);  } 

The DoDragDrop method's first parameter is the data, which can be any object. The second parameter is a combination of the drag-and-drop effects that the source supports. For example, Figure 8.25 shows the button initiating the drag and drop.

Figure 8.25. A Drag-and-Drop Operation Showing the None Effect

As the drag-and-drop operation progresses, the DoDragDrop method tracks the mouse as it moves over controls, looking to see whether they are potential drop targets (as set with the AllowDrop property) and firing the DragEnter event to see whether potential targets can accept the data. Depending on whether the target can accept the data, DoDragDrop sets the cursor based on the current effect indicated by the target to communicate to users what would happen if they were to drop at any point. Notice in Figure 8.25 that the button itself is not a drop target, so the cursor indicates that a drop on the button would have no effect.

On the other hand, when the data is dragged over a text box that is enabled to accept string data, the DragEnter event is fired, and the control indicates the effect that it will support. This causes the cursor to be updated appropriately, as shown in Figure 8.26.

Figure 8.26. Drop Target Indicating the Copy Effect

When the user releases the mouse button, dropping the data, the DragDrop event is fired on the target, and the target accepts the data, as shown in Figure 8.27.

Figure 8.27. Completed Drag-and-Drop Copy Operation

When the drag and drop is completed, the DoDragDrop method returns with the effect that was performed. If the effect was a Move, the source knows to remove its copy of the data.

Supporting Multiple Effects

If you want to support more than one effect, such as Copy and Move, you can check the KeyState property of the DragEventArgs structure. KeyState is a set of flags that determines which keys are being pressed. By Windows convention, the lack of modifier keys indicates a Move, the Ctrl modifier indicates a Copy, and the Ctrl+Shift modifier indicates a Link (which your application may or may not support).

Unfortunately, the KeyState property is an integer, and WinForms provides no data type for checking the flags. So you'll need your own, such as this KeyState enumeration: [8]

[8] The FlagsAttribute makes instances of the KeyState enumeration show up in a friendlier manner when debugging, such as "LeftMouse, CtrlKey" instead of "9."

 // KeyState Values (not available in WinForms) [FlagsAttribute] enum KeyState {   LeftMouse = 1,   RightMouse = 2,   ShiftKey = 4,   CtrlKey = 8,   MiddleMouse = 16,   AltKey = 32, } 

Because users may change the keys they're pressing at any time to get the effect they're looking for, you will want to notify the drag operation of which operation they are trying to do while the mouse is hovering . To do this you check the DragEnter and DragOver events:

 void textBox3_DragEnter(object sender, DragEventArgs e) {  SetDropEffect(e);  } void textBox3_DragOver(object sender, DragEventArgs e) {  SetDropEffect(e);  } void SetDropEffect(DragEventArgs  e) {  KeyState keyState = (KeyState)e.KeyState;  // If the data is a string, we can handle it   if( e.Data.GetDataPresent(typeof(string)) ) {  // If only Ctrl is pressed, copy it   if( (keyState & KeyState.CtrlKey) == KeyState.CtrlKey ) {   e.Effect = DragDropEffects.Copy;   }   else { // Else, move it   e.Effect = DragDropEffects.Move;   }  }   // We don't like the data, so do not allow anything   else {     e.Effect = DragDropEffects.None;   } } 

The SetDropEffect method makes sure that the data is a string because that is all we are expecting. If it finds a string, it tests to see whether the Ctrl key is pressed. If so, it specifies that the operation is a copy; otherwise, it specifies that it will do a move. Figure 8.28 shows what the drag operation now looks like over the text box without the Ctrl key pressed, indicating a move effect.

Figure 8.28. Dragging without Ctrl, Causing a Move

Figure 8.29 shows the same operation with the Ctrl key pressed, indicating a copy effect.

Figure 8.29. Dragging with Ctrl, Causing a Copy

In our sample, when the user drops the data with no modifiers, indicating a move, the text is removed from the button when it drops the text to the text box, as shown in Figure 8.30.

Figure 8.30. After a Drag-and-Drop Move Operation

To handle multiple effects in the drop source, you must specify which effects are allowed and check the resulting effect after the DoDragDrop method returns:

 void button3_MouseDown(object sender, MouseEventArgs e) {  DragDropEffects effect =  DoDragDrop(       button3.Text,  DragDropEffects.Copy   DragDropEffects.Move  );  // If the effect was move, remove the text of the button   // If the effect was a copy, we don't have anything to do   if( effect == DragDropEffects.Move ) {  button3.Text = "";  }  } 

Drag and drop is a great way to allow your mouse-oriented users to directly manipulate the data that your application presents without an undue development burden on you.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net