Drag '& Drop CListCtrl-derived Class

Environment: VC++ 6.0 sp3, NT 4.0 sp6a, Win98

Overview
How do I use it in my project?
How does it work?
Acknowledgements


Overview

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.

How do I use it in my project?

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) const
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
CFileDropListCtrl::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:

  1. 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.

  2. Use classwizard to assign a CListCtrl member variable to your list, e.g. m_List.

  3. 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;

  4. 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.

  5. 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
  6. 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	);
    {
     // Only do this for files.
     if(CFileDropListCtrl::DL_FILE_TYPE == iPathType)
     {
      // Get its size
    
    	// returns DWORD in reality
      csFileSize = GetFileSize(csPathname); 
    
      // Insert the filename in column 1
      int nItem = pList->InsertItem(0, csPathname, IMAGE_INDEX);
    
      // And the size in column 2
      pList->SetItemText(nitem, 1, csFileSize);
     }
     return S_OK;
    }
    

  7. 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.

  8. Sorted, drag 'n' drop!...

How does it work?

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:
  1. CWnd::OnCreate() - call DragAcceptFiles(TRUE) to register dynamically created windows as drop targets
  2. 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)
{
 //
 // Get the number of pathnames 
 // (files or folders) dropped
 //
 UINT nNumFilesDropped = DragQueryFile(dropInfo, 0xFFFFFFFF, NULL, 0);

 //
 // Iterate through them and do some funky stuff
 //
 TCHAR szFilename[MAX_PATH + 1];
 for (UINT nFile = 0 ; nFile < nNumFilesDropped; nFile++)
 {
  //
  // Get the pathname
  //
  DragQueryFile(dropInfo, nFile, szFilename, MAX_PATH + 1);

  //
  // Do something with it...pretty useless, 
	// but only example
  //
  CString csText;
  GetWindowText(csText);
  SetWindowText(csText + _T("; ") + szFilename);
 }

 //
 // Windows allocates the memory for 
 // file information, so we must clean it up
 //
 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.

Acknowledgements

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 ;-)

Downloads

Download demo project - 35 Kb
Download source - 6 Kb


Comments

  • More than 1 file extension

    Posted by Bersi on 07/07/2005 06:43am

    Hi this piece of code will help to use more than one file extensions in filter mode:

    if(m_dropMode.iMask & DL_FILTER_EXTENSION)
    {
    LPTSTR pszFileExt = PathFindExtension(csPathname);

    if(CString(m_dropMode.csFileExt).Find(pszFileExt) >= 0)
    {
    bValid = TRUE;
    }
    }


    simply override the following:

    if(m_dropMode.iMask & DL_FILTER_EXTENSION)
    {
    LPTSTR pszFileExt = PathFindExtension(csPathname);

    if(CString(pszFileExt).CompareNoCase(m_dropMode.csFileExt) == 0)
    {
    bValid = TRUE;
    }
    }


    Then you can work like this:

    dropMode.csFileExt = _T(".txt .bmp .jpg");


    Hope it's useful for someone. ;)

    Bye Bersi

    Reply
  • [AUTHOR] Posting a new comment? Please read...

    Posted by Legacy on 12/11/2000 12:00am

    Originally posted by: Stuart Carter

    Hi,

    I'm interested in all the comments, but I can't check everyday and Codeguru doesn't send me a notification either!

    So if you'd like a prompt reply, please mail me as well. I'll then respond to you and post the comment here.

    Cheers!
    Stu

    Reply
  • Displaying the "Cannot Drop" icon

    Posted by Legacy on 12/09/2000 12:00am

    Originally posted by: Jason Willett

    You code works really well! Great job! How would you make the pointer become the "Cannot Drop" (circle with a line through it) cursor for files that you don't have associated to drop? Would you use some sort of OnMouseMove event and just go from there or what?

    Thanks in adance,

    Jason Willett

    Reply
  • Could not Drag File out from the list control

    Posted by Legacy on 11/01/2000 12:00am

    Originally posted by: Ronald

    Hei, i am a new Visual C++ programmer. Currently, i implemented a Search program that will display all of the searched files on a List Control.

    Please note that all of the items displayed on the List Control are not the files, they just contain the following:
    1. Main item : File Name
    2. Subitem 1 : File Path
    3. Subitem 2 : The File Size
    4 Subitem 3 : The creating time


    My next target is to implement Drag & Drop on this list control so that when user drag the items, he can drop them on the Explorer and Explorer will accept the files and display them. Besides, if i drag a *.txt file and drop it in NotePad, NotePad can actually open the file.

    All of the current information that i obtained now is that all of the drag files will be stored in "HDROP" format and i successfully read the file names in the Drop target. The problem is that i could not convert my filename in the list control (store in main item) to HDROP format. Thus, please give me a hand on solving this problem.


    Thank you very much

    Reply
  • Modifying DragIcon to show ToolTip Text

    Posted by Legacy on 10/20/2000 12:00am

    Originally posted by: Graham Parker

    Thanks for your great article about showing ToolTips for Listboxes that were too small. This was a really nice simple code snippet that worked first time for me.

    I wanted to amend it so that if you tried to use drag and drop from the ListBox to another control on the same form the text item that you had chosen would be displayed in the DragIcon property.

    Do you have any idea how to do this?

    Thanks in advance.


    Graham.

    Reply
  • I want to drag files from your application to Explorer!!

    Posted by Legacy on 09/10/2000 12:00am

    Originally posted by: Dorakin


    This code only accepts the dropped 'FILENAME'.
    But I want to Drag&Drop to Explorer(or Desktop) from my application's ListView like Explorer.
    This means that I want to COPY to another folder.

    And, what is more, I want to Drag&Drop to another application.
    The application will OPEN the dropped file...
    For example, if you D&D the c:\test.txt to Notepad(running) ,the Notepad will open the c:\test.txt.
    I want to realize in my application's ListView.

    These 2 problem.
    Thank you.

    Reply
  • CFileDialog Question

    Posted by Legacy on 04/04/2000 12:00am

    Originally posted by: Robert

    I have an application that requires me to use both the Explorer and the CFileDialog class for drag/drop file support. For some reason I cannot drag files from the CFileDialog into the list control. However the Explorer works great. Is there a way to have drag drop support for the CFileDialog as well?

    Reply
  • Drag out to explorer

    Posted by Legacy on 02/19/2000 12:00am

    Originally posted by: Andreas

    How to drag something out to explorer?
    
    

    Regards
    Andreas

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Hybrid cloud platforms need to think in terms of sweet spots when it comes to application platform interface (API) integration. Cloud Velocity has taken a unique approach to tight integration with the API sweet spot; enough to support the agility of physical and virtual apps, including multi-tier environments and databases, while reducing capital and operating costs. Read this case study to learn how a global-level Fortune 1000 company was able to deploy an entire 6+ TB Oracle eCommerce stack in Amazon Web …

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds