Click here to Skip to main content
15,885,366 members
Articles / Desktop Programming / MFC

How to Implement Drag and Drop Between Your Program and Explorer

Rate me:
Please Sign up or sign in to vote.
4.91/5 (105 votes)
13 Dec 2002 857.5K   11.4K   278  
A step-by-step description of how to drag and drop files between your application and Explorer windows.
// MultiFilerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "MultiFiler.h"
#include "MultiFilerDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

// Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA

    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

// Implementation
protected:
    //{{AFX_MSG(CAboutDlg)
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)
        // No message handlers
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CMultiFilerDlg dialog

CMultiFilerDlg::CMultiFilerDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CMultiFilerDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CMultiFilerDlg)
    //}}AFX_DATA_INIT
    // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
}

void CMultiFilerDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CMultiFilerDlg)
    DDX_Control(pDX, IDC_FILELIST, c_FileList);
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CMultiFilerDlg, CDialog)
    //{{AFX_MSG_MAP(CMultiFilerDlg)
    ON_WM_SYSCOMMAND()
    ON_WM_DESTROY()
    ON_NOTIFY(LVN_BEGINDRAG, IDC_FILELIST, OnBegindragFilelist)
    ON_NOTIFY(NM_RCLICK, IDC_FILELIST, OnRclickFilelist)
    ON_COMMAND(IDC_SELECT_ALL, OnSelectAll)
    ON_COMMAND(IDC_INVERT_SELECTION, OnInvertSelection)
    ON_COMMAND(IDC_REMOVE_FROM_LIST, OnRemoveFromList)
    ON_COMMAND(IDC_CLEAR_LIST, OnClearList)
    ON_WM_DROPFILES()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CMultiFilerDlg message handlers

// We need to call TranslateAccelerator() ourselves - dialogs don't do this
// automagically.
BOOL CMultiFilerDlg::PreTranslateMessage(MSG* pMsg) 
{
    if ( NULL != m_hAccel && 
         pMsg->message >= WM_KEYFIRST  &&  pMsg->message <= WM_KEYLAST )
        {
        return TranslateAccelerator ( m_hWnd, m_hAccel, pMsg );
        }
    else
        return CDialog::PreTranslateMessage(pMsg);
}

BOOL CMultiFilerDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // Add "About..." menu item to system menu.

    // IDM_ABOUTBOX must be in the system command range.
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
        {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
            {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
            }
        }

    // Set the icon for this dialog.  The framework does this automatically
    //  when the application's main window is not a dialog.
HICON hLgIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
HICON hSmIcon = (HICON) LoadImage ( AfxGetResourceHandle(), 
                                    MAKEINTRESOURCE(IDR_MAINFRAME),
                                    IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR );

    SetIcon(hLgIcon, TRUE);         // Set big icon
    SetIcon(hSmIcon, FALSE);        // Set small icon
    
    // Remove Size & Maximize from the system menu.
    
    pSysMenu->DeleteMenu ( SC_MAXIMIZE, MF_BYCOMMAND );
    pSysMenu->DeleteMenu ( SC_SIZE, MF_BYCOMMAND );

    // Get a handle to the system small icon image list, and set that as the
    // image list for our list control

SHFILEINFO rInfo;
HIMAGELIST hImglist;

    hImglist = (HIMAGELIST) SHGetFileInfo ( _T(""), 0, &rInfo, sizeof(SHFILEINFO), 
                                            SHGFI_ICON | SHGFI_SMALLICON | 
                                              SHGFI_SYSICONINDEX );

    m_imglist.Attach ( hImglist );

    // Make sure the list control has the share image list style, so it won't
    // destroy the system image list when the dialog closes.

    c_FileList.ModifyStyle ( 0, LVS_SHAREIMAGELISTS );

    c_FileList.SetImageList ( &m_imglist, LVSIL_SMALL );

    // Set up the list columns.

    c_FileList.InsertColumn ( 0, _T("Filename"), LVCFMT_LEFT, 0, 0 );
    c_FileList.InsertColumn ( 1, _T("Type"), LVCFMT_LEFT, 0, 1 );
    c_FileList.InsertColumn ( 2, _T("Size"), LVCFMT_LEFT, 0, 2 );

    c_FileList.SetColumnWidth ( 0, LVSCW_AUTOSIZE_USEHEADER );
    c_FileList.SetColumnWidth ( 1, LVSCW_AUTOSIZE_USEHEADER );
    c_FileList.SetColumnWidth ( 2, LVSCW_AUTOSIZE_USEHEADER );

    // Enable tooltips for items that aren't completely visible.

    c_FileList.SetExtendedStyle ( LVS_EX_INFOTIP );

    // Position the dlg at the bottom-right of the desktop.
