Monday, 08 September 2008
PicoSearch

Directly Supporting The Mouse

This column appeared in the February 2000 edition of EXE. It reviews the programmatic steps required to implement drag and drop into an application.


Over the past few columns I've been giving a fair bit of coverage to the newer technologies that are of direct use to the Visual Basic developer and, judging by the feedback I get from some of you, this seems to be a popular approach to take. However in the interests of balance I feel that it is important to occasionally review existing technologies with the core product itself. To this end I would like to discuss the mechanics of drag and drop functionality within an application. Because we are discussing interaction with the Windows interface I’m also going to take the opportunity to mention adding support for the Windows system tray into an application.

My honest feeling is that drag and drop doesn’t really get used a great deal in many applications when perhaps it could be. After all it is a standard feature within many – not just Microsoft – off-the-shelf products. If you’re a Visual Basic developer of several years standing then it might well be that you tried it within an application a few years back but have never really done anything with it since. If this is the case then you should be made aware that a new implementation of drag and drop was introduced with Visual Basic 5 called OLE drag and drop, which is what I’m covering here.

Drag and drop is, of course, concerned with the movement of data from one control to another. Visual Basic provides the means to perform this operation with either as little effort on your part as possible, the automatic way, or the manual way which gives you a greater amount of programmatic control over what is going on. The automatic way is very straightforward in that no particular coding is required. This can be proven by pasting a RichTextBox onto a form in a Visual Basic project, setting the OLEDragMode and OLEDropMode properties to automatic, and running the project. Then try dragging some text from a Word document and you should see it all work as you would expect.

Manual mode

Manual mode gives you a greater degree of control over what actually happens to the data that is being dragged. The manual method of implementing drag and drop means that it is necessary to write code into several of the events that are listed in table 1.

Name Source/Target Description
OLECompleteDrag
Source
Denotes the end of the drop operation for the source
OLEGiveFeedback
Source
Offers the facility to customise the drag icon feedback
OLEStartDrag
Source
Fired in response to the OLEDrag method
OLESetData
Source
Fired in the source control when the OLEDragDrop event occurs in a target
OLEDragDrop
Target
Denotes the end of the drop operation for the target
OLEDragOver
Target
Occurs when a drag enters the area occupied by the target

Table 1: Standard OLE drag and drop events

The sequence that takes place in order for this to work is:

  1. Adding a call to the OLEDrag method within the MouseDown event for the control that will be acting as the source.

  2. As a consequence of the OLEDrag method the OLEStartDrag event is fired. OLEDrag doesn’t actually take any parameters so it is necessary to configure the operation through this event. The parameters for this event are

    <controlname>_OLEStartDrag(Data As DataObject, AllowedEffects As Long)

    We’ll come back to the DataObject shortly, but the AllowedEffects parameter determines which operations are supported. You might wish to allow only a copy operation, or a move operation, or both. There is also the option to disallow either operation if you need to.

  3. As the user moves the mouse over target controls the OLEDragOver event is fired in each case. This provides the option of changing the AllowedEffects value if necessary. A similar event, OLEGiveFeedback, also allows for the AllowedEffects to be altered. Both give you the opportunity to invoke your own custom cursors if you want.

  4. When the user finally releases the mouse button over a valid target the OLEDragDrop event is fired within the target control. At this point you can do whatever is necessary to tidy up the operation.

  5. Finally, the OLECompleteDrag event is fired within the source control to inform it that the operation has completed.

DataObject

The DataObject object, whose properties and methods are shown in table 2, is the means by which data is transferred between the source and the target controls.

Name Type Description
Files
Property
Collection of filenames (used to receive data that is dragged from the Windows Explorer)
Clear
Method
Removes existing data from DataObject
GetData
Method
Returns the data held within DataObject
GetFormat
Method
Used to ask whether a certain format of data is present
SetData
Method
Places new data into DataObject, and specifies the format

Table 2: DataObject properties and methods

It is automatically created just before the OLEStartDrag event fires, and is passed around as a parameter between several of the events shown in table 1. You would typically start to use DataObject in the OLEStartDrag event by calling the SetData method, which is defined as

DataObject.SetData Value, Format

The Value that you supply can be pretty much whatever you want, including bitmaps and metafiles, but you need to specify the format in the second parameter. For example:

Private Sub Text1_OLEStartDrag(Data As DataObject, _
    AllowedEffects As Long)
  Data.SetData Text1.SelText, vbCFText
  AllowedEffects = vbDropEffectMove
End Sub

As the user moves the mouse over each candidate target control the OLEDragOver event is fired. This gives you the opportunity to examine the nature of the data that is currently being dragged so that you can decide whether it can be accepted or not. Calling the GetFormat method of the DataObject object and manually comparing the result with the format of the current control can achieve this. For instance the image control supports drag and drop but of course it deals with bitmap data rather than text. Therefore if it identifies the incoming data as being of an unsupported nature then it should deny the operation. This can be coded in simple terms as:

Private Sub Image1_OLEDragOver(Data As DataObject, Effect As _
    Long, Button As Integer, Shift As Integer, X As Single, _
    Y As Single, State As Integer)

  ‘ Check that format is a bitmap, else disallow
  If Data.getFormat(vbCFBitmap) Or Data.GetFormat(vbCFDIB) Then
    Effect = vbDropEffectMove
  Else
    Effect = vbDropEffectNone
  End If

End Sub

Once the user finally releases the mouse button we need to complete the operation. Because we have decided to manually manipulate the data it is necessary to add it to the new control and, in the event of a move rather than a copy, also delete it from the source control. The data is extracted via a call to GetData, which has the format of

<target control.property> = Data.GetData(format)

So if we were dragging data from one text box to another we would finish up with:

Private Sub Text2_OLEDragDrop(Data As DataObject, _
    Effect As Long, Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
  Text2.Text = Data.GetData(vbCFText)
End Sub

Private Sub Text1_OLECompleteDrag(Effect As Long)
  If Effect = vbDropEffectMove Then
    Text1.SelText = ""
  End If
End Sub

In the Text1_OLECompleteDrag call above the Effect value as it finally stands contains the final outcome of the drag/drop transaction. If you had catered for the user pressing the Ctrl button in order to transform a move operation into a copy then the Effect would have been altered accordingly. You can therefore conditionally remove the selected data from the source control if necessary.

Frankly these code fragments that I have shown above are the most basic that you need to get the job done – you might well want to tweak them a little bit to get the effect that you want. This is certainly the case if you are dragging from a text box in manual mode so you will find that you will need to experiment a bit in order to get the best mix. If you are getting started with this for the first time then it is much easier to use automatic mode so it would be easier to try this approach first. If you’re looking to give your applications a little bit of extra polish then its well worth taking the time to implement this feature. Once you get going it should become second nature.

System tray support

One topic that I’ve been looking to include in this column for a while would seem to be appropriate here, and that is concerning system tray icons. The system tray is the area in the bottom right of the Windows screen, and is specifically designed to allow applications that don’t actually have an interface to make their presence known. In most cases there is a need for some kind of configuration support so a right-click will normally invoke a popup menu. This is quite a sensible approach because it prevents users from having to resort to the control panel, which of course is second nature to us techies but is a daunting journey into the unknown for a good many users.

Figure 1: The Windows system tray

The system tray is manipulated by just one shell function called Shell_NotifyIconA which exists within the shell32.dll file that resides in the Windows system32 directory. In order to use this from Visual Basic you should declare it as:

Public Declare Function Shell_NotifyIcon Lib "shell32" Alias "Shell_NotifyIconA" (ByVal dwMessage As Long, pnid As NOTIFYICONDATA) As Boolean

And you will also need to declare a data structure that goes with it, which is:

Public Type NOTIFYICONDATA
  cbSize As Long
  hWnd As Long
  uId As Long
  uFlags As Long
  uCallbackMessage As Long
  hIcon As Long
  szTip As String * 64
End Type

To use it you will need to have a window from which to provide a handle, and you will need to assign a 16x16 icon to the window. I would suggest that the best way to implement this might be to create a splash screen for your application which is then hidden rather than destroyed after 3 seconds or so. As long as the window still exists there will be a valid handle to provide for the system tray. Calling the Shell_Notify function is very straightforward, but first it is necessary to populate the NOTIFYICONDATA structure, as shown:

‘ Declaration section
Public gNotifyIconData As NOTIFYICONDATA

‘ Sub code
With gNotifyIconData
  .cbSize = Len(gNotifyIconData)
  .hWnd = frmSplash.hWnd
  .uId = vbNull
  .uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
  .uCallbackMessage = WM_MOUSEMOVE
  .hIcon = frmSplash.icon
  .szTip = "Initialising..." & vbNullChar
End With

The system tray icon is then created with a simple call to

Shell_NotifyIcon NIM_ADD, gNotifyIconData

It is necessary to explicitly delete the icon with the following call when you are done otherwise it will remain shown within the system tray after the application has terminated, which you can do like this:

Shell_NotifyIcon NIM_DELETE, gNotifyIconData

The uCallbackMessage parameter within the NOTIFYICONDATA structure tells the routine which piece of code to call when it has some activity to convey. In this case the WM_MOUSEMOVE identity is provided, which of course maps to the MouseMove event for the form. This message trap is where you can capture a right mouse click and thus display a popup menu.

Private Sub Form_MouseMove(Button As Integer, Shift As _
Integer, x As Single, y As Single)

  Dim Result As Long
  Dim lMessage As Long

  ' the value of X will vary depending upon the
  ' scalemode setting

  If Me.ScaleMode = vbPixels Then
    lMessage = x
  Else
    lMessage = x / Screen.TwipsPerPixelX
  End If

  If WM_RBUTTONUP = lMessage Then
    Me.PopupMenu Me.mnuPopup
  End If

End Sub

Remember though, you only need to do this if your application doesn’t really require an interface but does need to provide the user with a quick means of accessing a configuration or action menu.


In this article there are various constants referred to. All of the items that you should need for the declarations section are listed here.

Public Type NOTIFYICONDATA
cbSize As Long
hWnd As Long
uId As Long
uFlags As Long
uCallbackMessage As Long
hIcon As Long
szTip As String * 64
End Type

Public Const NIM_ADD = &H0
Public Const NIM_MODIFY = &H1
Public Const NIM_DELETE = &H2

Public Const NIF_MESSAGE = &H1
Public Const NIF_ICON = &H2
Public Const NIF_TIP = &H4

Public Const WM_MOUSEMOVE = &H200
Public Const WM_LBUTTONDOWN = &H201
Public Const WM_LBUTTONUP = &H202
Public Const WM_LBUTTONDBLCLK = &H203
Public Const WM_RBUTTONDOWN = &H204
Public Const WM_RBUTTONUP = &H205
Public Const WM_RBUTTONDBLCLK = &H206

Public Declare Function Shell_NotifyIcon Lib "shell32" Alias "Shell_NotifyIconA" (ByVal dwMessage As Long, pnid As NOTIFYICONDATA) As Boolean

Public gNotifyIconData As NOTIFYICONDATA

Copyright ©2002 Jon Perkins I, Jon Michael Perkins, hereby assert and give notice of my right under section 77 of the Copyright, Designs, and Patents Act 1988 to be identified as the author of the foregoing article.