Delphi,删除带内容的文件夹

12

当我有一个文件夹中包含子文件夹时,这段代码无法删除文件夹...是否存在错误?

procedure TForm.Remove(Dir: String);
var
  Result: TSearchRec; Found: Boolean;
begin
  Found := False;
  if FindFirst(Dir + '\*', faAnyFile, Result) = 0 then
    while not Found do begin
      if (Result.Attr and faDirectory = faDirectory) AND (Result.Name <> '.') AND (Result.Name <> '..') then Remove(Dir + '\' + Result.Name)
      else if (Result.Attr and faAnyFile <> faDirectory) then DeleteFile(Dir + '\' + Result.Name);
      Found := FindNext(Result) <> 0;
    end;
  FindClose(Result); RemoveDir(Dir);
end;

1
顺便提一下,这段代码可以用重复直到循环来编写,从而避免需要“Found”本地变量的需要。 - David Heffernan
1
另外,有点有趣的是,如果找到了文件,“Found”是“false”,如果没有找到文件,“Found”是“true”... - Andreas Rejbrand
如果以下任何答案解决了您的问题,您应该通过点击答案左侧的复选标记来接受它。如果有多个答案解决了您的问题,请选择“最佳”答案进行接受。 - Andreas Rejbrand
一些重复内容:https://dev59.com/5XHYa4cB1Zd3GeqPN6aN,https://dev59.com/Jmgt5IYBdhLWcg3w0wok - Vadzim
7个回答

36

最简单的方法是调用TDirectory.Delete(Dir, True)

TDirectory可以在相当近期的RTL添加中找到,其中包含了IOUtils

True标志传递给Recursive参数,这意味着在删除目录之前会清空目录中的内容,这是删除目录的重要部分。


在评论中,您告诉我们您使用的是 Delphi 7,因此无法使用此代码。
您的代码看起来大部分都很好。但是,您的意思并不是:
(Result.Attr and faAnyFile <> faDirectory)

我认为你的意思是:

(Result.Attr and faDirectory <> faDirectory)

我可能会这样写:

procedure TMyForm.Remove(const Dir: string);
var
  Result: TSearchRec;
begin
  if FindFirst(Dir + '\*', faAnyFile, Result) = 0 then
  begin
    Try
      repeat
        if (Result.Attr and faDirectory) = faDirectory then
        begin
          if (Result.Name <> '.') and (Result.Name <> '..') then
            Remove(Dir + '\' + Result.Name)
        end
        else if not DeleteFile(Dir + '\' + Result.Name) then
          RaiseLastOSError;
      until FindNext(Result) <> 0;
    Finally
      FindClose(Result);
    End;
  end;
  if not RemoveDir(Dir) then
    RaiseLastOSError;
end;

12
请不要将 EndTryFinally 大写! - Andreas Rejbrand
1
@Andreas 太晚了!我这样做是为了使它们突出,因为它们具有非标准的控制流程。它们就像高档的goto语句。 - David Heffernan
我认为这个版本有一个错误,它会尝试删除一个带有“.”或“..”的“文件”。所有问题都归结于在if (Result.Attr and faDirectory) = faDirectory之后缺少begin..end,Remy在他的版本中做对了。 - Wodzu
@Wodzu 谢谢。你是对的。这就是为什么我从不编写这样的代码。在我的工作场所,我们在所有块上使用begin/end。 - David Heffernan
很高兴能够帮助,没有开始/结束很容易陷入这种陷阱 :) - Wodzu

30

如果我是你,我会告诉操作系统删除整个文件夹及其所有内容。可以通过编写以下代码实现 (使用ShellAPI)

var
  ShOp: TSHFileOpStruct;
begin
  ShOp.Wnd := Self.Handle;
  ShOp.wFunc := FO_DELETE;
  ShOp.pFrom := PChar('C:\Users\Andreas Rejbrand\Desktop\Test\'#0);
  ShOp.pTo := nil;
  ShOp.fFlags := FOF_NO_UI;
  SHFileOperation(ShOp);

[If you do

  ShOp.fFlags := 0;

相反,你会得到一个漂亮的确认对话框。如果你这样做

ShOp.fFlags := FOF_NOCONFIRMATION;

如果操作需要一定时间,你将会看到进度条而不是确认对话框。最终,如果你添加了FOF_ALLOWUNDO标记,你将把目录移动到回收站而非永久删除。

ShOp.fFlags := FOF_ALLOWUNDO;

当然,您可以自由组合标志:
ShOp.fFlags := FOF_NOCONFIRMATION or FOF_ALLOWUNDO;

如果你没有指定FOF_NO_UI选项,将不会显示任何确认信息(但会有一个进度对话框),并且该目录将被移动到废纸篓中而不是永久删除。


PChar('C:\Users\Andreas Rejbrand\Desktop\Test\'#0) 这个写法相当奇怪。为什么不直接写 PChar(Dir) 呢?但如果你使用的是字面量,那么你可以直接写成 ShOp.pFrom := 'C:\Users\Andreas Rejbrand\Desktop\Test\'; - David Heffernan
3
根据文档pFrom字符串必须是双重空终止的。据我所知,如果没有添加#0,则仅保证以单个空字符结尾。 - Andreas Rejbrand
4
@David:SHFileOperation 函数需要一个以双空字符结尾的文件或文件夹列表。PChar 类型提供了第一个,而字符串拼接提供了第二个。 - Ken White
2
+1 使用操作系统来完成工作...不过,如果我删除文件夹的原因是什么,我会指定“没有UI”,这样标准确认可以在需要时引导出来。例如,当它不适合回收站或存在需要额外权限的子文件夹等情况。 - Marjan Venema
2
就此而言,这些方法不适用于服务,只适用于桌面应用程序,并且速度可能非常慢。 - Eric Grange
显示剩余2条评论

10

上次我需要删除一个有内容的文件夹时,我使用了JCL


uses JclFileUtils;

DeleteDirectory(DirToDelete, True);

最后一个参数表示文件是否应该进入回收站,这是一个不错的额外功能。


如果文件夹及其所有内容不适合回收站怎么办?DeleteDirectory是否使用ShellApi,以便处理这些情况就像操作系统所做的那样? - Marjan Venema
1
在幕后,“DeleteDirectory”实际上执行了Andreas解决方案所做的事情:调用“SHFileOperation”。但是,如果您已经在使用JCL,则调用DeleteDirectory只需要一行便捷的代码。不过,您应该检查返回值。 - Heinrich Ulbricht

6
为了解决原始问题 - 请尝试以下操作:
procedure TForm.Remove(const Dir: String);
var
  sDir: String;
  Rec: TSearchRec;
begin
  sDir := IncludeTrailingPathDelimiter(Dir);
  if FindFirst(sDir + '*.*', faAnyFile, Rec) = 0 then
  try
    repeat
      if (Rec.Attr and faDirectory) = faDirectory then
      begin
        if (Rec.Name <> '.') and (Rec.Name <> '..') then
          Remove(sDir + Rec.Name);
      end else
      begin
        DeleteFile(sDir + Rec.Name);
      end;
    until FindNext(Rec) <> 0;
  finally
    FindClose(Rec);
  end;
  RemoveDir(sDir);
end; 

4
uses DSiWin32;

DSiDeleteTree(folderName, false);

DSiWin32 是一个开源项目,采用“随意使用”许可证发布。



0

此过程适用于不同的格式:

procedure Remove(const FileName: string);
var
  Path: string;
  SearchRec: TSearchRec;
  procedure RemoveDirectory(const Dir: string);
  var
    SearchRec: TSearchRec;
  begin
    if FindFirst(Dir + '\*', faAnyFile, SearchRec) = 0 then
    begin
      try
        repeat
          if (SearchRec.Attr and faDirectory) = faDirectory then
          begin
            if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
              RemoveDirectory(Dir + '\' + SearchRec.Name)
          end
          else
            System.SysUtils.DeleteFile(Dir + '\' + SearchRec.Name);
        until FindNext(SearchRec) <> 0;
      finally
        System.SysUtils.FindClose(SearchRec);
      end;
    end;
    RemoveDir(Dir);
  end;

begin
  if DirectoryExists(FileName) then
    RemoveDirectory(FileName)
  else if FindFirst(FileName, faAnyFile, SearchRec) = 0 then
  begin
    repeat
      if (SearchRec.Name = '.') or (SearchRec.Name = '..') then
        Continue;

      Path := ExtractFilePath(FileName) + '\' + SearchRec.Name;
      if DirectoryExists(Path) then
        RemoveDirectory(Path)
      else
        System.SysUtils.DeleteFile(Path);

    until FindNext(SearchRec) <> 0;

    System.SysUtils.FindClose(SearchRec);
  end;
end;
  • D:\myFolder\ #删除目录
  • D:\myFolder\* #删除所有文件和目录
  • D:\myFolder\*.txt #删除文本文件
  • D:\myFolder\*lib*.dll #删除特定的dll文件
  • D:\myFolder\readme.txt #删除文件

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接