Title Implementing OLE Drop and Paste
Author Andre v.d. Merwe dart@pobox.com
Published July 1998 - Pinnacle Delphi Developer
Copyright Reprinted with permission from Pinnacle Publishing, Inc. http://www.pinpub.com
Downloads
  Back to the Zen Home Page



Introduction:

This article will show you how to implement OLE drop and paste functionality in your applications. There are many reasons why you might want to do this. You might want text from another application, files from explorer, bitmaps from another application, URLs from a www browser. The possibilities are endless. OLE drop and paste operations greatly simplify getting the data from the ‘other’ application to yours.

But First

Before I start explaining how drop and past work, it will be useful to mention some of the issues that must be addressed when transferring data from one ‘place’ to another. (Where a ‘place’ is an object, application machines or even hardware).

Firstly certain formats of data suite certain formats of data mediums. For instance if you are passing a file from one application to another, its pointless to read the entire file in to memory just so that the second file can access the data in the file. Likewise it is pointless to write data in memory to a file. Thus it would be useful if different types of media could be used depending on the type of data to be transferred.

Secondly different applications might require different renderings of the same data. As an example assume that you copy some text from a www browser. A simple text edit program (e.g. notepad) would want only plain text. A program that supports rich text (e.g. WordPad) would want a rich text representation of the data. Any program that supported HTML would want the HTML text. The point is that different apps need different renderings of the same data.

Last and defiantly not least is a simple, universal way to transfer this data.


IDataObject

IDataObject is and interface that was designed to simplify data transfer. When data is to be transferred it is packages in an IDataObject. This includes all renderings of the data and all descriptions of the data and media used to transfer it. Having all this information in one place is exactly what is needed for a simple, universal data transfer method.

Drop vs Paste

Both drop and paste are means to the same end, which is transferring data from one place to another. It would therefore seem logical that the method for handling a drop event be very similar, if not exactly the same, as handling a paste event. Well it is. Both operations result in the application getting an IDataObject. The method for extracting the required data from the IDataObject is the same no matter how the IDataObject was obtained.

This satisfies point 3 above, "a simple, universal way to transfer this data".

The IDataObject methods

For simple drop/paste operations, which is all you will need for most applications, there are only a few important methods. These are

GetData - Returns the requested data
QueryGetData - Check if a certain data rendering is available
EnumFormatEtc - Enumerate through all available data renderings.



Support Structures

There are two important structures that are used to describe the data in the IDataObject. These are FormatETC and STGMedium.

FormatETC

FormatETC contains a description of a data rendering. There is thus one FormatETC for every rendering of the data in an IDataObject.

Its fields are

cfFormat

Clipboard format: a numeric value indicating the type of the data.

ptd

Information about the device for which the data was rendered.

dwAspect

Describes the detail contained in the rendering

lIndex

Identifies the ‘piece’ of the data. This allows the data to be split across multiple FormatETCs. This value is normally –1 to indicate that all the data is included in this rendering.

tymed

Describes the medium used to transfer the data

Below are simple functions, which return text descriptions for the above items. These functions will be used in the example application and show the possible values for the FormatETC fields.



function StringFromClipbaordFormat(  cf : Word;  
                                     AndNumber : boolean  
                                  ) : string;
var
   sz : array[ 0..300 ] of char;
begin
      {Check if the data is one of the basic clipboard formats}
   case (cf) of
      CF_DIF          : result := 'CF_DIF';
      CF_DIB          : result := 'CF_DIB';
      CF_TEXT         : result := 'CF_TEXT';
      CF_SYLK         : result := 'CF_SYLK';
      CF_TIFF         : result := 'CF_TIFF';
      CF_RIFF         : result := 'CF_RIFF';
      CF_WAVE         : result := 'CF_WAVE';
      CF_HDROP        : result := 'CF_HDROP';
      CF_BITMAP       : result := 'CF_BITMAP';
      CF_LOCALE       : result := 'CF_LOCALE';
      CF_OEMTEXT      : result := 'CF_OEMTEXT';
      CF_PALETTE      : result := 'CF_PALETTE';
      CF_PENDATA      : result := 'CF_PENDATA';
      CF_UNICODETEXT  : result := 'CF_UNICODETEXT';
      CF_ENHMETAFILE  : result := 'CF_ENHMETAFILE';
      CF_METAFILEPICT : result := 'CF_METAFILEPICT';

      else begin
            {Data type not found, so get the description 
               for this data type}
         GetClipboardFormatName(  cf,  @sz,  200  );

            {Add the numeric value of the item?}
         if(  AndNumber  ) then
            result := '[' + IntToStr(  cf  ) + '] ' + sz
         else
            Result := sz;
     end;
  end;
end;


function StringFromTD(  td : Pointer  ) : string;
begin
   if(  td <> nil  ) then
      result := 'NON-NULL'
   else
      result := 'NULL';
end;


function StringFromAspect(  Aspect : Word  ) : string;
begin
   case (Aspect) of

         {All data is included}
      1 : result := 'DVASPECT_CONTENT';

         {A thumbnail of the data is included}
      2 : result := 'DVASPECT_THUMBNAIL';

         {An icon representing the data is included}
      4 : result := 'DVASPECT_ICON';

         {A full printer document is included. That is 
           headers, page numbers, footers etc.}
      8 : result := 'DVASPECT_DOCPRINT';

      else
         result := 'Unknown Aspect';
   end;
end;


function StringFromTymed(  Tymed : integer  ) : string;
begin
   case Tymed of
          {No data transfered}
       0 : Result := 'TYMED_NULL';

          {Data transfered in global memory}
       1 : Result := 'TYMED_HGLOBAL';

          {Data is in a standard disk file}
       2 : Result := 'TYMED_FILE';

          {Data transfered in an OLE stream}
       4 : Result := 'TYMED_ISTREAM';

          {Data transfered in an OLE storage}
       8 : Result := 'TYMED_ISTORAGE';

          {Data transfered as a standard windows GDI object}
      16 : Result := 'TYMED_GDI';

          {Data is a metafile in global memory}
      32 : Result := 'TYMED_MFPICT';

          {Data is an extended metafile in global memory}
      64 : Result := 'TYMED_ENHMF';

      else
         result := 'Unknown TYMED';
  end;
end;

STGMedium

 

The STGMedium structure describes where the data is located, it describes the medium used to transfer the data. At the beginning of this article I said it was important to allow data to be transferred in the medium which best suits its format, the STGMedium structure is used to achieve this. The formats supported range from the simplest file name to the more complex IStorage interface.

The fields of this structure are

Tymed

Same as FormatETC.Tymed

pUnkForRelease

IUnknown used to free the data. Nil if not used


The remaining fields are in a variable record (union), only one of them is used per STGMedium. The value of the Tymed determines which one.

HBitmap

Handle of a bitmap

HMetaFilePict

Handle of a meta-file

HEnhMetaFile

Handle of an enhanced meta-file

HGlobal

A global memory handle

LpszFileName

Null terminated file name

PStg

IStorage

PStm

IStream

 

As you can see there are quite a few possible data types. Writing the code to free the correct data object would be very tedious. However ReleaseStgMedium does all the work for you. It determines the type of the data and calls the relevant freeing procedure. Very usefull.

 

 

Demo Application

This application will…

 

Apart from demonstrating how to implement drop and paste functionality, its also a useful application. It allows you to see how other applications package their data. You can see which rendering are supported by which apps.

 

 

IDropTarget

Any application wanting to accept drop operations must implement the IDropTarget interface.

For simple drop operations (eg for the demo application) implementing this interface is very easy. All of its methods are 3 lines or less of code.

 

DragEnter

Dragged item has just been moved into the applications window, return the relevant icon.

DragOver

Dragged item is being moved over the application’s window, return the relevant icon.

DragLeave

Dragged item has just moved out of the applications window

Drop

The dragged item has been dropped on this application.

 

The first two methods, DragEnter and DragOver, require that the application return a cursor type. Since the demo application is going to be making a copy of the data, as opposed to a move/link operation, return DROPEFFECT_COPY.

DragOver is ignored in the demo application. The demo application is only interested in drop operations, not in tracking the data item.

The Drop method is the most important one. It is here that an IDataObject becomes available and its data can be retrieved.

 

Actually an IDataObject interface is available in the DragOver method. This allows the application to determine what type of object is being dragged over it, and allow it to decide if it should accept the operation. For example you might only want to accept an IDataObject if it contains a text rendering of the data. However the demo application needs the IDataObject interface no matter what it contains.

 

 

 

The source code

 

TForm1 = class(TForm, IDropTarget)

TForm1’s declaration indicates that it is a TForm and that is supports the IDropTarget interface.

 

 



procedure TForm1.FormCreate(Sender: TObject);
var
  res : HRESULT;
begin
   OleInitialize(  nil  );

      {Allow window to accept drop events}
   res := RegisterDragDrop(  Handle,  Self  );

      {Check if register was sucessfull}
   if(   Failed(  res  )   ) then
     ShowMessage(   ‘RegisterDragDrop Failed'   );
end;


In the OnCreate event handler two important methods are called. First OleInitalize is called, this initialises the OLE libraries and should always be called before your application uses any OLE functions.

RegisterDragDrop, registers the window as a valid drop target. If this is not called the window will never receive any drop events.


procedure TForm1.FormDestroy(Sender: TObject); begin {Finished accepting drops} RevokeDragDrop( Handle ); OleUninitialize; end;

OnDestroy does the exact opposite. It calls RevokeDropTarget to indicate that drop events are no longer accepted. It then calls OleUninitialize since the application is finished using all OLE functions.


procedure TForm1.EnumDataObject(  dataObj : IDataObject  );
var
   ef : IEnumFORMATETC;
   fetc : TFORMATETC;
   sFormat : string;
   bNotFound : boolean;
begin
   if(  dataObj = nil  ) then
   begin
      ShowMessage(  'dataObj = nil'  );
      exit;
   end;

      {Get the IEnumFORMATETC interface}
   dataObj.EnumFormatEtc(  DATADIR_GET,  ef  );

   
      {Start the enumeration}
   while(   ef.Next(  1,  fetc,  nil  ) <> S_FALSE   ) do
   begin
      with lv_Info.Items.Add do
      begin
         Caption := StringFromClipbaordFormat(  fetc.cfFormat,  
                                                true  
                                              );
         SubItems.Add(  StringFromTymed(  fetc.tymed  )   );
         SubItems.Add(  StringFromAspect(  fetc.dwAspect  ) );
         SubItems.Add(  IntToStr(  fetc.lindex  )   );
         SubItems.Add(  StringFromTD(  fetc.ptd  )   );
      end;


      bNotFound := false;


         {Look for a standard clipboard constant} 
      case fetc.cfFormat of
         CF_TEXT   : HandleText(  dataObj,  fetc  );
         CF_BITMAP : HandleBMP(  dataObj,  fetc  );
         CF_HDROP  : HandleHDrop(  dataObj,  fetc  );
      else
         bNotFound := true;
      end;

      if(  bNotFound  ) then
      begin
         {Get the format description}
 
         sFormat := StringFromClipbaordFormat(  fetc.cfFormat,                   
                                                false  
                                              );

         if(  sFormat = 'Rich Text Format'  ) then
            HandleRTF(  dataObj,  fetc  );

         if(  sFormat = 'HTML Format'  ) then
            HandleHTML(  dataObj,  fetc  );
      end;
   end;
end;


TForm1.EnumDataObject is the most important method in the application. Here is how it works

    1. Check that an IDataObject interface is available
    2. Call IDataObject.EnumFormatEtc to get an IEnumFORMATETC with which all the renderings in IDataObject can be enumerated.
    3. For every FormatETC in the IDataObject do
      a) Get string descriptions for the FormatETC items add to the ListView
      b) Determine the type of the data and call the appropriate handler method, if one is
      available

TForm1.EnumDataObject calls a number of ‘handler’ methods to display certain types of data. Since these methods are very similar I’ll only describe one of them.


procedure TForm1.HandleText(  dataObj : IDataObject;  
                              fetc : TFormatEtc  
                            );
var
   p : pointer;
   stgm : TSTGMEDIUM;
begin
      {Make certain the data rendering is available}
   if(  dataObj.QueryGetData(  fetc  ) = NOERROR  ) then
   begin
         {Get the data}
      dataObj.GetData(  fetc,  stgm  );

         {Lock the global memory handle to get
           a pointer to the data}
      p := GlobalLock(  stgm.hGlobal  );

         {Get the text from}
      memoText.Text := string(p);

         {Finished with the pointer}
      GlobalFree(  stgm.hGlobal  );

         {Free the memory}
      ReleaseStgMedium(  stgm  );
   end;
end;


TForm1.HandleText works as follows

    1. Make certain the data rendering is available by calling QueryGetData
    2. Get the data
    3. The data is returned as a HGlobal handle. Call GlobalLock to get a pointer to the data
    4. Get the text, add the text to the memo
    5. Finished with the global handle, so call GlobalFree
    6. Call ReleaseStgMedium to free the memory used by the data



procedure TForm1.but_PasteClick(Sender: TObject);
var
   dataObj : IDataObject;
begin
   ClearOldInfo;
   OleGetClipboard(  dataObj  );
   EnumDataObject(  dataObj  );
end;


TForm1.but_PasteClick is called when the ‘Paste’ button is clicked.

    1. Clear all the old information displayed by the application
    2. Get the IDataObject from the clipboard
    3. Call EnumDataObject to display the info.


function TForm1.Drop(  const dataObj : IDataObject;
                       grfKeyState : Longint;
                       pt : TPoint;
                       var dwEffect : Longint
                     ) : HResult;
begin
   ClearOldInfo;
   EnumDataObject(  dataObj  );
   result := S_OK;
end;


TForm1.Drop is called when a dragged item is dropped on the demo application.

    1. Clear all the old information displayed by the application
    2. Call EnumDataObject to display the info.


Notice how similar (and simple) the above two function are. This shows that handling a drop and a paste operation are very similar. Once you have the IDataObject there is no difference at all.

) : HResult; begin ClearOldInfo; EnumDataObject( dataObj ); result := S_OK; end;


TForm1.Drop is called when a dragged item is dropped on the demo application.

    1. Clear all the old information displayed by the application
    2. Call EnumDataObject to display the info.


Notice how similar (and simple) the above two function are. This shows that handling a drop and a paste operation are very similar. Once you have the IDataObject there is no difference at all.