Overview How do I use it in my project? How does it work? Acknowledgements
CFileDropListCtrl
- a class derived from CListCtrl
that accepts files and/or folders dropped from Explorer.
- Filters file types based on their extension
- Resolves shortcuts
- Checks for duplicate items
- Allows custom processing of dropped items through an optional user callback function
It was developed to overcome the usability problems of CFileDialog
for multiple file selection, and help cater for more experienced users.
I was using CFileDialog
to select files to add to a list control. This was OK, but selecting multiple files from different folders is often tedious with the small size of CFileDialog
on NT and 95 (it can be resized on 98 & 2000). Its a lot easier to locate files with Windows Explorer and drag and drop them onto the list control. There are less steps required for the user and its more intuitive for experienced ones.
The class has been tested with UNICODE, compiles cleanly on warning level 4 and is const
correct.
You can quickly add this to new or existing projects by substituting it for your original CListCtrl and specifying the type of dropped items you want to accept.
By default, the list inserts the items itself - CListCtrl::InsertItem(0, csFilename)
is called for each one. This will work with any style of list you have (Small Icon, Large Icon, List, Report). Note that if you've associated an image list, the default image (index 0) will be used and in Report view, the filename will be inserted into the first column.
If you want to do something fancier, say having a few columns in report view and showing the size and attributes of each file, you can! Give the control a callback function and it will notify you each time a suitable file is dropped - its then up to you to insert it, see below for details.
CFileDropListCtrl
has two public members used to update and retrieve settings:
BOOL SetDropMode(const CFileDropListCtrl::DROPLISTMODE& dropMode)
DROPLISTMODE GetDropMode() const
struct DROPLISTMODE
{
UINT iMask;
CString csFileExt;
LPFN_DROP_FILES_CALLBACK pfnCallback;
};
iMask: specifies what type of items to accept - a combination of these flags
FileDropListCtrl::DL_ACCEPT_FILES |
allow files to be dropped |
CFileDropListCtrl::DL_ACCEPT_FOLDERS |
allow folders to be droppped |
CFileDropListCtrl::DL_FILTER_EXTENSION |
only accept files with the specified extension. Specify in csFileExt |
CFileDropListCtrl::DL_USE_CALLBACK |
receive a callback for each item dropped, specified in pfnCallback (you have responsibility for inserting items into the list) |
CFileDropListCtrl::DL_ALLOW_DUPLICATES |
accept pathnames even if they already in the list (ignored if you are handling insertion through a callback function) |
csFileExt: the file extension on which to filter. Use the format ".extension". Ignored unless DL_FILTER_EXTENSION is specified.
pfnCallback: address of your callback function. Ignored unless DL_USE_CALLBACK is specified.
See step 5 below for detailed info on the format of the callback and how to use it.
SetDropMode()
return Values:
- TRUE if the mode was changed successfully
- FALSE if the mode is invalid (specifying DL_USE_CALLBACK, but not populating pfnCallback). The default settings will be used (accept files and folders with no duplicates)
Thats the interface, now step-by-step instructions on how to add and use it in your project:
- In resource editor, add a list control to your dialog and check the "Accept Files" property (on the Extended styles page).
Set the list style to "List", unless you are using a callback function to insert items yourself. Remember that if you choose a "Report" style, you MUST insert one column before any items will be inserted.
- Use classwizard to assign a CListCtrl member variable to your list, e.g. m_List.
- Now in the header file of your dialog class:
#include "FileDropListCtrl.h"
And change the member variable type of your list from CListCtrl to CFileDropListCtrl
i.e. CFileDropListCtrl m_List;
- Now you can specify what kind of items you want the list control to accept. In this case, its best to put it in your dialog class' OnInitDialog():
CFileDropListCtrl::DROPLISTMODE dropMode;
dropMode.iMask = CFileDropListCtrl::DL_ACCEPT_FILES |
CFileDropListCtrl::DL_FILTER_EXTENSION;
dropMode.csFileExt = _T(".txt");
m_List.SetDropMode(dropMode);
this will set the list to only accept files with an extension of ".txt".
Note: The default mode is to accept all types of files and folders, but not to allow duplicate entries.
- Optionally specify a callback function. Without this, the control will take care of inserting items into the list, albeit simply.
If you want to do something fancier you should install a callback - the control will use this to notify you each time a suitable file is dropped - its then up to you to insert it.
Declare the callback as a static member function in your dialog class or as a global if you like:
static HRESULT CALLBACK OnListFileDropped(CListCtrl* pList,
const CString& csPathname,
const UINT& iPathType );
pList |
pointer to the list control the item was dropped onto |
csPathname |
fully qualified path of the item |
iPathType |
indicates the type of item. CFileDropListCtrl::DL_FOLDER_TYPE or CFileDropListCtrl::DL_FILE_TYPE |
In OnInitDialog()
, register the callback along with other info - similar to step 4.
dropMode.iMask |= CFileDropListCtrl::DL_USE_CALLBACK;
dropMode.pfnCallback = CMyDialog::OnListFileDropped;
Heres an example of how you might customise insertion by displaying the size of a dropped file:
HRESULT CMyDialog::OnListFileDropped(CListCtrl* pList,
const CString& csPathname,
const UINT& iPathType );
{
if(CFileDropListCtrl::DL_FILE_TYPE == iPathType)
{
csFileSize = GetFileSize(csPathname);
int nItem = pList->InsertItem(0, csPathname, IMAGE_INDEX);
pList->SetItemText(nitem, 1, csFileSize);
return S_OK;
}
- Thats all the code done. Now add the input library "shlwapi.lib" to your projects Link settings. This is for PathFindExtension(), one of many useful Shell Utility functions.
- Sorted, drag 'n' drop!...
The method used to handle dropped files is generic and can be applied to any
CWnd
derived object (e.g. a
CEdit
). You just need to handle and override 2 messages -
WM_CREATE
and
WM_DROPFILES
:
CWnd::OnCreate()
- call DragAcceptFiles(TRUE)
to register dynamically created windows as drop targets
CWnd::OnDropFiles()
to process the files:
As an example, heres how you could handle WM_DROPFILES
in a subclassed CEdit control:
void CMyEdit::OnDropFiles(HDROP dropInfo)
{
UINT nNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);
TCHAR szFilename[MAX_PATH + 1];
for (UINT nFile = 0 ; nFile < nNumFilesDropped; nFile++)
{
DragQueryFile(dropInfo, nFile, szFilename, MAX_PATH + 1);
CString csText;
GetWindowText(csText);
SetWindowText(csText + _T("; ") + szFilename);
}
DragFinish(dropInfo);
}
Also, you'll probably want to expand any dropped shortcuts before processing the filename, so take a look at
CFileDropListCtrl::ExpandShortcut()
. It uses the COM interface
IShellLink for resolving them.
Thanks to these blokes for helping me get this working in double quick time!
- Handling of droppped files adapted from CDropEdit, 1997 Chris Losinger
- Shortcut expansion code adapted from
CShortcut
, 1996 Rob Warner
I'd appreciate a mail if you've any comments, suggestions, or bugs ;-)