通过SHFileOperation()进行文件复制 - 使用

时间:2016-04-20 16:20:23

标签: delphi winapi

我找到了一些使用SHFileOperation()复制文件的代码,但我对SHFILEOPSTRUCT structure中的所有可用标记感到困惑。

这就是我在做什么。我有一个递归过程,根据我的set-mask填充TStringList所有文件。我逐步完成TStringList并传递FileFromFileTo的完整路径。

F.Wnd:=frmMain.Handle;
F.wFunc:=FO_COPY;
F.pFrom:=PChar(FileFrom+#0);
F.pTo:=PChar(FileTo+#0);
Err:=ShFileOperation(F);

这就是我想要做的事。

  1. 如果文件大于10mb,那么我想显示Windows'进度对话框,如果小则显示任何内容。

  2. 能够中止复制过程,无需需要单击“进度”对话框中的“X”,因为复制大量较小的文件时可能无法显示。我有一个“取消”按钮和一个布尔“取消点击”,但我看不到如何从SHFileOperation()返回“中止”。

  3. 我知道我可以将整个文件夹传递给SHFileOperation()并让它以递归方式运行,但我需要在每个文件上处理其他内容,所以踩TStringList是我需要做的它,但我愿意接受建议。

    最后问题:

    我需要为10mb以下的尺寸设置fFlags

    我需要为超过10mb的尺寸设置fFlags

    if ThisFileSize < 10000000 then
      F.fFlags:=F.fFlags or ... else
      F.fFlags:= ...;
    

    此代码不会复制,但注释行会复制。

    lpCopyProgress:=@MyCopyProgressCallback;
    Err:=0;
    StopCopy:=False;
    if not CopyFileEx(PChar(FileFrom),PChar(FileTo),lpCopyProgress,nil,@StopCopy,0) then
    //          if not CopyFile(PChar(FileFrom),PChar(FileTo),False) then
    begin
      Err:=GetLastError;
      if Err = ERROR_REQUEST_ABORTED then
        Break;
    end;
    

1 个答案:

答案 0 :(得分:3)

使用SHFileOperation()时,您可以使用FOF_SILENT标志来阻止显示标准进度对话框。

但是,一旦它开始运行,就没有可用于以可编程方式中止SHFileOperation()的选项。为此,请改用CopyFileEx()CopyFile2()

这两个函数都允许您以两种不同的方式中止副本:

  • 他们都接受指向BOOL变量的指针作为输入。如果您的代码在函数运行时将BOOL变量设置为TRUE,则副本将被中止。

  • 他们都接受指向进度报告的回调函数的指针。在复制的各个阶段调用回调。如果回调返回值CANCELSTOP,则副本将中止(如果是CANCEL,则删除目标文件,而STOP允许文件将在以后恢复。)

无论哪种方式,这两个功能都不会显示Windows&#39;自动拥有进度对话框,但您可以使用display it manually界面IProgressDialog。或者,您可以显示自己的自定义对话框。

在处理多个文件时,在每个单独的文件上显示/隐藏进度对话框并不是一个好的用户体验。在操作系统上浪费开销来创建和销毁对话框。潜在的闪烁对于用户在视觉上看是不是很有趣。您应该在需要时显示对话框,然后保持可见并更新,直到完成最后一个文件。

尝试这样的事情:

var
  // this is redundant since IProgressDialog has its own
  // Cancel button, this is just an example to demonstrate
  // cancellation in code...
  CancelClicked: BOOL = FALSE;

function MyCopyProgressCallback(TotalFileSize, TotalBytesTransferred, StreamSize, StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber: DWORD; dwCallbackReason: DWORD; hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall;
var
  msg: WideString;
begin
  msg := WideFormat('Transferred %d of %d bytes', [TotalBytesTransferred.QuadPart, TotalFileSize.QuadPart]);
  IProgressDialog(lpData).SetLine(2, PWideChar(msg), False, PPointer(nil)^);
  if IProgressDialog(lpData).HasUserCancelled then
    Result := PROGRESS_CANCEL
  else
    Result := PROGRESS_CONTINUE;
end;

...

var
  FileFrom: string;
  FileTo: string;
  I: Integer;
  ProgressDialog: IProgressDialog;
begin
  ...

  CancelClicked := FALSE;

  OleCheck(CoCreateInstance(CLSID_ProgressDialog, nil, CLSCTX_INPROC_SERVER, IProgressDialog, ProgressDialog));
  try
    ProgressDialog.SetTitle('Processing files');
    ProgressDialog.SetCancelMsg('Canceling, please wait...', PPointer(nil)^);
    ProgressDialog.SetProgress(0, TheStringList.Count);
    ProgressDialog.StartProgressDialog(frmMain.Handle, nil, PROGDLG_MODAL or PROGDLG_AUTOTIME or PROGDLG_NOMINIMIZE, PPointer(nil)^);
    ProgressDialog.Timer(PDTIMER_RESET, PPointer(nil)^);

    for I := 0 to TheStringList.Count-1 do
    begin
      FileFrom := ...;
      FileTo := ...;

      ProgressDialog.SetLine(1, PWideChar(WideString(FileFrom)), True, PPointer(nil)^);
      ProgressDialog.SetLine(2, '', False, PPointer(nil)^);
      if ProgressDialog.HasUserCancelled then
        Break;

      ...

      if not CopyFileEx(PChar(FileFrom), PChar(FileTo), @MyCopyProgressCallback, Pointer(ProgressDialog), @CancelClicked, 0) then
      begin
        if GetLastError = ERROR_REQUEST_ABORTED then
          Break;

        // something else happened during the copy, so
        // you can decide whether to stop the loop here
        // or just move on to the next file...
      end;

      ...

      ProgressDialog.SetProgress(I+1, TheStringList.Count);
    end;
  finally
    ProgressDialog.StopProgressDialog;
    ProgressDialog := nil;
  end;

  ...

end;

或者,您可以使用IFileOperation界面。这允许您:

  • 使用CopyItem()CopyItems()方法提前排队所有文件路径。

  • 使用PerformOperations()方法一次执行所有排队副本。

  • 实施IFileOperationProgressSink界面,Advise()接口以接收进度更新。这包括在复制每个文件之前(PreCopyItem())和之后(PostCopyItem())执行您自己的操作的选项。从IFileOperationProgressSink方法返回的任何错误都将中止整个复制序列。

  • 让它自动显示标准的Windows进度对话框。它将延迟显示对话框,直到复制序列运行超过几秒钟。如果快速复制一堆小文件,则无需显示对话框,但一旦发生明显的延迟,将显示当前和后续文件的对话框,直到序列完成。如果要自定义行为,可以实现IOperationsProgressDialog界面并将其传递给IFileOperation::SetProgressDialog()方法。