Click here to Skip to main content
Click here to Skip to main content

How to drag a virtual file from your app into Windows Explorer

By , 13 Sep 2006
 
Prize winner in Competition "MFC/C++ Aug 2006"

Introduction

I was working on an FTP client application where I needed to support drag/drop with Windows Explorer. Dropping files from Explorer into the client was trivial and didn't cause me too much trouble, but dropping files back into Explorer was not as easy. Using CF_HDROP was ruled out because the source file would not physically exist, as it would have to be downloaded from the FTP server before Explorer can get at it. After playing around with various stuff, I finally hit upon using CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS, which suited my purposes. In this article, I'll demonstrate a simple dialog app that will let you drag non-existent files from your app into Explorer.

Basic Technique

  • Derive a class from COleDataSource
  • Allocate global memory and create data in CFSTR_FILEDESCRIPTOR format, and then use CacheGlobalData on the COleDataSource derived class
  • Override OnRenderFileData in this derived class
  • In the OnRenderFileData override, handle CFSTR_FILECONTENTS, and write directly to the CFSTR_FILECONTENTS

Creating the demo app

Generate a default Dialog based MFC application with Visual Studio 2005 (or an earlier version if you don't have 2005). Use the resource editor to add a List control to the dialog, and associate a DDX control variable with it of type CListCtrl and name it m_fileList. Now add the following code to the OnInitDialog to setup the List control.

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

    . . .       

    AfxOleInit();

    m_fileList.SetExtendedStyle(LVS_EX_FULLROWSELECT);
    m_fileList.InsertColumn(0, _T("File"), LVCFMT_LEFT,75);
    m_fileList.InsertColumn(1, _T("Details"), LVCFMT_LEFT,175);

    for(TCHAR c = _T('A'); c < _T('F'); c++)
    {
        CString text1, text2;
        text1.Format(_T("%c.txt"),c);
        text2.Format(_T("File full of %cs"),c);
        m_fileList.SetItemText(
            m_fileList.InsertItem(c - _T('A'),text1),1,text2);
    }

    return TRUE;  
}

The code merely fills up the List control with some dummy file names. Note how I have made a call to AfxOleInit (you don't need to do this if your app already supports OLE).

Deriving a class from COleDataSource

Since we are using delayed data rendering, we need to derive a class from COleDataSource so that we can override OnRenderFileData (the default version merely returns FALSE). So, the first step is to add a class named CMyOleDataSource (derived from COleDataSource) to the project.

class CMyOleDataSource : public COleDataSource
{
    DECLARE_DYNAMIC(CMyOleDataSource)

Now, we need to add an override for OnRenderFileData as shown below.

BOOL CMyOleDataSource::OnRenderFileData(
    LPFORMATETC lpFormatEtc,CFile* pFile)
{
    // We only need to handle CFSTR_FILECONTENTS
    if(lpFormatEtc->cfFormat == 
        RegisterClipboardFormat(CFSTR_FILECONTENTS))
    {   
        HGLOBAL hGlob = NULL;
        const int buffSize = 512;
        hGlob = GlobalAlloc(GMEM_FIXED, buffSize);
        if(hGlob)
        {
            LPBYTE pBytes = (LPBYTE)GlobalLock(hGlob);          
            if(pBytes)
            {
                // lpFormatEtc->lindex can be used to identify
                // the file that's being copied
                memset(pBytes, (int) m_Files.GetAt(
                    lpFormatEtc->lindex)[0], buffSize);
                pFile->Write(pBytes,buffSize);              
            }
            GlobalUnlock(hGlob);
        }
        GlobalFree(hGlob);
        // Need to return TRUE to indicate success to Explorer
        return TRUE;
    }
    return COleDataSource::OnRenderFileData(
        lpFormatEtc, pFile);
}

Note how in the above code, I create dummy files by filling up 512 bytes with a specific character that identifies the file. In a more real life scenario, you'd have to retrieve a specific file, identified by the lindex parameter, and then retrieve that file from a remote source or perhaps from an archive.

Now, we just need to handle the LVN_BEGINDRAG notification as shown below. You can either use the Properties box to add a handler, or manually add an ON_NOTIFY handler in the dialog class.

void CExplorerDelayDropDlg::OnBeginDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
    UINT uFileCount = m_fileList.GetSelectedCount();    

    // The CFSTR_FILEDESCRIPTOR format expects a 
    // FILEGROUPDESCRIPTOR structure followed by an
    // array of FILEDESCRIPTOR structures, one for
    // each file being dropped
    UINT uBuffSize = sizeof(FILEGROUPDESCRIPTOR) + 
        (uFileCount-1) * sizeof(FILEDESCRIPTOR);
    HGLOBAL hFileDescriptor = GlobalAlloc ( 
        GHND | GMEM_SHARE, uBuffSize );        

    if(hFileDescriptor)
    {
        FILEGROUPDESCRIPTOR* pGroupDescriptor = 
            (FILEGROUPDESCRIPTOR*) GlobalLock ( hFileDescriptor );
        if(pGroupDescriptor)
        {
            // Need a pointer to the FILEDESCRIPTOR array
            FILEDESCRIPTOR* pFileDescriptorArray = 
                (FILEDESCRIPTOR*)((LPBYTE)pGroupDescriptor + sizeof(UINT));
            pGroupDescriptor->cItems = uFileCount;            

            POSITION pos = m_fileList.GetFirstSelectedItemPosition();
            int index = 0;
            m_DataSrc.m_Files.RemoveAll();
            while( NULL != pos )
            {   
                int nSelItem = m_fileList.GetNextSelectedItem( pos );
                ZeroMemory(&pFileDescriptorArray[index], 
                    sizeof(FILEDESCRIPTOR));
                lstrcpy ( pFileDescriptorArray[index].cFileName, 
                    m_fileList.GetItemText( nSelItem, 0 ) );
                m_DataSrc.m_Files.Add(
                    pFileDescriptorArray[index].cFileName);
                pFileDescriptorArray[index].dwFlags = 
                    FD_FILESIZE|FD_ATTRIBUTES;
                pFileDescriptorArray[index].nFileSizeLow = 512;
                pFileDescriptorArray[index].nFileSizeHigh = 0;
                pFileDescriptorArray[index].dwFileAttributes = 
                    FILE_ATTRIBUTE_NORMAL;
                index++;
            }
        }
        else
        {
            GlobalFree ( hFileDescriptor );
        }
    }
    GlobalUnlock ( hFileDescriptor );       

    // For the CFSTR_FILEDESCRIPTOR format, we use
    // CacheGlobalData since we make that data available 
    // immediately
    FORMATETC etcDescriptor = { 
        RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR), 
        NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    m_DataSrc.CacheGlobalData ( RegisterClipboardFormat(
        CFSTR_FILEDESCRIPTOR), hFileDescriptor, &etcDescriptor );

    // For CFSTR_FILECONTENTS, we use DelayRenderFileData
    // as this data will have to come from a non-physical
    // device, like an FTP site, an add-on device, or an archive
    FORMATETC etcContents = { 
        RegisterClipboardFormat(CFSTR_FILECONTENTS), 
        NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    m_DataSrc.DelayRenderFileData(RegisterClipboardFormat(
        CFSTR_FILECONTENTS), &etcContents);

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

    // Free memory in case of failure
    if(dwEffect == DROPEFFECT_NONE )
    {
        GlobalFree( hFileDescriptor );
    } 
    *pResult = 0;
}

Conclusion

That's it. Obviously, this just shows the bare techniques. You'd need to write extra code to make the whole process smooth. For example, if you are pulling the file from a remote device, there'd be a delay before the file gets written, in which case, you'd need to show a progress bar, or ensure that your main app does not freeze up entirely. But the basic technique will remain the same.

Reference

For more on Drag/Drop with Explorer, read Mike Dunn's excellent article : How to Implement Drag and Drop Between Your Program and Explorer which explains how to use CF_HDROP to transfer existing files to Explorer.

History

  • Sep/13/2006 - Article first published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nish Sivakumar
United States United States
Member
Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.
 
Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.
 
Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.
 
Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.
 
Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionhow can I implement this in .Net or C#memberEdgar10527 May '12 - 8:39 
Hi I'm a .Net and C# programmer and I like to know how to do this in .Net or C#
QuestionConnecting to a real file or delay loading a CF_HDROPmemberRick Pingry12 Nov '10 - 7:50 
Thanks again for a great article. I am using your technique to deal with a delayed drag of a file that I download. Once I have downloaded the file to my temp directory, how do I attach it to the CFile* pointer passed in to OnRenderFileData? Do I need to open the file within the function as a separate CFile, then read from one and write to the other? That does not sound very efficient so I am guessing there is a better way but I am too dense to divine it D'Oh! | :doh:
 
Something else I noticed, is that I can drop these contents pretty well into some things, like file folders or emails in Outlook, but there are other places that is does not work as well as a real file drop does. For example, if I have notepad open and I drag a real text file in there, It will open the text file. I can use CF_HDROP to do this and it would work. Perhaps some applications, like notepad, accept CF_HDROP but not CF_FILECONTENTS? Is there any way to get some kind of callback on CF_HDROP when the drop happens so I can download the file into my temp directory then?
 
Thanks
-- Rick
AnswerRe: Connecting to a real file or delay loading a CF_HDROPmemberRick Pingry17 Nov '10 - 6:57 
In the research I have done, I could not find a way to deal with a CF_HDROP unless the file already exists. that is just how it works.
 
As for using an IStream to do the copy, the person who posted their code below show a great way to use an IStream to copy the file. They were having problems working with very large files, but so far it has worked for me.
GeneralRe: Connecting to a real file or delay loading a CF_HDROPmvpNishant Sivakumar17 Nov '10 - 7:18 
Hey Rick,
 
Been a while since I even looked at this article, which is why I wasn't of much help with this. Glad you got something working though.
Regards,
Nish
My technology blog: voidnish.wordpress.com

GeneralMy vote of 5memberRick Pingry2 Nov '10 - 9:35 
Thanks for anoher great article Nishant. Just what I needed. I remember your name. It seems your articles have helped me several times through my career.
QuestionWhat can I do if I want to move a folder?memberYu Tist12 May '09 - 3:26 
CFSTR_FILEDESCRIPTOR does not work...
AnswerRe: What can I do if I want to move a folder? [modified]memberjerry_wangjh4 Dec '10 - 15:48 
I think this method does not work for a folder(does it?) . After struggling for several weeks, I found : download the virtual files/folders to a temp folder ( which is implemented in COleDropSource or its decedent's QueryContinueDrag()) , and then perform normal CF_HDROP can solve this problem.
 
But I haven't find a way for Copy/Paste virtual foldersFrown | :(

modified on Monday, June 27, 2011 9:17 AM
a desktop app for Google Docs

GeneralRe: What can I do if I want to move a folder? [modified]memberLester_200811 Aug '12 - 23:16 
I had found an article in codeproject to solve the problem of copying/moving a folder.
Transferring Virtual Files to Windows Explorer in C#[^]
Just see the reply to the question about folders.
AnswerRe: What can I do if I want to move a folder?memberLester_200810 Aug '12 - 23:31 
Recently I also meet this problem. Is there anyone who can solve it? Thanks in advance! Smile | :)
QuestionHow would I do this without MFC?memberNTDLS4 Mar '08 - 8:09 
I really need this functionality, but I'm unable to use MFC. How would I drag a file from my application to Explorer? I need only the bare essentials, nothing fancy.
 
Thanks in advance!!!!
 
Josh M. Patterson

GeneralRe: How would I do this without MFC?mvpMichael Dunn18 Mar '08 - 8:49 
What a drag: Dragging a virtual file (HGLOBAL edition)[^]
 
--Mike--
Visual C++ MVP Cool | :cool:
LINKS~! PimpFish | CP SearchBar v3.0 | C++ Forum FAQ
 
"That's what's great about doing user interface work. No matter what you do, people will say that what you did was idiotic." -- Raymond Chen

QuestionHow do I drag a 4GB file?membercmumford9 Aug '07 - 11:05 
I'm trying to drag really big files into Explorer. It looks like my file data is written into a memory CFile instead of into a file. If I copy large files then COleStreamFile::SetLength throws an exception (E_OUTOFMEMORY).
 
Is there a solution to this?
AnswerRe: How do I drag a 4GB file?memberrfalck21 Aug '07 - 15:40 
The following works for me for any sized file on Windows XP. But it generates a zero length file on Windows 2000. I've stepped through it on 2000 and everything appears to work, it just ends up with no data. If you can see anything wrong with it please reply.
 
I've discovered that MFC insists on specifying TYMED_HGLOBAL as well as TYMED_ISTREAM and I've had to add some extra code to try and get it to really use CFile. Here's my setup for my data source:
 
// this is a copy of the defintion from oledobj2.cpp, which isn't publicised
struct AFX_DATACACHE_ENTRY
{
FORMATETC m_formatEtc;
STGMEDIUM m_stgMedium;
DATADIR m_nDataDir;
};
 
FORMATETC etcDescriptor = {
RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR),
NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
source->CacheGlobalData ( RegisterClipboardFormat(
CFSTR_FILEDESCRIPTOR), hFileDescriptor, &etcDescriptor );
FORMATETC etcContents = {
RegisterClipboardFormat(CFSTR_FILECONTENTS),
NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM };
source->DelayRenderFileData(RegisterClipboardFormat(
CFSTR_FILECONTENTS), &etcContents);
// set the tymed to istream, mfc insists on including hglobal as well
AFX_DATACACHE_ENTRY* pEntry = source->GetCacheEntry(&etcContents, DATADIR_GET);
pEntry->m_formatEtc.tymed = TYMED_ISTREAM;
 
I also had to override OnRenderData because it also wants to use HGLOBAL if it is possible:
 
BOOL CMyOleDataSource::OnRenderData(LPFORMATETC lpFormatEtc, LPSTGMEDIUM lpStgMedium)
{
BOOL result;
// make tymed_istream the only format since that is what we want
// the base OnRenderData tries to use tymed_hglobal if possible
if (lpFormatEtc->tymed & TYMED_ISTREAM)
{
int old_tymed = lpFormatEtc->tymed;
lpFormatEtc->tymed = TYMED_ISTREAM;
result = COleDataSource::OnRenderData(lpFormatEtc, lpStgMedium);
lpFormatEtc->tymed = old_tymed;
}
else
result = COleDataSource::OnRenderData(lpFormatEtc, lpStgMedium);
 
return result;
}
 
Then my logic in OnRenderFileData is (I've transferred my data to a temporary file first):
 
const int buffSize = 8192;
hGlob = GlobalAlloc(GMEM_FIXED, buffSize);
int count = -1;
long written = 0;
if(hGlob)
{
LPBYTE pBytes = (LPBYTE)GlobalLock(hGlob);
try
{
if(pBytes)
{
while (1)
{
count = temp_file.Read(pBytes, buffSize);
if (count <= 0)
break;
pFile->Write(pBytes, count);
written += count;
}
}
catch(...)
{
count = -1;
}
GlobalUnlock(hGlob);
}
temp_file.Close();
GlobalFree(hGlob);
}

GeneralRe: How do I drag a 4GB file?memberrfalck22 Aug '07 - 16:41 
I've found the problem I was having with Windows 2000. Apparently the destination end of the ISTREAM reads from the current stream positionConfused | :confused: , which after the Write is always at the end. The solution is to put
 
pFile->Seek(0, CFile::begin);
 
After the writing is complete. Presumably in XP the destination end reads from the beginning of the stream automatically.
GeneralRe: How do I drag a 4GB file?memberdrichard283017 Sep '07 - 12:35 
I got this working thanks to all your input, but I have run into one issue. I am dragging a file from an encrypted archive utility, so I first have to decrypt, then write to the CFile pointer in the OnRenderFileData(). I ran into the memory problem until I changed to use TYMED_ISTREAM (thanks for the code). Now, when I drag large files 40MB or larger, I get a sort of time out message just as the file finishes writing to disk. The message is something like "the program has failed to respond" and gives two button options, either "Switch to" or "Retry". By the time you read the dialog, the file has been delivered, but I'd like to get rid of that message. Also, it doesn't seem to appear all the time - I can drag the same file over and over out of the archive, and the message shows about 50% of the time (depending on the file size). Also, it only seems to happen on Windows XP. When I run it on 2kpro, 2kAdvSer, 2k3 server or Vista, it seems to work fine. Any ideas?
GeneralRe: How do I drag a 4GB file?memberrfalck17 Sep '07 - 13:54 
I'm sorry, I have no answer to this one. I can confirm that it often happens on XP for large files, I haven't really tested it on the others. I just tried it now on 2kpro sp4 and didn't get the message, so that is interesting. At the moment we just say "that's the way Windows works". If you find a solution please let me know.
GeneralRe: How do I drag a 4GB file?membercmumford25 Sep '07 - 3:51 
I'm having the same issues. Also, during the drag operation the destination application (i.e. Explorer) hangs until the copy is finished.
GeneralRe: How do I drag a 4GB file?memberBrian Pence27 Sep '07 - 9:57 
Try implementing the IAsyncOperation interface. This allows the drag/drop operation to leave the UI thread and process in the background.

 
Brian Pence
Celestial Software
www.celestialsoftware.net

GeneralRe: How do I drag a 4GB file?membercmumford27 Sep '07 - 12:24 
Thanks Brian:
 
I've looked into it a bit and that definitely sounds like the solution. Unfortunately I'm having a difficult time implementing it in MFC. Is it possible to do this in an MFC application. If so can I still leverage MFC's drag/drop support or do I have to do it all myself?
 
Thanks!
GeneralRe: How do I drag a 4GB file?memberfabioglauser9 Oct '07 - 13:34 
If you want just to prevent the message, see this:
http://support.microsoft.com/kb/248019
GeneralRe: How do I drag a 4GB file?memberdrichard28307 Nov '07 - 6:17 
Thank you for the link! That is exactly what I wanted to do. Works like a charm now!
QuestionBuild Fails if "Use of MFC" changed to "Static Library" from "Shared DLL"memberRaptorikus19 Jul '07 - 7:15 
The error is in MyOleDataSource.cpp
 
error C2039: 'classCOleDataSource' : is not a member of 'COleDataSource'
error C2065: 'classCOleDataSource' : undeclared identifier
 
I'm building it as a Static Library instead of a Shared DLL because I'm making an ActiveX element that I want embedded in IE. I want to ensure that the end user has exactly what I require. Why doesn't the build work when changed to static?
AnswerRe: Build Fails if "Use of MFC" changed to "Static Library" from "Shared DLL"membercmumford8 Aug '07 - 10:12 
Raptorikus wrote:
The error is in MyOleDataSource.cpp
 
error C2039: 'classCOleDataSource' : is not a member of 'COleDataSource'
error C2065: 'classCOleDataSource' : undeclared identifier
 
I'm building it as a Static Library instead of a Shared DLL because I'm making an ActiveX element that I want embedded in IE. I want to ensure that the end user has exactly what I require. Why doesn't the build work when changed to static?

Comment out this line:
 
IMPLEMENT_DYNAMIC(MyOleDataSource, COleDataSource)
QuestionWhere was the file dropped?memberrfalck1 May '07 - 15:04 
How can I find out where the file(s) were dropped? I have several reasons to want to know, including that I'd like to potentially rename the file since the original has come from a system that doesn't have file extensions, and also "execute" the file once the drop is complete. Does anyone know?
AnswerRe: Where was the file dropped?membertimothy_russell3 May '07 - 15:29 
I would love to know the answer to this as well!
 
Timothy Lee Russell
www.anatone.net

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 13 Sep 2006
Article Copyright 2006 by Nish Sivakumar
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid