Dropping Files into a WTL Window (The Easy Way)
Dropping Files into a WTL Window (The Easy Way)
Introduction
Every day, many of us drop files on windows in order to get them to 'do' something: an editor (text or graphics), a word processor, an electronic worksheet, a media player, and even Visual Studio usually opens the file: a shortcut is created when dropping the file on top of the 'Start' button; some utilities add a shortcut to a toolbar, when dropping the file on that toolbar.
The API way to do it, is to add WS_EX_ACCEPTFILES
to the extended style of your dialog, view or child window, and handle WM_DROPFILES
.
Since most of the WM_DROPFILES
handlers are quite similar, the WTL way to do it is to make a mixin class which handles the boring parts of the job, inherit from it, and add just your own logic.
After that, you can add WM_DROPFILES
handling to your windows with about a dozen lines of code.
Background
The WM_DROPFILES
handler usually does the following actions:
- Tests to see that the drop happened inside the client area of the relevant window.
- Tests to see that the application is ready to handle dropped files.
- Checks how many files were dropped.
- In a loop (since multiple files can be dropped at once): A. Gets the name (a fully qualified path is given) of each dropped file. B. Does some application-specific handling.
- Releases memory and resources used to manage the drag and drop operation.
- Does application-specific resource cleanup, if needed.
As you see, most of the functionality is boilerplate: only the actions marked in bold are application-specific. Those are the only ones you'll have to write, if you decide to use CDropFilesHandler
.
Using the code
There are basically two kinds of windows which will handle dropped files: dialogs and frame windows.
Using CDropFilesHandler<CMyDialog> (with dialogs):
- In the resource editor, mark the checkbox "Accept Files" in the tab "Extended Styles" (see image below), otherwise the
WM_DROPFILES
message will never arrive.
#include <DropFilesHandler.h>
(which should be in your WTL\Include\CustomExtensions folder).- Inherit your class (from now on,
CMyDialog
) fromCDropFilesHandler<CMyDialog>
. - Forward messages, by adding
CHAIN_MSG_MAP(CDropFilesHandler<CMyDialog>)
to your message map. - Implement three functions:
BOOL IsReadyForDrop(void)
- Called once for each drop operation, before entering the dropped files loop. ReturnTRUE
if your dialog is ready to accept dropped files,FALSE
otherwise.BOOL HandleDroppedFile(LPCTSTR szBuff)
- This is the function called inside the loop, once per dropped file, and it should returnFALSE
to break the loop. This one might implement the real 'handling' of the drop (e.g., by pasting the file's name in an edit box, or, in a SDI/MDI/Multi SDI application, by opening the file).void EndDropFiles(void)
- This function is called once, after exiting the loop. You can leave this as an empty inline, but if your file handling is not trivial, and you want to handle several files at once, putting all their names in a container during the loop (std::list<WTL::CString>
comes to mind) and starting a worker thread here to really handle them all can be an option.
This is the dialog class in the dialog sample project (relevant parts in bold):
// maindlg.h : interface of the CMainDlg class
//
/////////////////////////////////////////////////////////////////////
#include "DropFileHandler.h" // Include the relevant file...
class CMainDlg : public CDialogImpl<CMainDlg>,
// Add CDropFilesHandler to your inheritance list...
public CDropFilesHandler<CMainDlg>
{
public:
enum { IDD = IDD_MAINDLG };
BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
// Send WM_DROPFILES to its handler...
CHAIN_MSG_MAP(CDropFilesHandler<CMainDlg>)
END_MSG_MAP()
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// center the dialog on the screen
CenterWindow();
// set icons
HICON hIcon = (HICON)::LoadImage(
_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_ICON, ::GetSystemMetrics(SM_CXICON),
::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
SetIcon(hIcon, TRUE);
HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON),
::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
SetIcon(hIconSmall, FALSE);
// The sample dialog application just adds all
// dropped files to a listbox.
m_ListBox.Attach(GetDlgItem(IDC_LISTFILES));
return TRUE;
}
// These were left as the wizard created them...
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/,
HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID,
HWND /*hWndCtl*/, BOOL& /*bHandled*/);
LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID,
HWND /*hWndCtl*/, BOOL& /*bHandled*/);
/////////////////////////////////////////////////////////////////////
//////////////////////// CDropFilesHandler requirements' implementation.
// In this particular example, we'll put all dropped files in a listbox.
CListBox m_ListBox;
BOOL IsReadyForDrop(void) { m_ListBox.ResetContent(); return TRUE; }
BOOL HandleDroppedFile(LPCTSTR szBuff)
{
ATLTRACE("%s\n", szBuff);
// In this particular example, we'll do the VERY LEAST possible.
m_ListBox.AddString(szBuff);
// Return TRUE unless you're done handling files (e.g., if you want
// to handle only the first relevant file,
// and you have already found it).
return TRUE;
}
void EndDropFiles(void)
{
// Sometimes, if your file handling is not trivial,
// you might want to add all
// file names to some container (std::list<CString> comes to mind),
// and do the
// handling afterwards, in a worker thread.
// If so, use this function to create your worker thread.
// In this example, we'll display the total number of files dropped.
CWindow wnd;
wnd.Attach(GetDlgItem(IDC_COUNT));
char fmt[] = "Count of files in the last drop: %d";
char buff[sizeof(fmt) + 30];
wsprintf(buff, fmt, m_ListBox.GetCount());
wnd.SetWindowText(buff);
}
};
Using CDropFilesHandler<CMyView> (with frame windows):
It is almost the same, except that you don't usually have access to your view through the resource editor. You have (at least) three options: adding the style when the mainframe creates the view (this was done in the sample application), calling RegisterDropHandler()
on creation from the view itself (more self-contained), if the view handles WM_CREATE
, or even calling ModifyStyleEx(0, WS_EX_ACCEPTFILES)
either depending on an initialization parameter or on a menu option, thus letting the user enable/disable file dropping.
Points of Interest
This class might save you writing a couple of dozen lines of code per window, which is not much, but it also centralizes maintenance, which is a desirable thing, and enables a programmer to handle dropped files without learning the API related to WM_DROPFILES
, which is a (maybe mixed) blessing. If any of you finds this code helpful or inspiring (as I have found so many CodeProject samples), this article has filled its purpose.