您的位置:首页 > 运维架构

TvirtualStringTree之DragDrop

2009-04-23 10:55 211 查看
昨天在项目中需要做一个播放列表管理界面,播放列表中的结点本身要求可以拖动,并且要求播放列表能够支持从资源管理器上拖入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事件的帮助文档,讲的不多,但是提到了

If you do not find CF_VIRTUALTREE then the operation has been initiated by another application, e.g. the Explorer (then you will find CF_HDROP or CF_SHELLIDLIST in formats) or Notepad (then you will get CF_TEXT and perhaps CF_UNICODETEXT) etc., depending on the data which is actually dropped.

嗯,看来是可以响应拖入文件的,而且帮助文件里还给了一个示范的代码(郁闷的是这个示范代码只处理 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,文件全路径弹出来了。搞定了这一步,下面的就是一个程序逻辑问题了,嘿嘿。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: