[OLE拖拽]与OLE拖拽相关的接口有很多如下:IDataObject:此接口主要负责数据处理行为,在OLE拖拽的源实现该接口以定义数据传输格式。在OLE拖拽的目的对象中,可以获取IDataObject接口,并且通过该接口的GetData方法获取数据。具体信息下面介绍。IDropSource:此接口定义了一些方法来处理拖拽的界面显示。(是否如此还有待考证)IDropTarget:此接口定义了接收OLE拖拽将要实现的方法。接收OLE拖拽的对象必须实现该接口。
--------------------------------------------------------------------------------我现在主要是要响应OLE拖拽,所以先从IDropTarget说起。要能够响应OLE拖拽首先要实现IDropTarget接口,并把该接口注册到要响应OLE拖拽的窗口。例如有对象如下:
  TDropManager = class(TInterfacedObject, IDropTarget) 
  private
    FOwner: TWinControl; 
    FHandle: HWND;
    FTarget: Pointer;
  public 
    constructor Create(AOwner: TWinControl);
    destructor Destroy; override;
    function DragEnter(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint;
      var dwEffect: Longint): HResult; stdcall;
    function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
    function DragLeave: HResult; stdcall;
    function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint;
      var dwEffect: Longint): HResult; stdcall;
  end;
该对象实现了IDropTarget,就可以创建该对象了。在Create方法中写入如下代码:
constructor TpsDragDropManager.Create(AOwner: TWinControl);
begin
  inherited Create;
  FOwner := AOwner;
  FHandle := AOwner.Handle;
  OleCheck(RegisterDragDrop(FHandle, Self));
end;
其中RegisterDragDrop就是把支持IDropTarget接口的对象注册为FHandle所指的窗口的用于处理OLE拖拽的对象。由程序可知,此对象的父窗口就可以获取OLE拖拽事件了,这些事件是在DragEnter,DragOver,DragLeave和Drop中触发的。理所当然的,你还需要在该对象释放的时候取消注册。也就是在析构函数中调用RevokeDragDrop(FHandle);注意, RegisterDragDrop和RevokeDragDrop不一定要放在构造函数中,它们可以放在任何地方。[重要说明:这里进行OLE调用,因此必须初始化,使用OleInitialize(nil)和OleUninitialize函数。我是在此单元的initialization和finalization部分分别使用这两个函数(注意,我指Delphi中)] 
<另一点说明,在DragEnter中,Result := S_OK事件才会继续传递到DragOver,DragLeave等>在DragEnter,DragOver和Drop函数中都有const dataObj: IDataObject参数,这个接口就是把数据传递进来的接口。可以调用该接口的GetData方法获取OLE拖拽的数据。GetData的声明如下:function GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium): HResult; stdcall; 它需要2个参数。其中formatetcIn函数指明了能够响应的拖拽数据类型。medium参数存放有具体数据信息。
如:
const
  HDropFormatEtc: TFormatEtc = (cfFormat: CF_HDROP; ptd: nil; dwAspect: DVASPECT_CONTENT;
    lindex: - 1; tymed: $FFFFFFFF);
  HDropFormatEtc1: TFormatEtc = (cfFormat: CF_TEXT; ptd: nil; dwAspect: DVASPECT_CONTENT;
    lindex: - 1; tymed: $FFFFFFFF);

解决方案 »

  1.   

    关于TFormatEtc类型说明:Pascal(Delphi):
    tagFORMATETC = record
      cfFormat: TClipFormat;
      ptd: PDVTargetDevice;
      dwAspect: Longint;
      lindex: Longint;
      tymed: Longint;
    end;
    TFormatEtc = tagFORMATETC;
    {$EXTETNALSYM FORMATETC}
    FORMATETC = TFormatEtc;  C++(VC++) :
    typedef struct tagFORMATETC
    {
        CLIPFORMAT      cfFormat;
        DVTARGETDEVICE  *ptd;
        DWORD           dwAspect;
        LONG            lindex;
        DWORD           tymed;
    }FORMATETC, *LPFORMATETC; 
    cfFormat参数:指明特定的剪帖板格式。OLE认识3种格式。
    1.标准交换格式。例如CF_TEXT.
      补充:MSDN上的说明只提到了CF_TEXT,在Delphi的声明中却有很多CF_XXX,我所测试使用的只有CF_TEXT和CF_HDROP。一般文件拖放是CF_HDROP,WORD的文字拖放是CF_TEXT。
      Delphi中的声明:
      {Predefined Clipboard Formats}
      {$EXTERNALSYM CF_TEXT}
      CF_TEXT = 1;
      {$EXTERNALSYM CF_BITMAP}
      CF_BITMAP = 2;
      {$EXTERNALSYM CF_METAFILEPICT}
      CF_METAFILEPICT = 3;
      {$EXTERNALSYM CF_SYLK}
      CF_SYLK = 4;
      {$EXTERNALSYM CF_DIF}
      CF_DIF = 5;  CF_TIFF = 6;  CF_OEMTEXT = 7;  CF_DIB = 8;  CF_PALETTE = 9;  CF_PENDATA = 10;  CF_RIFF = 11;  CF_WAVE = 12;  CF_UNICODETEXT = 13;  CF_ENHMETAFILE = 14;  CF_HDROP = 15;  CF_LOCALE = $10(注意);  CF_MAX = 17;  CF_DIBV5 = 17;  CF_MAX_XP = 18;
      {Note: CF_MAX changes values if WINVER < 5.In order to maintain backwards compatability, use CF_MAX for WINVER < 5,and CF_MAX_XP for WINVER > 5.}
      {$EXTERNALSYM CF_OWNERDISPLAY}
      CF_OWNERDISPLAY = 128;  CF_DSPTEXT = 129;  CF_DSPBITMAP = 130;  CF_DSPMETAFILEPICT = 131;  CF_DSPENHMETAFILE = 142;
      {"Private" formats don't get GlobalFree()'d}
      CF_PRIVATEFIRST = $200(512);  CF_PRIVATELAST = 767;
      {"GDIOBJ" formats do get DeleteObject()'d}
      CF_GDIOBJFIRST = 768;  CF_GDIOBJLAST = 1023;  但是具体的格式说明我还没有找到相关资料.2.私有应用格式。
      (我想应该是上面的CF_PRIVATEFIRST与CF_PRIVATELAST之间的,由开发者自己定义的格式)3.OLE格式。
       用来创建链接对象和嵌入对象。  
    ptd参数:指向一个DVTARGETDEVICE结构。
        该结构包含了数据要传输的目标设备的信息。当指定的数据依赖设备或者调用者不区分设备时,需要将此参数置NULL。(Delphi中是Nil)在以后的情况中,当数据需要特定设备时,对象必须选取适当的默认设备(一般是显示或可见元件)。通过一个不以来设备的对象(或者说有空(NULL)的目的设备的对象)来获取数据。例如图元文件,它是不以来与特定设备的。  
    dsAcept参数:一个DVASPECT枚举常量。
        它指明应该包含多少细节。一个剪贴板格式可以支持多种对象的表现形式。<Most data and presentation transfer and caching methods pass aspect information.>(这一句不知道什么意思)。例如,调用者可能要求获得一个对象的图标图片,此时使用metafile clipboard format可以获得。注意,只有一个DVASPECT值可以用于dsAcept参数,换言之就是dsAcept不能是DVASPECT中几个常量的组合。
    DVASPECT:Longint;
    值:
    typedef enum tagDVASPECT 
    {
      DVASPECT_CONTENT = 1, 
      DVASPECT_THUMBNAIL = 2, 
      DVASPECT_ICON = 4, 
      DVASPECT_DOCPRINT = 8 
    } DVASPECT;  DVASPECT_CONTENT
        提供一个对象的描叙。它能被置入一个容器内显示。
    DVASPECT_THUMBNAIL
        提供一个thumbnail对象的描叙。它可以在浏览工具中显示。该thumbnail是一个大约120*120像素,16色(建议)设备无关的,事先包含在图元文件中的位图。
    DVASPECT_ICON
        提供一个对象的图标描叙。
    DVASPECT_DOCPRINT
        在屏幕上提供一个描叙,就象通过文件菜单把它提交给打印机打印。描叙的数据可能是一个页队列。  
    lindex:当数据必须按页边界进行分割的时候,该参数是aspect的一部分。
        通常它的值是-1,表示所有数据。对于DVASPECT_THUMBNAIL和DVASPECT_ICON,此参数被忽略。  
    tymed:TYMED枚举常量。
        它指明了存储在媒体(medium)中的数据类型。注:该媒体是传输到本对象的数据。数据可以用任何可以识别的媒体传输,这些枚举常量可以用或操作组合。例如,数据可以使用global memory(全局内存),a disk file(磁盘文件)或者structuree storage objects(结构化存储对象)。
    关于TYMED:
    typedef [transmit_as(long)] enum tagTYMED
    {
      TYMED_HGLOBAL  = 1,
      TYMED_FILE     = 2,
      TYMED_ISTREAM  = 4,
      TYMED_ISTORAGE = 8,
      TYMED_GDI      = 16,
      TYMED_MFPICT   = 32,
      TYMED_ENHMF    = 64,
      TYMED_NULL     = 0
    } TYMED;
    说明:
    TYMED_HGLOBAL:
        存储媒体是一个global memory(全局内存) 句柄(HGLOBAL)。可以用GlobalAlloc函数的GMEM_SHARE参数标志分配全局句柄。(global handle)。如果STGMEDIUM的punkForRelease成员为NULL,那么目的进程应该使用GlobalFree来释放内存。TYMED_FILE:
        存储媒体是一个磁盘文件的路径标识。如果STGMEDIUM的punkForRelease成员为NULL,目的进程应该用OpenFile删除该文件。TYMED_ISTREAM:
        存储媒体是一个IStream指向的流对象标识。使用ISequentialStream::Read函数来读取数据。如果STGMEDIUM的punkForRelease成员不是NULL,则目的进程应该使用IStream::Release来释放流元件。TYMED_ISTORAGE:
        存储媒体是一个IStorage指向的存储元件标识。数据包含在IStorage实例所包含的流或存储介质中。如果STGMEDIUM的punkForRelease成员不为NULL,那么目的进程应该使用IStorage::Release来释放存储元件。TYMED_GDI:
        存储媒体是一个GDI元件(HBITMAP)。如果STGMEDIUM的punkForRelease成员为NULL,则目的进程应该使用DeleteObject来删除该。TYMED_MFPICT:
        存储媒体是一个图元文件(HMETAFILE)。使用Windows或者Win32函数来访问图元文件的数据。如果STGMEDIUM的punkForRelease成员为NULL,则目的进程应该使用DeleteMetaFile函数来删除。TYMED_ENHMF:
        存储媒体是一个增强的图元文件。如果STGMEDIUM的punkForRelease成员为NULL,目的进程应该使用DeleteEnhMetaFile来删除。TYMED_NULL
        没有数据传输。MSDN的帮助信息中还有一个注意的部分,我看不太懂,大致的意思可能是可以使用ReleaseStgMedium函数来释放任何类型的媒体资源。 
      

  2.   

    medium: TStgMedium参数很简单,如下:
    PStgMedium = ^TStgMedium;
    {$EXTERNALSYM tagSTGMEDIUM}
    tagSTGMEDIUM = record
      tymed: Longint;
      case Integer of
        0: (hBitmap: HBitmap; unkForRelease: Pointer{IUnknown});
        1: (hMetaFilePict: THandle);
        2: (hEnhMetaFile: THandle);
        3: (hGlobal: HGlobal);
        4: (lpszFileName: POleStr);
        5: (stm: Pointer{IStream});
        6: (stg: Pointer{IStorage});
    end;
    TStgMedium = tagSTGMEDIUM;
    {$EXTERNALSYM STGMEDIUM}
    STGMEDIUM = TStgMedium;IDataObject::GetData的返回值:(HRESULT) 
    S_OK 
    数据成功获得。 
    DV_E_LINDEX 
    无效的lindex值; 当前, 只有 -1 有效. 
    DV_E_FORMATETC 
    无效的pFormatetc. 
    DV_E_TYMED 
    无效的tymed. 
    DV_E_DVASPECT 
    无效的dwAspect. 
    OLE_E_NOTRUNNING 
    对象应用程序没有运行. 
    STG_E_MEDIUMFULL 
    分配medium时出错. 
    例如以下程序:const
      HDropFormatEtc: TFormatEtc = (cfFormat: CF_HDROP; ptd: nil; 
        dwAspect: DVASPECT_CONTENT; lindex: - 1; tymed: $FFFFFFFF);
    {cfFormat: CF_HDROP指明拖拽类型(此类型指一般的文件,目录拖放),tymed:$FFFFFFFF指明所有数据类型}
    begin
      if dataObj.QueryGetData(HDropFormatEtc) = S_OK then 
      begin
        if medium.tymed = TYMED_HGLOBAL then 
        begin
          DropFiles := PDropFiles(GlobalLock(medium.hGlobal));
          FileName := PChar(DropFiles) + DropFiles^.pFiles;      
        end;
      end;
    end;PDropFiles类型声明我在Delphi的源文件中看到,MSDN中并没有提到。仅仅提到medium.hGlobal是个指针。由上面的代码可以看到要引用该指针还要使用GlobalLock函数。来看看PDropFiles类型的声明:
      { format of CF_HDROP and CF_PRINTERS, in the HDROP case the data that follows is a double 
        null terinated list of file names, for printers they are printer friendly names }   PDropFiles = ^TDropFiles; 
      {$EXTERNALSYM _DROPFILES}
      _DROPFILES = record 
        pFiles: DWORD;  { offset of file list } 
        pt: TPoint;     { drop point (client coords) } 
        fNC: BOOL;      { is it on NonClient area and pt is in screen coords } 
        fWide: BOOL;    { WIDE character switch } 
      end; 
      TDropFiles = _DROPFILES; 
      {$EXTERNALSYM DROPFILES} 
      DROPFILES = _DROPFILES;由声明以及注释知道,该结构是CF_HDROP和CF_PRINTERS的内存数据结构,而数据则是一个以Null结尾的文件列表。而上面DropFiles就是指向该结构体。pFile表示文件列表的偏移值。这里没有指明是相对什么东西的偏移值。我看了一些相关代码,才知道该偏移值就是相对本记录的偏移。fWide则表示是否是宽字符。在Win 98和Win 2000下是不同的,98下是单字符,2000下是双字符。最后强调一下,数据是一个以Null结尾的串的列表,可以使用^PWideChar或者^PChar来访问。(我没测试,还需要测试,呵呵)
      

  3.   

    以下是我翻译的一篇相关文章,英文水平差,可能有不少错误。
    附原文:实现OLE拖拽和粘贴
    Implementing OLE Drop and PasteAnder Van Der Merwe有很多原因可能使你希望在你的应用程序里实现OLE拖拽和粘贴功能。也许你想从另一个程序获得文本,从资源管理器获得文件,从其他程序获得位图,或者从Web浏览器中获得URL链接。这种需求是无止尽的。OLE拖拽和粘贴操作极大简化了你从另一个程序获取数据的操作。There are many resons why you might want to implement OLE drop and paste functionality in your applications.Perhaps you want to receive text from another application,files from Explorer,bitmaps from another application,or URLs from a Web browser.The possibilities are endless.OLE drop and paste operations greatly simplify getting the data from other applications to yours.在我解释拖拽和粘贴是怎样工作之前,我不得不谈到从一个地方把数据传输到其他地方的问题(我这里的地方的意思是指一个对象、应用程序,机器或者任何类型的硬件)Before I start explaining how drop and paste works,it will be helpfull to mention some of the issues that must be addressed when transferring data from one place to another.(By place,I mean an object,applications,machines,or even different types of hardware.)首先,特定的数据适合特定的数据格式媒体。举个例子,如果你正要把一个文件从一个应用程序传送到另一个应用程序,那么把整个文件读到内存而只是让第2个文件可以访问其中的数据是没有什么意义的。同样地,将数据通过内存写入到文件也是无意义的。因此,不同类型的媒体可以按照不同的数据类型进行传输。First,certain formats of data suit certain formats of data mediums.For instance,if you're passing a file from one application to another,it's pointless to read the entire file into memory just so the second file can access the data in the file.Likewise,it's 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.其次,对于同种数据,不同的应用程序可能需要不同的处理。例如,假设你从Web浏览器中复制了一些文本。一个简单的文本编辑程序(例如记事本)将只需要纯文本。一个支持复合文本(例如写字板)将需要一个复合文本的描述数据。任何支持HTML的程序将需要HTML文本。要点就是对于同一数据,不同的程序需要不同的描叙。Second,different applications might require different renderings of the same data.As an example,assume that you copy some text from a Web brower.A simple text edit program(for example, Notepad) would want only plain text.A program that supports rich text(such as WordPad) would want a rich text representation of the data.Any program that supports HTML would want the HTML text.The point is tha different apps need different renderings of the same data.你需要一个简单的,通用的方法来传输这种数据。You need a simple,universal way to transfer this data.IDataObject
    IDataObject是一个设计用来简化数据传输的接口。当数据被传输时,它被包裹在一个IDataObject中。包含了数据的所有描叙、说明以及传输所用的媒体。有了所有这些信息,就能准确知道需要怎样的简单的、通用的方法来传输数据.IDataObject
    IDataObject is an interface that was designed to simplify data transfer.When data is to be transferred,it's packaged 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's needed for a simple,universal data transfer method.Drop vs. paste
    无论是使用拖拽还是粘贴来把数据从一处传输到另一处,其结果是相同的。因此,处理拖拽事件和处理粘贴事件的方法非常的相似。这两种操作都导致应用程序获得一个IDataObject。不管IDataObject是怎样获得的,从IDataObject中获取所需的数据的方法是相同的。Drop vs. paste
    Both drop and paste are a 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 to - 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.
      

  4.   

    上次发了半天没人理,网速又慢,就没发了,这次继续发完!这就是你所需要的那个简单、通用的数据传输方法。This is the simple,universal way to transfer the data that you need.IDataObject的方法和结构
    你绝大多数程序所需要的就是简单的拖拽/粘贴操作,它只有几个重要的方法。详细信息见表1
    表1.拖拽/粘贴操作的方法 方法 描叙 
    GetData 返回请求的数据 
    QueryGetData 检查一个特定数据描叙是否可用 
    EnumFormatEtc 列举全部可用数据描叙 
    The IDataObject methods and structures
    For simple drop/paste operations,which is all you'll need for moust applications,there are only a few important methods.These are detailed in Table 1.
    Table 1.Methods for simple drop/paste operations. Method Description 
    GetData Returns the requested data 
    QueryGetData Checks whether a certain data rendering is available 
    EnumFormatEtc Enumerates through all available data renderings 
    还有两个重要的结构用来描叙IDataObject中的数据,分别是FormatETC和STGMedium.There are two important structures that are used to describe the data in the IDataObject:FormatETC and STGMedium.FormatETC
    FormatETC包含一个数据的描叙。IDataObject中的每一种数据都有一个FormatETC对应。其字段见表2.
    表2.FormatETC的字段 字段 描叙 
    cfFormat 剪贴板格式,一个指明数据类型的数字 
    ptd 递交数据的设备的信息 
    dwAspect 描叙包含在递交信息中的详细信息 
    lIndex 标识数据“块”,此项允许数据被分割成多个FormatETCs.此值通常是1用以表示所有数据都在本描叙内。 
    tymed 描叙用来传输数据的媒体。 FormatETC
    FormatETC contains a description of a data rendering.There's one FormatETC for every rendering of the data in an IDataObject.Its fields are shown in Table 2.
    Table 2.Fields of FormatETC. Field Description 
    cfFormat Clipboard format,a numeric value indicating the 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 acoss multiple FormatETCs.This value is normally 1 to indicate that all the data is included in this rendering. 
    tymed Following are some simple functions that return text descriptions for the preceding items.These functions will be used in the example application and show the possible values for the FormatETC fields. 
    function StringFromClipboardFormat(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 the
            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 inclueded}
        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 transferred}
        0: result := 'TYMED_NULL';    {Data transferred in global memory}
        1: result := 'TYMED_HGLOBAL';    {Data is in a standard disk file}
        2: result := 'TYMED_FILE';    {Data transferred in an OLE stream}
        4: result := 'TYMED_ISTREAM';    {Data transferred in an OLE storage}
        8: result := 'TYMED_ISTORAGE';    {Data transferred as a standard windows GDI object}
        16: result := 'TYMED_GDI';    {Data is a metafile in global memory}
        32: result := 'TYMED_MEPICT';    {Data is an extended metafile in global memory}
        64: result := 'TYMED_ENHMF';    else
          result := 'Unknown TYMED';
      end;
    end;
      

  5.   

    STGMedium
    STGMedium结构描叙了用来传输数据的媒体所在的位置。在本文章的开始,要重点强调的是允许数据的在最合适的媒体中传输。STGMedium结构可以完成此项功能。格式支持范围从简单文件名到复合的IStorage接口。该接口的字段的详细信息见表3.STGMedium
    The STGMedium structure describs 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 that best suits its format;the STGMedium structure is used to achieve this.The formats supported range from the simplest filename to the more complex IStorage interface.The fields of this structure are detailed in Table 3.表3.Fields of STGMedium. 字段 描叙 
    Tymed 与FormatETC中的Tymed相同 
    pUnkForRelease IUnknown用来释放数据,如果没有使用,值为Nil Table 3.Fields of STGMedium. Field Description 
    Tymed Same as FormatETC.Tymed. 
    pUnkForRelease IUnknown used to free the data.Nil if not used. 保留字段在变体记录中(联合)。它们之中,一个STGMedium中只有一个成员被使用;Tymed的值决定了使用哪一个值。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.从表4可以看出,有相当多的可能的数据类型。通过写代码来释放恰当的数据对象是很枯燥的事情。然而,释放StgMedium可以为你完成所有的工作。它确定数据的类型并调用相关的释放过程。非常有效。As you can see from Table 4,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.It's very useful!表4.STGMedium数据类型。 数据类型 描叙 
    hBitmap 位图句柄 
    hMetaFilePict 元文件句柄 
    hEnhMetaFile 增强元文件句柄 
    hGlobal 全局内存句柄 
    lpszFileName 空字符结尾的文件名 
    pStg IStorage 
    pStm IStream Table 4.STGMedium data types. Data type Description 
    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 filename 
    pStg IStorage 
    pStm IStream 例子程序
    本文附上了例子程序(VANDRMER.ZIP),它演示了本文所讨论的技术。 接收拖放和粘贴 
    列举所有的表现,以FormatETC信息显示. 
    显示特定表现中的数据,如Rich Text,Bitmap,Plain Text,HTML,和 HDrop. 
    Demo application
    I've included a demo application along with this article-it's available in the accompanying Download file.It demonstrates the techniques discussed in this article.Specifically,it will:Accept drop and paste operations. 
    Enumerate through all the renderings,displaying the FormatETC info for each. 
    Display data in certain renderings-Rich Text,Bitmap,Plain Text,HTML,and HDrop. 
    范例程序不仅示范了如何实现拖拽和粘贴功能,同时也是一个很有用的程序。他可以让你看到其他程序如何打包他们的数据。你可以看到哪些表现可以被哪些程序所支持。Apart from demonstrating how to implement drop and paste functionality,it's also a useful application.It allows you to see how other applications package their data.You can see which renderings are supported by which apps;Figure 1 shows what is looks like.IDropTarget
    任何想实现的拖放操作的应用程序都必须实现IDropTarget接口。IDropTarget
    Any application wanting to accept drop operations must implement the IDropTarget interface.
      

  6.   

    对于简单的拖放操作来说(例如范例程序),实现此接口十分简单。它所有的方法只要3行或者少量的代码。(见图5).
    图5.IDropTarget接口的方法。 方法 描叙 
    DragEnter 拖动的项被拖进应用程序窗口,返回相关的图标 
    DragOver 拖动的项正在应用程序窗口上,返回相关的图标 
    DragLeave 拖动的项被拖出应用程序窗口。 
    Drop 拖动的项在本应用程序中释放。 
    For simple drop operations-like the demo application-implementing this interface is very easy.All of its methods are three lines or less of code(see Table 5).
    Table 5.Methods of the IDropTarget interface. Method Description 
    DragEnter Dragged item has just been moved into the application's 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 application's window. 
    Drop The dragged item has been dropped on this application. 前面两个方法,DragEnter和DragOver,需要应用程序返回一个光标类型。应用程序会产生数据的一个副本,与move/link操作相反,返回DROPEFFECT_COPY。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,示例程序只关心释放操作,不跟踪数据项。DragOver is ignored in the demo application.The demo application is only interested in drop operations,not in tracking the data item.Drop方法是最重要的一个方法。在这里,IDataObject可用并且可以获得它的数据。The Drop method is the most important one.It's here that an IDataObject becomes available and its data can be retrieved.事实上,IDataObject接口在DragOver方法中是可用的。这样可以让应用程序区分拖动的对象的数据类型,以便程序确定是否接受此操作。例如,你可能只是想接受一个包含文本数据表现的IDataObject。但是,示例程序中无论IDataObject包含何种数据,都需要该接口。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 to decide whether 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.TForm1声明成了一个支持IDropTarget接口的TForm。
    interface:
      TForm1 = class(TForm, IDropTarget)
    TForm1的OnCreate事件中的处理如下:
    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 sucessful}
      if Failed(res) then
        ShowMessage('RegisterDragDrop Failed');
    end;TForm1's declaration indicates that it's a TForm and that it supports the IDropTarget
    interface:
      TForm1 = class(TForm, IDropTarget)
    TForm1's OnCreate event handler looks like this:
    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 sucessful}
      if Failed(res) then
        ShowMessage('RegisterDragDrop Failed');
    end;在OnCreate事件处理中,两个重要的方法被调用。第一个是OleInitalize被调用。在程序使用任何OLE函数之前,应该总是先调用该函数来初始化OLE库。RegisterDragDrop把一个窗口注册为有效的拖放的接受者。如果此函数没有被调用,窗口将永远接受不到任何拖放事件。In the OnCreate event handler,two important methods are alled.First,OleInitalize is called.This initializes 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 isn't called.the window will never receive any drop events.procedure TForm1.FormDestroy(Sender: TObject);
    begin
      {Finished accepting drops}
      RevokeDragDrop(Handle);  OleUninitialize;
    end;OnDestroy做相反的事情。它调用RevokeDropTarget来说明拖放事件不再被接受。应用程序使用完所用的OLE函数后,再调用OleUninitialize。OnDestroy does the exact opposite.It calls RevokeDropTarget to indicate that drop events are no longer accepted.It then calls Uninitialize,since the application is finished using all OLE functions.procedure TForm1.EnumDataObject(dataObj: IDataObject);
    var
      ef: IEnumFORMATETC;
      fetc: TFORMATETC;
      sFormat: string;
      hNotFound: 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 := StringFromClipboardFormat(fetc.cfFormat, true);
          SubItems.Add(StringFromTymed(fetc.tymed));
          SubItems.Add(StringFromspect(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_BITMP   : HandleBMP(dataObj, fetc);
          CF_HDROP   : HandleHDrop(dataObj, fetc);
        else
          bNotFound := true;
        end;    if bNotFound then
        begin
          {Get the format description}
          sFormat := StringFromClipboardFormat(fetc.cfFormat, false);
       
          if sFormat = 'Rich Text Format' then
            HandleRTF(dataObj, fetc);
          if sFormat = 'HTML Format' then
            HandleHTML(dataObj, fetc);
        end;  
    end;TForm1.EnumDataObject是应用程序中最重要的方法。以下介绍它如何工作的: 检查IDataObject接口是否可用 
    调用IDataObject.EnumFormatEtc,获得IEnumFORMATETC,它可以列举IDataObject中所有表现 
    对于IDataObject中的每个FormatETC来说,有以下步骤:获得对FormatETC项的字符串描叙,加入到ListView中 
    确定数据的类型,调用合适的处理过程(如果程序此处理过程) 
    TForm1.EnumDataObject is the most important method in the application.Here's how it work: Check that an IDataObject is available 
    Call IDataObject.EnumFormatEtc to get an IEnumFORMATETC with which all the renderings in IDataObject can be enumerated. 
    For every FormatETC in the IDataObject,follow these steps:Get string descriptions for the FormatETC items to add to the ListView. 
    Determine the type of the data,and call the appropriate handler method,if one is available.
      

  7.   

    TForm1.EnumDataObject调用许多处理方法来显示特定类型的数据。由于这些方法非常相似,我只描叙他们中的一个: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(dataObject: 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工作如下: 
    通过调用QueryGetData来确定数据表现是可用的。 
    获取数据 
    数据作为HGlobal句柄返回,调用GlobalLock来获得指向该数据的指针。 
    获取文本,把文本加入到Memo中 
    当使用完全局句柄后,调用GlobalFree。 
    调用ReleaseStgMedium来释放数据所用的内存。 
    TForm1.HandleText works as follows: 
    Make certain the data rendering is available by calling QueryGetData. 
    Get the data. 
    The data is returned as an HGlobal handle.Call GlobalLock to get a pointer to the data. 
    Get the text,and add the text to the memo. 
    When finished with the global handle,call GlobalFree. 
    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;当Paste按钮按下时,触发TForm1.but_PasteClick: 
    清除应用程序显示的旧信息 
    从剪贴斑获取IDataObject 
    调用EnumDataObject来显示信息: 
    TForm1.but_PasteClick is called when the Paste button is clicked: 
    Clear all the old information displayed by the application. 
    Get the IDataObject from the clipboard. 
    Call EnumDataObject to display the information: 
    function TForm1.Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longing): HResult;
    begin
      ClearOldInfo;
      EnumDataObject(dataObj);
      result := S_OK;
    end;当拖放的项在示例应用程序上释放的时候会调用TForm1.Drop清除应用程序显示的旧信息 
    调用EnumDataObject来显示信息: 
    TForm1.Drop is called when a dragged item is dropped on the demo application: 
    Clear all the old inforation displayed by the application. 
    Call EnumDataObject to display the information: 
    注意前面的两个函数是如此的简单和相似。这说明处理一个拖放操作和处理一个粘贴操作非常的相似。一旦你获得了IDataObject,,它们实现就根本没区别了。Notice how similar(and simple) the two functions are.This shows that handling a Drop Operation and a paste operation is very similar.Once you have the IDataObject,there's no difference at all. 总算是发完了,算是有始有终。
    结帖!