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 Im 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 doesnt 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 youre 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
Im 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:
-
Adding a call to the OLEDrag method
within the MouseDown event for the control that will be acting
as the source.
-
As a consequence of the OLEDrag method the OLEStartDrag event is fired. OLEDrag
doesnt 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)
Well 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.
-
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.
-
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.
-
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)
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 youre
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 Ive 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
dont 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:
Public gNotifyIconData As NOTIFYICONDATA
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
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
doesnt 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