【问题标题】:Copying files to clipboard and then pasting them into their original folder does not work将文件复制到剪贴板,然后将它们粘贴到其原始文件夹中不起作用
【发布时间】:2014-09-23 10:39:40
【问题描述】:

我遇到了一个令人费解的情况。我在 Delphi 中使用以下代码将文件列表复制到剪贴板;

procedure TfMain.CopyFilesToClipboard(FileList: string);
const
  C_UNABLE_TO_ALLOCATE_MEMORY = 'Unable to allocate memory.';
  C_UNABLE_TO_ACCESS_MEMORY = 'Unable to access allocated memory.';
var
  DropFiles: PDropFiles;
  hGlobal: THandle;
  iLen: Integer;
begin
  iLen := Length(FileList);
  hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or
  GMEM_ZEROINIT, SizeOf(TDropFiles) + ((iLen + 2) * SizeOf(Char)));
  if (hGlobal = 0) then
    raise Exception.Create(C_UNABLE_TO_ALLOCATE_MEMORY);
  try DropFiles := GlobalLock(hGlobal);
    if (DropFiles = nil) then raise Exception.Create(C_UNABLE_TO_ACCESS_MEMORY);
    try
      DropFiles^.pFiles := SizeOf(TDropFiles);
      DropFiles^.fWide := True;
      if FileList <> '' then
        Move(FileList[1], (PByte(DropFiles) + SizeOf(TDropFiles))^,
      iLen * SizeOf(Char));
    finally
      GlobalUnlock(hGlobal);
    end;
    Clipboard.SetAsHandle(CF_HDROP, hGlobal);
  except
    GlobalFree(hGlobal);
  end;
end;

(这似乎是互联网上流行的一段代码)

使用我的应用程序,将文件复制到剪贴板后,我可以使用 Windows 资源管理器将它们粘贴到所有其他文件夹中,但文件最初来自的文件夹除外!我期待它的行为就像一个普通的 Windows 副本(即在粘贴时它应该创建一个后缀为“-Copy”的文件),但这似乎不起作用。有什么线索吗?

【问题讨论】:

  • 你能分享你的“粘贴”代码吗?复制代码貌似没问题。
  • @mg30rg:没有“粘贴”代码,因为他正在使用资源管理器粘贴文件?
  • @whosrdaddy - 我真的不明白他的代码有什么意义。我以为他是在用资源管理器作为现实检查设备来查看文件列表是否真的被复制了。
  • @dudeinmoon - 所以你想使用资源管理器来粘贴你的文件?如果你是并且它失败了,它究竟是如何失败的?它会抛出 error/exception 消息吗?它是否粘贴同时手动删除的文件?如果您不提供更多信息,我们将无法提供帮助。
  • @mg30rg - 你好,是的,我正在使用 Windows 资源管理器粘贴文件(我正在尝试创建一个简单的剪贴板文件管理器)。它失败的方式是,当您转到文件夹并右键单击粘贴时,什么也没有发生。如果我转到另一个文件夹并右键单击粘贴,它可以工作

标签: delphi clipboard copy-paste


【解决方案1】:

当唯一可用的剪贴板格式为 CF_HDROP 时,我无法让 Windows 资源管理器粘贴到源文件夹中。但是,如果文件名是在 IDataObject 中提供的,则可以正常工作。

如果所有文件都来自同一个源文件夹,您可以检索源文件夹的 IShellFolder 并在其中查询各个文件的子 PIDL,然后使用 IShellFolder.GetUIObjectOf() 获取代表的 IDataObject文件。然后使用OleSetClipboard() 将该对象放在剪贴板上。例如:

uses
  System.Classes, Winapi.Windows, Winapi.ActiveX, Winapi.Shlobj, Winapi.ShellAPI, System.Win.ComObj;

procedure CopyFilesToClipboard(const Folder: string; FileNames: TStrings);
var
  SF: IShellFolder;
  PidlFolder: PItemIDList;
  PidlChildren: array of PItemIDList;
  Eaten: UINT;
  Attrs: DWORD;
  Obj: IDataObject;
  I: Integer;
begin
  if (Folder = '') or (FileNames = nil) or (FileNames.Count = 0) then Exit;
  OleCheck(SHParseDisplayName(PChar(Folder), nil, PidlFolder, 0, Attrs));
  try
    OleCheck(SHBindToObject(nil, PidlFolder, nil, IShellFolder, Pointer(SF)));
  finally
    CoTaskMemFree(PidlFolder);
  end;
  SetLength(PidlChildren, FileNames.Count);
  for I := Low(PidlChildren) to High(PidlChildren) do
    PidlChildren[i] := nil;
  try
    for I := 0 to FileNames.Count-1 do
      OleCheck(SF.ParseDisplayName(0, nil, PChar(FileNames[i]), Eaten, PidlChildren[i], Attrs));
    OleCheck(SF.GetUIObjectOf(0, FileNames.Count, PIdlChildren[0], IDataObject, nil, obj));
  finally
    for I := Low(PidlChildren) to High(PidlChildren) do
    begin
      if PidlChildren[i] <> nil then
        CoTaskMemFree(PidlChildren[i]);
    end;
  end;
  OleCheck(OleSetClipboard(obj));
  OleCheck(OleFlushClipboard);
end;

更新:如果文件在不同的源文件夹,可以使用CFSTR_SHELLIDLIST格式:

uses
  System.Classes, System.SysUtils, Winapi.Windows, Winapi.ActiveX, Winapi.Shlobj, Winapi.ShellAPI, System.Win.ComObj, Vcl.Clipbrd;

{$POINTERMATH ON}

function HIDA_GetPIDLFolder(pida: PIDA): LPITEMIDLIST;
begin
  Result := LPITEMIDLIST(LPBYTE(pida) + pida.aoffset[0]);
end;

function HIDA_GetPIDLItem(pida: PIDA; idx: Integer): LPITEMIDLIST;
begin
  Result := LPITEMIDLIST(LPBYTE(pida) + (PUINT(@pida.aoffset[0])+(1+idx))^);
end;

var
  CF_SHELLIDLIST: UINT = 0;

type
  CidaPidlInfo = record
    Pidl: PItemIDList;
    PidlOffset: UINT;
    PidlSize: UINT;
  end;

procedure CopyFilesToClipboard(FileNames: TStrings);
var
  PidlInfo: array of CidaPidlInfo;
  Attrs, AllocSize: DWORD;
  gmem: THandle;
  ida: PIDA;
  I: Integer;
begin
  if (FileNames = nil) or (FileNames.Count = 0) or (CF_SHELLIDLIST = 0) then Exit;
  SetLength(PidlInfo, FileNames.Count);
  for I := Low(PidlInfo) to High(PidlInfo) do
    PidlInfo[I].Pidl := nil;
  try
    AllocSize := SizeOf(CIDA)+(SizeOf(UINT)*FileNames.Count)+SizeOf(Word);
    for I := 0 to FileNames.Count-1 do
    begin
      OleCheck(SHParseDisplayName(PChar(FileNames[I]), nil, PidlInfo[I].Pidl, 0, Attrs));
      PidlInfo[I].PidlOffset := AllocSize;
      PidlInfo[I].PidlSize := ILGetSize(PidlInfo[I].Pidl);
      Inc(AllocSize, PidlInfo[I].PidlSize);
    end;
    gmem := GlobalAlloc(GMEM_MOVEABLE, AllocSize);
    if gmem = 0 then RaiseLastOSError;
    try
      ida := PIDA(GlobalLock(gmem));
      if ida = nil then RaiseLastOSError;
      try
        ida.cidl := FileNames.Count;
        ida.aoffset[0] := SizeOf(CIDA)+(SizeOf(UINT)*FileNames.Count);
        HIDA_GetPIDLFolder(ida).mkid.cb := 0;
        for I := 0 to FileNames.Count-1 do
        begin
          ida.aoffset[1+I] := PidlInfo[I].PidlOffset;
          Move(PidlInfo[I].Pidl^, HIDA_GetPIDLItem(ida, I)^, PidlInfo[I].PidlSize);
        end;
      finally
        GlobalUnlock(gmem);
      end;
      Clipboard.SetAsHandle(CF_SHELLIDLIST, gmem);
    except
      GlobalFree(gmem);
      raise;
    end;
  finally
    for I := Low(PidlInfo) to High(PidlInfo) do
      CoTaskMemFree(PidlInfo[I].Pidl);
  end;
end;

initialization
  CF_SHELLIDLIST := RegisterClipboardFormat(CFSTR_SHELLIDLIST);

或者:

procedure CopyFilesToClipboard(FileNames: TStrings);
var
  Pidls: array of PItemIdList;
  Attrs: DWORD;
  I: Integer;
  obj: IDataObject;
begin
  if (FileNames = nil) or (FileNames.Count = 0) then Exit;
  SetLength(Pidls, FileNames.Count);
  for I := Low(Pidls) to High(Pidls) do
    Pidls[I] := nil;
  try
    for I := 0 to FileNames.Count-1 do
      OleCheck(SHParseDisplayName(PChar(FileNames[I]), nil, Pidls[I], 0, Attrs));
    OleCheck(CIDLData_CreateFromIDArray(nil, FileNames.Count, PItemIDList(Pidls), obj));
  finally
    for I := Low(Pidls) to High(Pidls) do
      CoTaskMemFree(Pidls[I]);
  end;
  OleCheck(OleSetClipboard(obj));
  OleCheck(OleFlushClipboard);
end;

但是,我发现 Windows 资源管理器有时但并不总是允许将 CFSTR_SHELLIDLIST 粘贴到引用文件的源文件夹中。我不知道阻止 Windows 资源管理器粘贴的标准是什么。也许是某种权限问题?

你应该听取微软的建议:

Handling Shell Data Transfer Scenarios

包括尽可能多的格式,你可以支持。您通常不知道数据对象将被删除到哪里。这种做法提高了数据对象包含放置目标可以接受的格式的几率。

【讨论】:

  • 对不起,我还想补充一点,被复制的文件集合可能来自多个源文件夹。
  • @dude - 普通的 Windows 副本不会这样做。
  • @SertacAkyuz:用户不能同时从不同文件夹中选择多个文件,但应用程序可以,并且 Windows 资源管理器能够粘贴来自多个来源的文件。
  • 感谢 Remy,这非常有效。我还没有遇到任何权限问题(可能是因为我的应用程序有一个请求管理员权限的清单?)。无论如何,我会留意这件事。非常感谢您的帮助。
猜你喜欢
  • 2017-10-14
  • 1970-01-01
  • 2011-01-03
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2017-05-15
相关资源
最近更新 更多