CRect  rcDesktop;
CRect  rcDlg;
CPoint pt;

    SystemParametersInfo ( SPI_GETWORKAREA, 0, &rcDesktop, 0 );
    GetWindowRect ( &rcDlg );

    pt.x = rcDesktop.right - rcDlg.Width();
    pt.y = rcDesktop.bottom - rcDlg.Height();

    SetWindowPos ( NULL, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER );

    // Load the accelerator table.
    m_hAccel = LoadAccelerators ( AfxGetResourceHandle(),
                                  MAKEINTRESOURCE(IDR_ACCEL) );

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CMultiFilerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
        {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
        }
    else
        {
        CDialog::OnSysCommand(nID, lParam);
        }
}

void CMultiFilerDlg::OnDestroy() 
{
    // Detach our CImageList from the system image list.

    if ( NULL != m_imglist.GetSafeHandle() )
        m_imglist.Detach();

    CDialog::OnDestroy();
}

void CMultiFilerDlg::OnBegindragFilelist(NMHDR* pNMHDR, LRESULT* pResult) 
{
NMLISTVIEW*    pNMLV = (NMLISTVIEW*) pNMHDR;
COleDataSource datasrc;
HGLOBAL        hgDrop;
DROPFILES*     pDrop;
CStringList    lsDraggedFiles;
POSITION       pos;
int            nSelItem;
CString        sFile;
UINT           uBuffSize = 0;
TCHAR*         pszBuff;
FORMATETC      etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

    *pResult = 0;   // return value ignored

    // For every selected item in the list, put the filename into lsDraggedFiles.

    pos = c_FileList.GetFirstSelectedItemPosition();

    while ( NULL != pos )
        {
        nSelItem = c_FileList.GetNextSelectedItem ( pos );
        sFile = c_FileList.GetItemText ( nSelItem, 0 );

        lsDraggedFiles.AddTail ( sFile );

        // Calculate the # of chars required to hold this string.

        uBuffSize += lstrlen ( sFile ) + 1;
        }

    // Add 1 extra for the final null char, and the size of the DROPFILES struct.

    uBuffSize = sizeof(DROPFILES) + sizeof(TCHAR) * (uBuffSize + 1);

    // Allocate memory from the heap for the DROPFILES struct.

    hgDrop = GlobalAlloc ( GHND | GMEM_SHARE, uBuffSize );

    if ( NULL == hgDrop )
        return;

    pDrop = (DROPFILES*) GlobalLock ( hgDrop );

    if ( NULL == pDrop )
        {
        GlobalFree ( hgDrop );
        return;
        }

    // Fill in the DROPFILES struct.

    pDrop->pFiles = sizeof(DROPFILES);

#ifdef _UNICODE
    // If we're compiling for Unicode, set the Unicode flag in the struct to
    // indicate it contains Unicode strings.

    pDrop->fWide = TRUE;
#endif;

    // Copy all the filenames into memory after the end of the DROPFILES struct.

    pos = lsDraggedFiles.GetHeadPosition();
    pszBuff = (TCHAR*) (LPBYTE(pDrop) + sizeof(DROPFILES));

    while ( NULL != pos )
        {
        lstrcpy ( pszBuff, (LPCTSTR) lsDraggedFiles.GetNext ( pos ) );
        pszBuff = 1 + _tcschr ( pszBuff, '\0' );
        }

    GlobalUnlock ( hgDrop );

    // Put the data in the data source.

    datasrc.CacheGlobalData ( CF_HDROP, hgDrop, &etc );

    // Add in our own custom data, so we know that the drag originated from our 
    // window.  CMyDropTarget::DragEnter() checks for this custom format, and
    // doesn't allow the drop if it's present.  This is how we prevent the user
    // from dragging and then dropping in our own window.
    // The data will just be a dummy bool.
HGLOBAL hgBool;

    hgBool = GlobalAlloc ( GHND | GMEM_SHARE, sizeof(bool) );

    if ( NULL == hgBool )
        {
        GlobalFree ( hgDrop );
        return;
        }

    // Put the data in the data source.

    etc.cfFormat = g_uCustomClipbrdFormat;
    
    datasrc.CacheGlobalData ( g_uCustomClipbrdFormat, hgBool, &etc );


    // Start the drag 'n' drop!

DROPEFFECT dwEffect = datasrc.DoDragDrop ( DROPEFFECT_COPY | DROPEFFECT_MOVE );

    // If the DnD completed OK, we remove all of the dragged items from our
    // list.

    switch ( dwEffect )
        {
        case DROPEFFECT_COPY:
        case DROPEFFECT_MOVE:
            {
            // Note: Don't call GlobalFree() because the data will be freed by the drop target.

            for ( nSelItem = c_FileList.GetNextItem ( -1, LVNI_SELECTED );
                  nSelItem != -1;
                  nSelItem = c_FileList.GetNextItem ( nSelItem, LVNI_SELECTED ) )
                {
                c_FileList.DeleteItem ( nSelItem );
                nSelItem--;
                }

            // Resize the list columns.

            c_FileList.SetColumnWidth ( 0, LVSCW_AUTOSIZE_USEHEADER );
            c_FileList.SetColumnWidth ( 1, LVSCW_AUTOSIZE_USEHEADER );
            c_FileList.SetColumnWidth ( 2, LVSCW_AUTOSIZE_USEHEADER );
            }
        break;

        case DROPEFFECT_NONE:
            {
            // This needs special handling, because on NT, DROPEFFECT_NONE
            // is returned for move operations, instead of DROPEFFECT_MOVE.
            // See Q182219 for the details.
            // So if we're on NT, we check each selected item, and if the
            // file no longer exists, it was moved successfully and we can
            // remove it from the list.

            if ( g_bNT )
                {
                bool bDeletedAnything = false;

                for ( nSelItem = c_FileList.GetNextItem ( -1, LVNI_SELECTED );
                      nSelItem != -1;
                      nSelItem = c_FileList.GetNextItem ( nSelItem, LVNI_SELECTED ) )
                    {
                    CString sFilename = c_FileList.GetItemText ( nSelItem, 0 );

                    if ( 0xFFFFFFFF == GetFileAttributes ( sFile ) &&
                         GetLastError() == ERROR_FILE_NOT_FOUND )
                        {
                        // We couldn't read the file's attributes, and GetLastError()
                        // says the file doesn't exist, so remove the corresponding 
                        // item from the list.

                        c_FileList.DeleteItem ( nSelItem );
                    
                        nSelItem--;
                        bDeletedAnything = true;
                        }
                    }

                // Resize the list columns if we deleted any items.

                if ( bDeletedAnything )
                    {
                    c_FileList.SetColumnWidth ( 0, LVSCW_AUTOSIZE_USEHEADER );
                    c_FileList.SetColumnWidth ( 1, LVSCW_AUTOSIZE_USEHEADER );
                    c_FileList.SetColumnWidth ( 2, LVSCW_AUTOSIZE_USEHEADER );

                    // Note: Don't call GlobalFree() because the data belongs to 
                    // the caller.
                    }
                else
                    {
                    // The DnD operation wasn't accepted, or was canceled, so we 
                    // should call GlobalFree() to clean up.

                    GlobalFree ( hgDrop );
                    GlobalFree ( hgBool );
                    }
                }   // end if (NT)
            else
                {
                // We're on 9x, and a return of DROPEFFECT_NONE always means
                // that the DnD operation was aborted.  We need to free the
                // allocated memory.

                GlobalFree ( hgDrop );
                GlobalFree ( hgBool );
                }
            }
        break;  // end case DROPEFFECT_NONE
        }   // end switch
}

void CMultiFilerDlg::OnRclickFilelist(NMHDR* pNMHDR, LRESULT* pResult) 
{
CPoint ptCursorPos;
CMenu  menu;

    // We'll show the menu at the current cursor position.

    GetCursorPos ( &ptCursorPos );

    VERIFY ( menu.LoadMenu ( IDR_LIST_CONTEXTMENU ));
    ASSERT ( NULL != menu.GetSubMenu(0) );  // check that there's a popup menu at index 0.

    // Show the menu!

    menu.GetSubMenu(0)->TrackPopupMenu ( TPM_RIGHTBUTTON, ptCursorPos.x,
                                         ptCursorPos.y, this );

    *pResult = 0;   // return value ignored
}

void CMultiFilerDlg::OnDropFiles(HDROP hdrop) 
{
UINT        uNumFiles;
TCHAR       szNextFile [MAX_PATH];
SHFILEINFO  rFileInfo;
LVFINDINFO  rlvFind = { LVFI_STRING, szNextFile };
LVITEM      rItem;
int         nIndex = c_FileList.GetItemCount();
HANDLE      hFind;
WIN32_FIND_DATA rFind;
TCHAR       szFileLen [64];

    // Get the # of files being dropped.

    uNumFiles = DragQueryFile ( hdrop, -1, NULL, 0 );

    for ( UINT uFile = 0; uFile < uNumFiles; uFile++ )
        {
        // Get the next filename from the HDROP info.

        if ( DragQueryFile ( hdrop, uFile, szNextFile, MAX_PATH ) > 0 )
            {
            // If the filename is already in the list, skip it.

            if ( -1 != c_FileList.FindItem ( &rlvFind, -1 ))
                continue;

            // Get the index of the file's icon in the system image list and
            // its type description.

            SHGetFileInfo ( szNextFile, 0, &rFileInfo, sizeof(rFileInfo),
                            SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES |
                              SHGFI_TYPENAME );

            // Set up an LVITEM for the file we're about to insert.

            ZeroMemory ( &rItem, sizeof(LVITEM) );

            rItem.mask    = LVIF_IMAGE | LVIF_TEXT;
            rItem.iItem   = nIndex;
            rItem.pszText = szNextFile;
            rItem.iImage  = rFileInfo.iIcon;

            // If the file has the hidden attribute set, its attributes in
            // the SHFILEINFO struct will include SFGAO_GHOSTED.  We'll mark
            // the item as "cut" in our list to give it the same ghosted look.

            if ( rFileInfo.dwAttributes & SFGAO_GHOSTED )
                {
                rItem.mask |= LVIF_STATE;
                rItem.state = rItem.stateMask = LVIS_CUT;
                }

            // Insert it into the list!

            c_FileList.InsertItem ( &rItem );

            // Set column #1 to the file's type description.

            c_FileList.SetItemText ( nIndex, 1, rFileInfo.szTypeName );

            // Get the file size, and put that in column #2.

            hFind = FindFirstFile ( szNextFile, &rFind );

            if ( INVALID_HANDLE_VALUE != hFind )
                {
                StrFormatByteSize ( rFind.nFileSizeLow, szFileLen, 64 );
                FindClose ( hFind );
                }

            c_FileList.SetItemText ( nIndex, 2, szFileLen );

            nIndex++;
            }
        }   // end for

    // Resize columns so all text is visible.

    c_FileList.SetColumnWidth ( 0, LVSCW_AUTOSIZE_USEHEADER );
    c_FileList.SetColumnWidth ( 1, LVSCW_AUTOSIZE_USEHEADER );
    c_FileList.SetColumnWidth ( 2, LVSCW_AUTOSIZE_USEHEADER );

    c_FileList.EnsureVisible ( nIndex-1, FALSE );

    DragFinish ( hdrop );
}


/////////////////////////////////////////////////////////////////////////////
// CMultiFilerDlg command handlers

void CMultiFilerDlg::OnSelectAll() 
{
int  nIndex, nMaxIndex = c_FileList.GetItemCount() - 1;

    for ( nIndex = 0; nIndex <= nMaxIndex; nIndex++ )
        {
        c_FileList.SetItemState ( nIndex, LVIS_SELECTED, LVIS_SELECTED );
        }
}

void CMultiFilerDlg::OnInvertSelection() 
{
int  nIndex, nMaxIndex = c_FileList.GetItemCount() - 1;
UINT uItemState;

    for ( nIndex = 0; nIndex <= nMaxIndex; nIndex++ )
        {
        uItemState = c_FileList.GetItemState ( nIndex, LVIS_SELECTED );

        c_FileList.SetItemState ( nIndex, uItemState ^ LVIS_SELECTED, 
                                  LVIS_SELECTED );
        }
}

void CMultiFilerDlg::OnRemoveFromList() 
{
int nSelItem;

    for ( nSelItem = c_FileList.GetNextItem ( -1, LVNI_SELECTED );
          nSelItem != -1;
          nSelItem = c_FileList.GetNextItem ( nSelItem, LVNI_SELECTED ) )
        {
        c_FileList.DeleteItem ( nSelItem );
        nSelItem--;
        }
}

void CMultiFilerDlg::OnClearList() 
{
    c_FileList.DeleteAllItems();    
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) VMware
United States United States
Michael lives in sunny Mountain View, California. He started programming with an Apple //e in 4th grade, graduated from UCLA with a math degree in 1994, and immediately landed a job as a QA engineer at Symantec, working on the Norton AntiVirus team. He pretty much taught himself Windows and MFC programming, and in 1999 he designed and coded a new interface for Norton AntiVirus 2000.
Mike has been a a developer at Napster and at his own lil' startup, Zabersoft, a development company he co-founded with offices in Los Angeles and Odense, Denmark. Mike is now a senior engineer at VMware.

He also enjoys his hobbies of playing pinball, bike riding, photography, and Domion on Friday nights (current favorite combo: Village + double Pirate Ship). He would get his own snooker table too if they weren't so darn big! He is also sad that he's forgotten the languages he's studied: French, Mandarin Chinese, and Japanese.

Mike was a VC MVP from 2005 to 2009.

Comments and Discussions