What is this article about:
Many programs allow the user to open files by dragging them from the explorer to the application window. As a rule, this is more convenient for the user, in contrast to the standard opening model by selecting the File -> Open menu or clicking the corresponding button on the toolbar, since in this case the user skips the work with the dialog box.
Adding this feature to your application will make it more “professional.”
How it works:
There are two main ways to add this functionality to a Windows application.
The first way is to use the standard Windows Drag-and-Drop API functions and handle the associated window messages.
The second method is the use of technology OLE (COM), which provides advanced methods for transferring data between applications.
This article will discuss the first and easiest way.
Short review:
By default, drag and drop support is disabled in Windows applications.
To enable this feature, we need to tell Windows that we want to receive notifications about dragging, as well as specify the window responsible for processing the notification data.
This window should be able to handle the WM_DROPFILES message that comes to him at the moment when the user has completed the drag and drop operation.
This message provides us with the ability to implement a Drop event handler, from which we can call various API functions, to get information about what was sent to us.
Upon completion of the processing of this message, it is good practice to notify Windows that we have processed this operation and no longer want to receive notifications about it.
')
Security issues in Windows Vista (and above)For security reasons in Windows Vista, dragging between windows that have different security settings may be prohibited.
As a consequence, the receipt of the WM_DROPFILES message by your application may be blocked.
Consider step by step what needs to be done in the Delphi application for the implementation of receiving files through Drag-and-Drop:
1. To gain access to the necessary API functions, you need to connect the ShellAPI module:
uses ShellAPI;
2. Call the DragAcceptFiles function, passing the first parameter of the handle of the window, which will be responsible for processing the WM_DROPFILES message, and the second the value True, indicating that this window is ready to receive notifications.
For example, this window will be the main form of our application:
DragAcceptFiles(Form1.Handle, True);
3. Process the WM_DROPFILES message. In Delphi, we can declare a message handler in the application's main form class:
procedure WMDropFiles(var Msg: TWMDropFiles); message WM_DROPFILES;
4. When receiving the WM_DROPFILES message, use the DragQueryXXX functions for information about the transferred files — see the explanations below.
5. Notify Windows when the message has been processed.
This is done by calling the function DragFinish, in which the parameter passes the HDROP handle that was received when processing the WM_DROPFILES message.
Calling this function will release the resources used to store data about the operation Drag-and-Drop.
DragFinish(DropH);
6. When the application closes, you need to call the DragAcceptFiles function again, but this time you must pass False to the second parameter, which will notify Windows that the window no longer processes the WM_DROPFILES message.
DragAcceptFiles(Form1.Handle, False);
Retrieving information about transferred files.
We will use two functions to get information about transferred files - DragQueryFile and DragQueryPoint.
Both of these functions require the parameter descriptor HDROP, received when processing the WM_DROPFILES message.
Greetings to the jack of all trades - DragQueryFile.
Like most of the Windows API functions, DragQueryFile can implement several behaviors, depending on the parameters passed to it.
This behavior sometimes leads to the fact that the programmer is quite difficult to remember the correct use of this function.
Let's consider its parameters (taking into account the syntax of Delphi):
- DropHandle: HDROP - the operation descriptor sent by the WM_DROPFILES message.
- Index: Integer - file index when querying the list of transferred files.
- FileName: PChar - a pointer to the buffer to which the file name will be transferred.
- BufSize: Integer - the size of the FileName buffer.
Calling this function enables us to obtain the following data:
- Getting the number of transferred files. We can get this information by passing $ FFFFFFFF or DWORD (-1) in the Index parameter, the FileName parameter should be “nil”, and the BufSize parameter, which is responsible for the size of the FileName buffer, should be zero. The value returned by the function will be equal to the number of transferred files. Pretty obvious.
- Getting the size of the memory required to store the file name based on the Index parameter (counting from zero, i.e. the first file is indexed in the list by zero). To do this, you need to specify the necessary file in the list using the Index parameter, leaving the FileName and BufSize parameters the same as in the previous paragraph. As a result, the function will return the number of bytes required to store the file name.
- Getting the name of the file based on the transferred index. For this, it is necessary, in addition to specifying the file index through the Index parameter, to prepare a buffer (slightly larger than the required size), which can receive data about the path to the file, plus the terminating zero.
So far, all this does not look clear enough, but at the end of the article we will consider all these stages in the form of an independent code implemented as a class.
Dragquerypoint
This function is in principle an alternative to DragQueryFile, by calling it, we can find out the coordinates by which the drag and drop of files ended.
Consider its parameters:
- DropHandle: HDROP - the operation descriptor sent by the WM_DROPFILES message.
- var Point: TPoint is a TPoint structure in which data about the point of completion of a Drag-and-Drop operation is returned. If the operation is completed in the client area of ​​the window, then it will return a non-zero value, otherwise the function will return zero.
Consider the whole thing.
Create a new project.
We have a “fish” Delphi application and we need to add to it the possibility of obtaining information about the transferred files via Drag-and-Drop.
In our form, we register the handler of the event of interest to us:
procedure TForm1.FormCreate(Sender: TObject); begin
With this code, we have subscribed to the receipt of the WM_DROPFILES message.
Here is the handler for this message:
procedure TForm1.WMDropFiles(var Msg: TWMDropFiles); var DropH: HDROP;
At the very beginning, we remember the Drop operation handle in a local variable.
This is done simply for convenience, in the future we will use this value for calls to the DragQueryFile function (as well as for the rest of DragXXX), the first call of which we will get the number of files in the list.
Our next step is to get the name of each file in the loop by its index (the files in the list are indexed from zero — do not forget this).
For each file, you first need to know the size of the buffer, which can be obtained using the second method of using DragQueryFile, which was described above, and then allocating memory for the line that will hold the file path.
In conclusion, you need to read the file path in the selected buffer, by calling DragQueryFile in the third way.
After reading all the information about the file names, we can get the coordinates of the point to which the user dragged the files.
After we have completed all the operations, we will call the DragFinish function to free the handle.
Notice that we must perform this action anyway, even when raising an exception.
Well, at the very end we will indicate the result of processing the message by setting the value 0 to the structure of Msg.Result, thus indicating that we have successfully processed this message.
When the application is completed, we will disable the main window from receiving WM_DROPFILES messages.
procedure TForm1.FormDestroy(Sender: TObject); begin
Implement it as a class (The Delphi Way)
If you agree that using the API directly, directly in the code of the base application, introduces some confusion, then I think you will agree that it is much more convenient to bring this functionality to the outside, in the form of some auxiliary class.
As an example, here is a small class that can handle the WM_DROPFILES message.
It hides in itself a lot of code, which we should have implemented independently without using it.
True, there is a nuance - we still have to notify Windows about the presence of a window processing the WM_DROPFILES message.
The declaration for this class looks like this:
type TFileCatcher = class(TObject) private fDropHandle: HDROP; function GetFile(Idx: Integer): string; function GetFileCount: Integer; function GetPoint: TPoint; public constructor Create(DropHandle: HDROP); destructor Destroy; override; property FileCount: Integer read GetFileCount; property Files[Idx: Integer]: string read GetFile; property DropPoint: TPoint read GetPoint; end;
... well, and its implementation:
constructor TFileCatcher.Create(DropHandle: HDROP); begin inherited Create; fDropHandle := DropHandle; end; destructor TFileCatcher.Destroy; begin DragFinish(fDropHandle); inherited; end; function TFileCatcher.GetFile(Idx: Integer): string; var FileNameLength: Integer; begin FileNameLength := DragQueryFile(fDropHandle, Idx, nil, 0); SetLength(Result, FileNameLength); DragQueryFile(fDropHandle, Idx, PChar(Result), FileNameLength + 1); end; function TFileCatcher.GetFileCount: Integer; begin Result := DragQueryFile(fDropHandle, $FFFFFFFF, nil, 0); end; function TFileCatcher.GetPoint: TPoint; begin DragQueryPoint(fDropHandle, Result); end;
In principle, there is nothing here that has not been described earlier. You have already seen all the code.
The constructor accepts the HDROP handle, the destructor finalizes it with a call to DragFinish.
The list of files and their number are represented by class properties.
All class methods are essentially a wrapper over the DragQueryFile API and DragQueryPoint.
Let's rewrite the WM_DROPFILES message handler using the TFileCatcher:
procedure TForm1.WMDropFiles(var Msg: TWMDropFiles); var I: Integer;
Future plans
The TFileCatcher class can be extended to hide all APIs used during drag-and-drop operations. True, this will require access to the form window to intercept the WM_DROPFILES message.
One of the variants of such an implementation is subclassing the form window. True, this is beyond the scope of this article. However, if you want more information, check out my
Drop Files component set .
Demo app
The demo application source code is based on the code published in this article. The code has been tested in versions Delphi from the fourth to the seventh.
This source code is only a concept and is intended only to illustrate this article. It is not designed for use. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. The code may be used. If you agree to the following link, please use the following link.
Download demo