如何使我的文件 DropSouce 被所有使用文件的目标接受?
How to make my file DropSouce be accepted by all targets that works with files?
我制作了一个表示文件列表的控件,我希望能够将文件从我的控件拖到其他处理文件的应用程序。我实现了 IDragSource 接口(如下所示),但是当我拖动时,文件仅被 windows 资源管理器接受,其他应用程序如 Firefox、Yahoo Messenger、Photoshop...不接受我的文件。我做错了什么?我觉得 IDataObject 设置不正确,恐怕我必须自己实现它......这对我来说是一项非常复杂的工作,因为我刚刚开始使用接口。
这里是重现问题的代码:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ActiveX, ShlObj;
type
TMyControl = class(TMemo, IDropSource)
private
function QueryContinueDrag(fEscapePressed:BOOL; grfKeyState:Longint):HResult; stdcall;
function GiveFeedback(dwEffect:Longint):HResult; stdcall;
procedure DoDragAndDrop;
function GetFileListDataObject:IDataObject;
protected
procedure MouseMove(Shift:TShiftState; X,Y:Integer); override;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
public
MyMemo:TMyControl;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{TMyControl}
function TMyControl.QueryContinueDrag(fEscapePressed:BOOL; grfKeyState:Longint):HResult;
begin
if fEscapePressed then Result:=DRAGDROP_S_CANCEL
else if (grfKeyState and (MK_LBUTTON or MK_RBUTTON) = 0) then Result:=DRAGDROP_S_DROP
else Result:=S_OK;
end;
function TMyControl.GiveFeedback(dwEffect:Longint):HResult;
begin
Result:=DRAGDROP_S_USEDEFAULTCURSORS;
end;
procedure TMyControl.DoDragAndDrop;
var AllowedEffects,DropEffect:Longint;
DataObj:IDataObject;
begin
AllowedEffects:=DROPEFFECT_COPY;
DataObj:=GetFileListDataObject;
if DataObj <> nil then
DoDragDrop(DataObj, self, AllowedEffects, DropEffect);
end;
function TMyControl.GetFileListDataObject:IDataObject;
var Desktop:IShellFolder;
Attr,Eaten:ULONG;
Count,x:Integer;
Pidls:array of PItemIDList;
begin
Result:=nil;
Count:=Lines.Count;
if Count<1 then Exit;
if Failed(SHGetDesktopFolder(Desktop)) then Exit;
SetLength(Pidls,Count);
for x:=0 to Count-1 do Pidls[x]:=nil;
try
for x:=0 to Count-1 do
if Failed(Desktop.ParseDisplayName(0, nil, PWideChar(Lines[x]), Eaten, Pidls[x], Attr)) then Exit;
Desktop.GetUIObjectOf(0, Count, Pidls[0], IDataObject, nil, Result);
finally
for x:=0 to Count-1 do
if Pidls[x]<>nil then CoTaskMemFree(Pidls[x]);
end;
end;
procedure TMyControl.MouseMove(Shift:TShiftState; X,Y:Integer);
begin
if ssLeft in Shift then DoDragAndDrop;
inherited;
end;
//---------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
MyMemo:=TMyControl.Create(Form1);
MyMemo.Parent:=Form1;
MyMemo.Align:=alClient;
end;
end.
问题是您使用了不正确的 Desktop.GetUIObjectOf 调用。当您调用 SomeFolder.GetUIObjectOf 时,项目必须是 SomeFolder 的子项。但就您而言,情况并非如此。尝试这样的事情:
type
PPItemIDList = ^PItemIDList;
function GetFileListDataObject(AParentWnd: HWND; const APath: string; AFileNames: TStrings): IDataObject;
var
Desktop: IShellFolder;
Eaten, Attr: ULONG;
i: Integer;
PathIDList: PItemIDList;
PathShellFolder: IShellFolder;
IDLists: PPItemIDList;
IDListsSize: Integer;
Pos: PPItemIDList;
begin
Result := nil;
if AFileNames.Count < 1 then Exit;
if Failed(SHGetDesktopFolder(Desktop)) then Exit;
try
Attr := 0;
if Failed(Desktop.ParseDisplayName(AParentWnd, nil, PWideChar(APath), Eaten, PathIDList, Attr)) then Exit;
try
if Failed(Desktop.BindToStorage(PathIDList, nil, IShellFolder, PathShellFolder)) then Exit;
try
IDListsSize := SizeOf(PItemIDList) * AFileNames.Count;
GetMem(IDLists, IDListsSize);
try
ZeroMemory(IDLists, IDListsSize);
Pos := IDLists;
for i := 0 to AFileNames.Count - 1 do
begin
Attr := 0;
if Failed(PathShellFolder.ParseDisplayName(0, nil, PWideChar(AFileNames[i]), Eaten, Pos^, Attr)) then Exit;
Inc(Pos);
end;
PathShellFolder.GetUIObjectOf(0, AFileNames.Count, IDLists^, IDataObject, nil, Result);
finally
Pos := IDLists;
for i := 0 to AFileNames.Count - 1 do
begin
if Assigned(Pos^) then
CoTaskMemFree(Pos^);
Inc(Pos);
end;
FreeMem(IDLists);
end;
finally
PathShellFolder := nil;
end;
finally
CoTaskMemFree(PathIDList);
end;
finally
Desktop := nil;
end;
end;
我制作了一个表示文件列表的控件,我希望能够将文件从我的控件拖到其他处理文件的应用程序。我实现了 IDragSource 接口(如下所示),但是当我拖动时,文件仅被 windows 资源管理器接受,其他应用程序如 Firefox、Yahoo Messenger、Photoshop...不接受我的文件。我做错了什么?我觉得 IDataObject 设置不正确,恐怕我必须自己实现它......这对我来说是一项非常复杂的工作,因为我刚刚开始使用接口。
这里是重现问题的代码:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ActiveX, ShlObj;
type
TMyControl = class(TMemo, IDropSource)
private
function QueryContinueDrag(fEscapePressed:BOOL; grfKeyState:Longint):HResult; stdcall;
function GiveFeedback(dwEffect:Longint):HResult; stdcall;
procedure DoDragAndDrop;
function GetFileListDataObject:IDataObject;
protected
procedure MouseMove(Shift:TShiftState; X,Y:Integer); override;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
public
MyMemo:TMyControl;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{TMyControl}
function TMyControl.QueryContinueDrag(fEscapePressed:BOOL; grfKeyState:Longint):HResult;
begin
if fEscapePressed then Result:=DRAGDROP_S_CANCEL
else if (grfKeyState and (MK_LBUTTON or MK_RBUTTON) = 0) then Result:=DRAGDROP_S_DROP
else Result:=S_OK;
end;
function TMyControl.GiveFeedback(dwEffect:Longint):HResult;
begin
Result:=DRAGDROP_S_USEDEFAULTCURSORS;
end;
procedure TMyControl.DoDragAndDrop;
var AllowedEffects,DropEffect:Longint;
DataObj:IDataObject;
begin
AllowedEffects:=DROPEFFECT_COPY;
DataObj:=GetFileListDataObject;
if DataObj <> nil then
DoDragDrop(DataObj, self, AllowedEffects, DropEffect);
end;
function TMyControl.GetFileListDataObject:IDataObject;
var Desktop:IShellFolder;
Attr,Eaten:ULONG;
Count,x:Integer;
Pidls:array of PItemIDList;
begin
Result:=nil;
Count:=Lines.Count;
if Count<1 then Exit;
if Failed(SHGetDesktopFolder(Desktop)) then Exit;
SetLength(Pidls,Count);
for x:=0 to Count-1 do Pidls[x]:=nil;
try
for x:=0 to Count-1 do
if Failed(Desktop.ParseDisplayName(0, nil, PWideChar(Lines[x]), Eaten, Pidls[x], Attr)) then Exit;
Desktop.GetUIObjectOf(0, Count, Pidls[0], IDataObject, nil, Result);
finally
for x:=0 to Count-1 do
if Pidls[x]<>nil then CoTaskMemFree(Pidls[x]);
end;
end;
procedure TMyControl.MouseMove(Shift:TShiftState; X,Y:Integer);
begin
if ssLeft in Shift then DoDragAndDrop;
inherited;
end;
//---------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
MyMemo:=TMyControl.Create(Form1);
MyMemo.Parent:=Form1;
MyMemo.Align:=alClient;
end;
end.
问题是您使用了不正确的 Desktop.GetUIObjectOf 调用。当您调用 SomeFolder.GetUIObjectOf 时,项目必须是 SomeFolder 的子项。但就您而言,情况并非如此。尝试这样的事情:
type
PPItemIDList = ^PItemIDList;
function GetFileListDataObject(AParentWnd: HWND; const APath: string; AFileNames: TStrings): IDataObject;
var
Desktop: IShellFolder;
Eaten, Attr: ULONG;
i: Integer;
PathIDList: PItemIDList;
PathShellFolder: IShellFolder;
IDLists: PPItemIDList;
IDListsSize: Integer;
Pos: PPItemIDList;
begin
Result := nil;
if AFileNames.Count < 1 then Exit;
if Failed(SHGetDesktopFolder(Desktop)) then Exit;
try
Attr := 0;
if Failed(Desktop.ParseDisplayName(AParentWnd, nil, PWideChar(APath), Eaten, PathIDList, Attr)) then Exit;
try
if Failed(Desktop.BindToStorage(PathIDList, nil, IShellFolder, PathShellFolder)) then Exit;
try
IDListsSize := SizeOf(PItemIDList) * AFileNames.Count;
GetMem(IDLists, IDListsSize);
try
ZeroMemory(IDLists, IDListsSize);
Pos := IDLists;
for i := 0 to AFileNames.Count - 1 do
begin
Attr := 0;
if Failed(PathShellFolder.ParseDisplayName(0, nil, PWideChar(AFileNames[i]), Eaten, Pos^, Attr)) then Exit;
Inc(Pos);
end;
PathShellFolder.GetUIObjectOf(0, AFileNames.Count, IDLists^, IDataObject, nil, Result);
finally
Pos := IDLists;
for i := 0 to AFileNames.Count - 1 do
begin
if Assigned(Pos^) then
CoTaskMemFree(Pos^);
Inc(Pos);
end;
FreeMem(IDLists);
end;
finally
PathShellFolder := nil;
end;
finally
CoTaskMemFree(PathIDList);
end;
finally
Desktop := nil;
end;
end;