Handling Drag and Drop of Email Attachments

Environment: VC6 SP3, VB6 SP3, NT4 SP5

So, you would like to allow an e-mail (Outlook) attachment to be dropped onto one of your controls.  Seems like a simple enough task.  After all, doing this from Explorer is very simple (especially in VB).  However, e-mail attachments from Outlook are different beasts altogether.  They are not actually files (until you make them files).  The clipboard formats used are ones used by the Windows Shell.  There is very little documentation on how to handle these clipboard formats and no code samples (believe me, I've searched the universe).  To make matters worse for VB programmers, VB can't handle these formats at all.  This article and the attached code is a result much research and help from 3 different Microsoft support reps on 3 separate incidents which are all related to solving this problem.  I imagine this will be handled in VB someday just like an Explorer file drop (and it should be noted that Outlook Express file attachments are handled like Explorer file attachments - the vbCFFiles format).

This article is applicable to both C++ and VB programs.  If you are using VB, you have no choice but to create a C++ component to handle this task.  I've provided one here. If you are using C++, then all you need is the code in the Drop method of this C++ component.  

The C++/ATL component provided (UIHelper.DLL), provides a UIFileDragDrop interface with 2 methods:

RegisterFileDrop( ) and UnRegisterFileDrop( )

These would be used by a VB program to let the C++ component handle the Drop event of an e-mail attachment.

The 2 events provided: DragOver( ) and FilesDropped( ), are provided to allow the VB program to be informed of  DragOver( ) and DragDrop events that took place in the C++ component.  The FilesDropped( ) event passes the names of the files dropped as a comma-separated string so that the VB application can do something appropriate with the files (e.g. store the path name(s) somewhere).

The VB sample provided implements these methods and events and allows e-mail attachments to be dropped onto a tree control.  It puts the file names in as the text (and key) for each tree node.  It places each new node at the currently selected node location.

I will go into a little more detail now on the inner workings of the code, but if you want to dig in now,  just download the code and take a look.  There really isn't much code and there are some comments placed near the not-so-obvious sections to make reading a little easier.

The nitty-gritty details:

There are several issues that need to be addressed to solve this problem:

  • Registering the C++ component as the drop target
  • Re-registering the VB drop target interface
  • Deciphering the data provided in the Drop event

Registering the C++ component as the drop target

In the VB application, on the OLEDragOver( ) event of your control, you need to check to see if the data is an e-mail attachment.  Since VB doesn't support these, I had to do some hacking to find out that -16370 is the format type for the FileDescriptor structure.  If it is an e-mail attachment, then we can create a UIFileDragDrop object and call RegisterFileDrop( ) passing the window handle of our control and optionally a path name to indicate where the files should be stored (I put this in for my benefit - you may not need it).  This is the code from the VB sample.

Private Sub TreeView1_OLEDragOver(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single, State As Integer)
   If (Data.GetFormat(-16370) = True) Then 'e-mail attachment
      Set UIDragDrop = New UIFileDragDrop
      Dim location As String
      location = "c:\tmp"
      If (Dir(location, vbDirectory) = "") Then
         MkDir location
      End If
      UIDragDrop.RegisterFileDrop TreeView1.hWnd, location
   End If
   Call HandleDragOver(x, y)
End Sub

Notice the HandleDragOver( ) call.  This function was added so that all DragOver( ) messages are handled the same whether they came from VB or the C++ component.  In the sample we wanted to highlight the tree node under the cursor and expand any un-expanded nodes:

Private Sub HandleDragOver(ByVal x As Long, ByVal y As Long)
    Set TreeView1.SelectedItem = TreeView1.HitTest(x, y)
    TreeView1.DropHighlight = TreeView1.SelectedItem
    If (Not TreeView1.SelectedItem Is Nothing) Then
        If (Not TreeView1.SelectedItem.Expanded) Then
            TreeView1.SelectedItem.Expanded = True
        End If
    End If
End Sub

Keep in mind that once we register the C++ component as the drop target, all message are sent to that component (not VB).  This includes the DragOver( ), DrageEnter( ), DragLeave( ), and Drop( ) messages until we revoke the C++ components interface and re-register the VB interface.  This is the reason that the DragOver( ) event is provided by the UIFileDragDrop interface.

Private Sub UIDragDrop_DragOver(ByVal x As Long, ByVal y As Long)
    'Convert pixels to Twips
    x = x * Screen.TwipsPerPixelX
    y = y * Screen.TwipsPerPixelY
    Call HandleDragOver(x, y)
End Sub

Notice we had to convert pixels to Twips, since our VB application is using Twips.  Also, in VB the coordinates are relative to the client (as opposed to the screen in C++).  So before UIFileDragDrop::DragOver( ) is called, the coordinates are converted using ScreenToClient( ).

Also, note we are only passing the x, y coordinates.  There is other information that could be passed (e.g. the current keyboard state).  I'll leave that up to you if you need it (I didn't).

Re-registering the VB drop target interface

The key code in the RegisterFileDrop( ) method is the following:

m_OriginalDropTargetInterface = (IDropTarget *) GetProp((HWND)hWnd, "OleDropTargetInterface");

m_OriginalDropTargetInterface->AddRef();

hr = RevokeDragDrop((HWND) hWnd); // Revoke the VB interface
if (SUCCEEDED(hr))
    hr = RegisterDragDrop((HWND) hWnd, pDT); // Register our C++ interface

Before we Revoke VB's drop target interface, we need to save it away so that we can restore it after the Drop( ) event.  This is accomplished with the undocumented/unsupported call to GetProp( ).  It turns out that (if you care) the IDropTarget interface pointer is attached to the window via the SetProp( ) API call.  If you know the name (in this case "OleDropTargetInterface"), you can get the interface pointer via the property name with GetProp( ).  In order to figure out what the name was, I had to use the EnumProps( ) API function and look for something meaningful (luckily they named it appropriately).  Next we need to do an AddRef( ) on that interface pointer, because the call to RevokeDragDrop( ) will do a Release( ) and we don't want it to go away yet.  Once we Revoke the VB interface, we can register our own interface in the C++ component.  At this point all Drag events will get sent to the C++ interface and VB won't get anything.

To unregister our interface and re-register VB's interface after the Drop event has completed we need the following code (taken from UnRegisterFileDrop( )):

RevokeDragDrop((HWND) hWnd); //Revoke our current interface
if (m_OriginalDropTargetInterface) { //Re-register the VB interface
    hr = RegisterDragDrop((HWND) hWnd, m_OriginalDropTargetInterface);
    m_OriginalDropTargetInterface->Release(); //Release the reference we added in RegisterFileDrop
}

One other note:  The UnRegisterFileDrop( ) method is also called from the DragLeave( ) event.  This fixes a little side-affect which happens if the drop doesn't take place.  If the drop doesn't take place, there would be no way to re-register the VB drop target interface.  So, if we always call UnregisterFileDrop( ) on the DragLeave( ) event we won't be left hanging in limbo.  As long as the VB application calls RegisterFileDrop( ) on the DragOver( ) event we are fine (i.e. we will always re-register the C++ interface ont he first DragOver( ) event).  This would be a little cleaner if VB had the DragEnter( ) and DragLeave( ) events, but you only get these in C++.

Deciphering the data provided in the Drop event

This is the part that applies to both C++ and VB programs.  You need to decipher the data provided in the Drop event in the C++ interface.  There are 2 types of data that are provided with the e-mail attachment: FileDescriptor and FileContents.  The FileDescriptor has file information for each file like the name, size, date/time, etc.  The FileContents data provides an interface pointer to an IStream object that you can use to read the file contents and then write to a file.  So it's up to you to create a file and write the contents to it.  

It should be noted that the FileDescriptor data is actually a FILEGROUPDESCRIPTOR structure which contains the number of files (cItems variable) and an array of FILEDESCRIPTOR structures (one for each file).

 

STDMETHODIMP DragDropObject::Drop(IDataObject * pDataObj,
                                                                    DWORD grfKeyState,
                                                                    POINTL pt,
                                                                    DWORD * pdwEffect)
{
    HRESULT hr = S_OK;

    if (pDataObj) {
        FILEGROUPDESCRIPTOR *file_group_descriptor;
        FILEDESCRIPTOR file_descriptor;

        // Important: these strings need to be non-Unicode (don't compile UNICODE)
        unsigned short cp_format_descriptor = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
        unsigned short cp_format_contents = RegisterClipboardFormat(CFSTR_FILECONTENTS);

        //Set up format structure for the descriptor and contents
        FORMATETC descriptor_format = 
            {cp_format_descriptor, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
        FORMATETC contents_format = 
            {cp_format_contents, NULL, DVASPECT_CONTENT, -1, TYMED_ISTREAM};

        // Check for descriptor format type
        hr = pDataObj->QueryGetData(&descriptor_format);

        if (hr == S_OK) { 
            // Check for contents format type
            hr = pDataObj->QueryGetData(&contents_format);

            if (hr == S_OK) { 
                // Get the descriptor information
                STGMEDIUM storage= {0,0,0};

                hr = pDataObj->GetData(&descriptor_format, &storage);
                file_group_descriptor = (FILEGROUPDESCRIPTOR *) GlobalLock(storage.hGlobal);

                // For each file, get the name and copy the stream to a file
                _bstr_t file_list;
                for (unsigned int file_index = 0; file_index < file_group_descriptor->cItems; file_index++) {
                    file_descriptor = file_group_descriptor->fgd[file_index];
                    contents_format.lindex = file_index;
                    hr = pDataObj->GetData(&contents_format, &storage);

                    if (hr == S_OK) { // Dump stream to a file
                        char file_name[MAX_PATH+1];

                        if (m_FileDragObject->m_Location == _bstr_t("")) // No path specified, so
                            m_FileDragObject->m_Location = "."; // use current directory

                            sprintf(file_name, "%s\\%s", (char *) m_FileDragObject->m_Location, file_descriptor.cFileName);
                            hr = StreamToFile(storage.pstm, file_name);
                            if (file_index == 0)
                                file_list = file_name;
                            else
                                file_list = file_list + _bstr_t(",") + _bstr_t(file_name);
                    }
                    // Let the VB application know about the drop operation completing and
                    // give it a comma-separated list of file paths
                    m_FileDragObject->Fire_FilesDropped(file_list);
                }
                GlobalUnlock(storage.hGlobal);
                GlobalFree(storage.hGlobal);
            }
        }
    } 
    // We are done, so re-register the VB IDropTarget Interface
    m_FileDragObject->UnRegisterFileDrop((long) m_FileDragObject->m_hWnd);
    return hr;
}

The StreamToFile( ) method just reads from the stream and writes to a file.  I used good-old C-runtime sopen( ) and write( ), but you can use whatever you like.  I chose an arbitrary block size of 1024 (BLOCK_SIZE).  The code follows:


HRESULT DragDropObject::StreamToFile(IStream *stream, char *file_name)
{
    byte buffer[BLOCK_SIZE];
    unsigned long bytes_read = 0;
    int bytes_written = 0;
    int new_file;
    HRESULT hr = S_OK;

    new_file = sopen(file_name, O_RDWR | O_BINARY | O_CREAT, SH_DENYNO, S_IREAD | S_IWRITE);
    if (new_file != -1) {
        do {
            hr = stream->Read(buffer, BLOCK_SIZE, &bytes_read);
            if (bytes_read)
                bytes_written = write(new_file, buffer, bytes_read);
        } while (S_OK == hr && bytes_read == BLOCK_SIZE);
        close(new_file);
        if (bytes_written == 0)
            unlink(file_name);
    }
    else {
        unsigned long error;
        if ((error = GetLastError()) == 0L)
            error = _doserrno;
            hr = HRESULT_FROM_WIN32(errno);
    }
    return hr;
}

That's it! I hope I can save just one person from the agony I went through to solve this problem.

Downloads

Download C++ component - 13 Kb
Download VB demo - 3 Kb


Comments

  • how to use UIHelper.dll in VC++ application

    Posted by KISHMORK on 03/01/2006 02:09pm

    I did not understand how the UIHelper.dll can be used in VC++ application. The article explains using the dll in VB. Can anybody tell me how do we need to use this dll VC application? please mail me at kmcy@chevron.com

    • how to use UIHelper.dll in VC++ application

      Posted by smile_8691 on 02/02/2007 10:18am

      can any one help?

      Reply
    Reply
  • Believe me, VB is more powerful than you thought

    Posted by xidongzhang on 07/28/2005 03:45pm

    Kraig Hiersche, You said "VB can't handle these formats at all". I don't agree. Believe me, VB is more powerful than you thought. Actually, I already find a way to hanlde this task in pure VB, not even using WinAPI by "Declare".

    • W. T. F.

      Posted by RobCr on 02/05/2009 02:18am

      Why did xidongzhang make that cryptic post, and then ignore requests for more info ?

      Reply
    • Looking for drag drop codes

      Posted by coineroy on 10/10/2007 03:45am

      Hi all. Could anyone share the drag&drop VB.NET codes? Pls.email to coineroy@gmail.com. Thanks.

      Reply
    • i'm also looking for the vb codes

      Posted by Voj on 06/04/2007 10:11am

      Is there anyone who can share the vb codes? My email address is vojzville@yahoo.com Thanks in advance!

      Reply
    • Looking for the code

      Posted by sreiter on 03/23/2007 08:06am

      Is anyone willing to share the code for this? Ideally VB.net, but VB6 will work. My email address is sreiter@foundrysoftware.com. Thanks in advance.

      Reply
    • Help

      Posted by BeckerThomas on 02/17/2006 02:06pm

      Could anybody share the code with me. My email address is Becker-Software@t-online.de. Thanks in advance Thomas

      Reply
    • OK, , xidongzhang, how did you do it?

      Posted by Phil2005 on 12/07/2005 06:54pm

      "Actually, I already find a way to hanlde this task in pure VB, not even using WinAPI by Declare". Please post how you did it!

      Reply
    • xidongzhang pls help

      Posted by yccheah on 08/06/2005 03:33am

      Hi XidongZhang, I'm looking for VB code that can drag and drop email attachments. Appreciate that you could share the code with me ? My email address is yencheah@yahoo.com. Thanks in advance.

      Reply
    Reply
  • DLL won't compile

    Posted by justinmy on 02/04/2005 08:29pm

    I have Visual C++ 6, but am getting errors on the compile. Has anyone had this issue?

    • Did u find the solution

      Posted by KISHMORK on 03/01/2006 05:27pm

      Hi did you find the solution how to compile UIHelper.dll in VC++? please mail me at kmcy@chevron.com. Thank you in advance.

      Reply
    Reply
  • I need the compiled dll too

    Posted by Neetje on 12/22/2004 08:23am

    Hi,
    
    Great works, but im only have VB6 and not C++.
    Please, send my the dll.
    
    Thanx

    Reply
  • Compiled dll?!

    Posted by TOMAN on 10/21/2004 09:32am

    Not having c/++, it would be great if someone would tell me where to get the compiled dll.

    • Amen to that

      Posted by RobCr on 02/05/2009 02:21am

      I too have no means to compile the DLL. Actually I am interested in the ability to drop the email from Outlook onto a VB6 Form, which would then place the .msg file into a folder. Any advice would be appreciated.

      Reply
    Reply
  • Clipboard Format :-16370: Means :Object Descriptor:

    Posted by alexa_mu on 10/07/2004 04:47pm

    Actually, there is a command GetClipboardFormatName 
    which returns LPSTR by given ID. More then that, if you want to see all transfered ID's, you could use the loop to check it. 
    (it is all in C, MFC, so the IDataObject is COleDataObject class. Anyway, the interface should have the same functionality as COleDataObject; 
    at least I was able to find the IDataObject functions in COleDataObject class).
    
    The sample of this is:
    	pDataObject->BeginEnumFormats(); //received in Drop function
    	FORMATETC lpFormatEtc;
    	while (pDataObject->GetNextFormat(&lpFormatEtc))
    	{
    		if (pDataObject->IsDataAvailable(lpFormatEtc.cfFormat))
    		{
    			//get format's name
    			char strFormatName[MAX_PATH];
    			if (GetClipboardFormatName(lpFormatEtc.cfFormat, strFormatName, MAX_PATH) == 0)
    			{
    				LPVOID lpMsgBuf;
    				FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
    									NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
    									(LPTSTR) &lpMsgBuf, 0, NULL);
    				// Display the string.
    				TRACE("Cannot get eudora format :%d:'s name: %s", lpFormatEtc.cfFormat, (LPCTSTR)lpMsgBuf);
    				// Free the buffer.
    				LocalFree( lpMsgBuf );
    			}
    			else
    			{
    				TRACE("Format :%d: Means :%s:\n", lpFormatEtc.cfFormat, strFormatName);
    			}
    			if (GetClipboardFormatName(-16370, strFormatName, MAX_PATH) == 0)
    			{
    				TRACE("Error getting -16370 format\n");
    			}
    			else
    			{
    				TRACE("Format :%d: Means :%s:\n", -16370, strFormatName);
    			}
    		}
    	}

    Reply
  • UIHelper compiles in the Debug configuration

    Posted by cutcrew on 09/02/2004 02:38pm

    Just select 'Debug' under Tools/Set Active Configuration in Visual Studio 6.

    Reply
  • Compiled DLL Please!

    Posted by jostros on 08/30/2004 06:30pm

    The source didn't link on my Win2K machine. I would like the complete source that does build, but I can live with just a compiled version. It was an unresolved external error on the symbol _main if that tells you anything.

    Reply
  • Anyone got this compiled dll?

    Posted by Halandriel on 08/03/2004 04:58am

    Hi, since I need the compiled dll, too, I wanted to ask if anyone got it, yet? Anyway, very good article.

    Reply
  • Possible solve for Outlook XP and 2003

    Posted by rippersoftware on 06/21/2004 04:26pm

    I think that the default email format is not rich text for the newer outlook packages. Goto Tools, Options, Mail Format, and then select Rich Text. Hope this helps.

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Live Event Date: September 19, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT In response to the rising number of data breaches and the regulatory and legal impact that can occur as a result of these incidents, leading analysts at Forrester Research have developed five important design principles that will help security professionals reduce their attack surface and mitigate vulnerabilities. Check out this upcoming eSeminar and join Chris Sherman of Forrester Research to learn how to deal with the influx of new device …

  • A majority of organizations are operating under the assumption that their network has already been compromised, or will be, according to a survey conducted by the SANS Institute. With many high profile breaches in 2013 occurring on endpoints, interest in improving endpoint security is top-of-mind for many information security professionals. The full results of the inaugural SANS Endpoint Security Survey are summarized in this white paper to help information security professionals track trends in endpoint …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds