昨天在项目中需要做一个播放列表管理界面,播放列表中的结点本身要求可以拖动,并且要求播放列表能够支持从资源管理器上拖入FLV文件和播放列表文件。我继续使用了强大的TvirtualStringTree组件,但是在同时支持这两种拖动上犯了难,以前做的程序都是只支持其中一种拖动。
将TvirtualStringTree组件的dragMode属性设置为dmAutomatic,然后添加onDragOver事件,在事件处理函数中将Accept设置为true,然后运行,嗯很好,结点已经可以拖动了,而且拖动到结点的不同位置,光标处加亮形式是不一样的,也就是可以分别拖到结点正上方、和结点上面、和结点下面,做到这里我就知道结点是已经可以拖动的了。
然后开始处理文件的拖动,嗯,现成的有个TargetFileDrop组件,好的,拖到界面上去,目标设置为我使用的TvirtualStringTree组件,然后运行。砰……程序挂掉,弹出错误提示:“该窗口已经被注册为了拖放目标”。哇靠,不让我两个一起使用,我郁闷。然后仔细看看这个错误是OleException的一种,心里想我啥时候将TvirtualStringTree组件也注册为ole拖动对象了?看看TvirtualStringTree组件的属性,嗯,看见了属性TreeOptions.MiscOptions.toAcceptOleDrop默认是为true,原来是这个属性,想都不想直接改为false,然后运行。嗯,很好程序没挂。拖一个FLV到组件上面,等等,怎么拖动到指定结点,把鼠标移到任何地方都不加亮,这可真郁闷,因为我拖入文件要能够拖到指定的播放列表上去的。看来TargetFileDrop组件不合要求,pass。然后试一下拖动结点,唉?怎么也没加亮了?难道,难道是?对了,是TreeOptions.MiscOptions.toAcceptOleDrop属性的问题,还是改回true吧。嗯,可以有加亮了。
继续寻找拖入文件的方法,百度一下,嗯,有个方法是要将组件使用AcceptFileDrop函数注册成为能够接收文件拖放的组件?好方法,那来吧,照着说明添加上代码,然后运行,怎么发现怎么都执行不到消息处理代码里去,都直接执行到组件的OnDragDrop事件里了。唉,使用消息添加文件的方法,又失败……
继续看,嗯VB的Ole拖放?有么有delphi版本的?没有。没有怎么办,不管了,继续找。嗯,等等,ole拖放?怎么这么熟悉,向上看看toAcceptOleDrop属性,哇这个组件已经支持ole拖放了,看来还是去研究研究TvirtualStringTree的帮助文件吧。找到OnDragDrop事件的帮助文档,讲的不多,但是提到了
嗯,看来是可以响应拖入文件的,而且帮助文件里还给了一个示范的代码(郁闷的是这个示范代码只处理 CF_VIRTUALTREE,其它的都没有),那照着搬一下。
procedure TTFrmPlayListMgr.vstPlayListMgrDragDrop(Sender: TBaseVirtualTree; Source: TObject; DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); var I, J: Integer; AttachMode: TVTNodeAttachMode; Medium: TStgMedium; Data: Pointer; fomat: tagFORMATETC ; DropHandle: HDROP; path: string; nFileCount: Integer; nBufferLen: Integer; lpszFile: LPSTR; nCount : Cardinal; begin if Length(Formats) > 0 then begin // OLE drag'n drop // If the native tree format is listed then use this and accept the drop, otherwise recject (ignore) it. // It is recommend by Microsoft to order available clipboard formats in decreasing detail richness so // the first best format which we can accept is usually the best format we can get at all. for I := 0 to High(Formats) do begin if Formats[I] = CF_VIRTUALTREE then begin case Mode of dmAbove: AttachMode := amInsertBefore; dmOnNode: AttachMode := amAddChildLast; dmBelow: AttachMode := amInsertAfter; else if Assigned(Source) and (Source is TBaseVirtualTree) and (Sender <> Source) then AttachMode := amInsertBefore else AttachMode := amNowhere; end; // in the case the drop target does an optimized move Effect is set to DROPEFFECT_NONE // to indicate this also to the drag source (so the source doesn't need to take any further action) Sender.ProcessDrop(DataObject, Sender.DropTargetNode, Effect, AttachMode); Break; end; if Formats[i] = CF_HDROP then begin showMessage('CF_HDROP '); end; end else begin // VCL drag'n drop, Effects contains by default both move and copy effect suggestion, // as usual the application has to find out what operation is finally to do Beep; end; end;
运行然后拖入文件,嗯有提示了,证明是能够执行到ShowMessage的地方,但是从哪里去找拖入的文件名呢?看参数说明,是可以从DataObject里找到数据,但是鬼知道怎么获取数据啊,百度没有,google没有,唉,看TDropFIletarget自己的源码是怎么处理DataOjbect的,从上面的代码看到DataObject参数传给方法ProcessDrop再传到GetTreeFromDataObject,然后,嗯,有处理代码了。
function TBaseVirtualTree.GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree; // Returns the owner/sender of the given data object by means of a special clipboard format // or nil if the sender is in another process or no virtual tree at all. var Medium: TStgMedium; Data: PVTReference; begin Result := nil; if Assigned(DataObject) then begin StandardOLEFormat.cfFormat := CF_VTREFERENCE; if DataObject.GetData(StandardOLEFormat, Medium) = S_OK then begin Data := GlobalLock(Medium.hGlobal); if Assigned(Data) then begin if Data.Process = GetCurrentProcessID then Result := Data.Tree; GlobalUnlock(Medium.hGlobal); end; ReleaseStgMedium(Medium); end; end; end;
嗯,照着做吧,但问题只有一个对应着cfFormat = CF_HDROP的数据是什么格式?对GlobaLock返回的指针我怎么处理。结果没有一个地方有说明,MSDN、delphi的Windows SDK帮助文档上对CF_HDROP都没有说明,使用百度对CF_HDROP进行搜索,发现只有对剪切板的一些操作的示范代码,再回去研究下OleDrop的原理,正是将数据存到剪切板中。那么,把对剪切板的操作的代码移植到程序中,好了,一个成功的OnDragDrop事件处理函数出来了。
procedure TTFrmPlayListMgr.vstPlayListMgrDragDrop(Sender: TBaseVirtualTree; Source: TObject; DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState; Pt: TPoint; var Effect: Integer; Mode: TDropMode); var I, J: Integer; AttachMode: TVTNodeAttachMode; Medium: TStgMedium; Data: Pointer; fomat: tagFORMATETC ; DropHandle: HDROP; path: string; nFileCount: Integer; nBufferLen: Integer; lpszFile: LPSTR; nCount : Cardinal; begin if Length(Formats) > 0 then begin // OLE drag'n drop // If the native tree format is listed then use this and accept the drop, otherwise recject (ignore) it. // It is recommend by Microsoft to order available clipboard formats in decreasing detail richness so // the first best format which we can accept is usually the best format we can get at all. for I := 0 to High(Formats) do begin if Formats[I] = CF_VIRTUALTREE then begin case Mode of dmAbove: AttachMode := amInsertBefore; dmOnNode: AttachMode := amAddChildLast; dmBelow: AttachMode := amInsertAfter; else if Assigned(Source) and (Source is TBaseVirtualTree) and (Sender <> Source) then AttachMode := amInsertBefore else AttachMode := amNowhere; end; // in the case the drop target does an optimized move Effect is set to DROPEFFECT_NONE // to indicate this also to the drag source (so the source doesn't need to take any further action) Sender.ProcessDrop(DataObject, Sender.DropTargetNode, Effect, AttachMode); Break; end; if Formats[i] = CF_HDROP then begin if Assigned(DataObject) then begin // Format must later be set. fomat.cfFormat:= CF_HDROP; // No specific target device to render on. fomat.ptd:= nil; // Normal content to render. fomat.dwAspect:= DVASPECT_CONTENT; // No specific page of multipage data (we don't use multipage data by default). fomat.lindex:= -1; // Acceptable storage formats are IStream and global memory. The first is preferred. fomat.tymed:= TYMED_ISTREAM or TYMED_HGLOBAL; //fomat.cfFormat := CF_HDROP; if DataObject.GetData(fomat, Medium) = S_OK then begin Data := GlobalLock(Medium.hGlobal); if Assigned(Data) then begin DropHandle := Integer(Data); nFileCount := DragQueryFile(DropHandle, Cardinal(-1),nil,0); for J := 0 to nFileCount - 1 do begin nBufferLen := DragQueryFile(DropHandle,j,nil,0); // lpszFile := LPSTR(GlobalAlloc(GPTR,nBufferLen+1)); // nCount := DragQueryFile(DropHandle,i,lpszFile,nBufferLen+1); lpszFile := LPSTR(GlobalAlloc(GPTR,255)); nCount := DragQueryFile(DropHandle,j,lpszFile,255); //FIXme operation about lpszFile path := lpszFile; ShowMessage(path); GlobalFree(Cardinal(lpszFile)); end; GlobalUnlock(Medium.hGlobal); end; ReleaseStgMedium(Medium); end; end; end; end; end else begin // VCL drag'n drop, Effects contains by default both move and copy effect suggestion, // as usual the application has to find out what operation is finally to do Beep; end; end;
然后运行,拖入一个文件,OK,文件全路径弹出来了。搞定了这一步,下面的就是一个程序逻辑问题了,嘿嘿。