我正在编写DirectoryTreeDrawer
类随附的库。它的主要目的是根据提供的目录路径或TextWriter
实例绘制树结构(到基础DirectoryInfo
)。
以下是使用DirectoryTreeDrawer
类的示例.NET Core控制台应用程序:
public class Program
{
public static void Main()
{
using (var drawer = new DirectoryTreeDrawer(System.Console.Out))
{
var workingDirectoryPath = System.IO.Directory.GetCurrentDirectory();
drawer.DrawTree(workingDirectoryPath);
}
}
}
运行上面的命令将产生类似于下面几行的输出(为简洁起见被截断):
ConsoleDemo\
├────ConsoleDemo.csproj.nuget.cache
├────ConsoleDemo.csproj.nuget.g.props
├────ConsoleDemo.csproj.nuget.g.targets
├────project.assets.json
├──ConsoleDemo.csproj
├──Program.cs
DrawTree()
调用PrintDirectoryContent()
,在此开始递归魔术。从提供的路径开始,该程序递归地遍历子目录,并以反映原始目录相对深度的方式打印文件和目录的名称。
public void DrawTree(DirectoryInfo directoryInfo)
{
var searchPattern = "*";
var searchOption = SearchOption.TopDirectoryOnly;
PrintDirectoryName(directoryInfo, depth: 0);
PrintDirectoryContent(directoryInfo, searchPattern, searchOption, depth: 0);
}
private void PrintDirectoryContent(DirectoryInfo currentDirectory, string searchPattern, SearchOption searchOption, int depth)
{
var directories = currentDirectory.GetDirectories(searchPattern, searchOption);
var directoriesCount = directories.GetLength(0);
for (var directoryIndex = 0; directoryIndex < directoriesCount; directoryIndex++)
{
var directoryInfo = directories[directoryIndex];
PrintDirectoryName(directoryInfo, depth + 1);
PrintDirectoryContent(directoryInfo, searchPattern, searchOption, depth + 1);
}
var files = currentDirectory.GetFiles(searchPattern, searchOption);
var filesCount = files.GetLength(0);
for (var fileIndex = 0; fileIndex < filesCount; fileIndex++)
{
var fileInfo = files[fileIndex];
PrintFileName(fileInfo, depth + 1);
}
}
文件(或目录)前缀由单个├
符号组成,后跟分别重复到当前深度的─
符号。
private void PrintDirectoryName(DirectoryInfo directoryInfo, int depth)
{
_textWriter.WriteLine($"{CreateDepthPrefix(depth)}{directoryInfo.Name}{Path.DirectorySeparatorChar}");
}
private void PrintFileName(FileInfo fileInfo, int depth)
{
_textWriter.WriteLine($"{CreateDepthPrefix(depth)}{fileInfo.Name}");
}
private string CreateDepthPrefix(int depth)
{
return $"{'├'}{new string('─', 2 * depth)}";
}
我想用与通常的前缀不同的前缀标记最后一个条目(文件或目录)。我不想以├
符号开头,而是以└
符号开头。所以输出的最后一行代替:
├──Program.cs
...看起来像这样:
└──Program.cs
对我来说,问题在于如何知道哪个文件或目录是最后一个要打印的文件。如果我知道,可以在打印前缀时简单地进行检查。
是否有更好的解决方案,然后将所有条目(文件和目录的名称和深度)保存到集合中,然后对“ being-the-last-entry”条件进行检查?或者也许是唯一的一个?
该库是开放源代码,可在GitLab上使用。在这里,您还可以找到原始的DirectoryTreeDrawer
类。请注意,出于代码简洁的目的,我对其进行了大量编辑。
我想说明的是,我并没有要求对代码进行审查,就像有人看起来那样。我遇到了问题,正在寻求解决方案。
答案 0 :(得分:1)
我的简短回答是我在评论中写的内容,彼得已经提供了答案,但这是另一种格式,可能被认为更具可读性,它为每个目录和文件提供缩进,因此更容易看看它属于哪个父母。请参阅最后一张图片以获取示例输出。
这是通过跟踪父级文件中的最后一个文件或文件夹并将其传递给PrintItem
方法来完成的(这是我在对问题的评论中提出的答案)。另一个更改是前缀从父级传递到子级,因此我们可以包括嵌套项目的连接器。为了跟踪“嵌套”的项(意味着该项的父项具有在当前项之后显示的同级项),我们将一个IsNested
参数传递给PrintDirectory
方法,以便前缀可以进行相应的更新。
我也将其修改为static
类,将TextWriter
传递给不同的方法。不知道这是否真的更好,但是除了TextWriter
之外,其他所有内容似乎都应该是静态的。
public static class DirectoryTreeDrawer
{
public static void DrawTree(string directoryPath, TextWriter textWriter)
{
DrawTree(new DirectoryInfo(directoryPath), textWriter);
}
public static void DrawTree(DirectoryInfo directoryInfo, TextWriter textWriter)
{
PrintDirectory(directoryInfo, textWriter);
}
private static void PrintDirectory(DirectoryInfo directory, TextWriter textWriter,
string prefix = " ", string searchPattern = "*", SearchOption searchOption =
SearchOption.TopDirectoryOnly, bool isLast = true, bool isNested = false)
{
PrintItem(directory.Name, prefix, isLast, textWriter, true);
var subDirs = directory.GetDirectories(searchPattern, searchOption);
var files = directory.GetFiles(searchPattern, searchOption);
// If this is a "nested" directory, add the parent's connector to the prefix
prefix += isNested ? "│ " : " ";
for (var directoryIndex = 0; directoryIndex < subDirs.Length; directoryIndex++)
{
var isLastChild = directoryIndex == subDirs.Length - 1 && files.Length == 0;
// If the parent has files or other directories, mark this as "nested"
var isNestedDir = files.Length > 0 || !isLastChild;
PrintDirectory(subDirs[directoryIndex], textWriter, prefix, searchPattern,
searchOption, isLastChild, isNestedDir);
}
for (var fileIndex = 0; fileIndex < files.Length; fileIndex++)
{
var isLastFile = fileIndex == files.Length - 1;
PrintItem(files[fileIndex].Name, prefix, isLastFile, textWriter);
}
}
private static void PrintItem(string name, string prefix, bool isLastItem,
TextWriter textWriter, bool isDirectory = false)
{
var itemConnector = isLastItem ? "└─" : "├─";
var suffix = isDirectory ? Path.DirectorySeparatorChar.ToString() : "";
textWriter?.WriteLine($"{prefix}{itemConnector}{name}{suffix}");
}
}
用法
private static void Main()
{
DirectoryTreeDrawer.DrawTree(Environment.CurrentDirectory, Console.Out);
GetKeyFromUser("\nDone! Press any key to exit...");
}
输出
从输出来看,很明显,我多年来一直在重复使用同一项目,并且Debug
文件夹中有一堆无关紧要的文件...:)
答案 1 :(得分:0)
我要做什么,我将列出所有这些内容,然后更改光标位置,然后更改打印的字符:
public void DrawTree(string directoryPath)
{
if (string.IsNullOrWhiteSpace(directoryPath))
{
throw new ArgumentException(
"Provided directory path is null, emtpy " +
"or consists of only whitespace characters.",
nameof(directoryPath));
}
DrawTree(new DirectoryInfo(directoryPath));
//remember current position because we need to return position to it
int currentCursorPositionTop = Console.CursorTop;
//set position to the last row
Console.SetCursorPosition(0, Console.CursorTop-1);
//change the first charachter
Console.Write("└");
//return cursor position to the previous one so our "Press any key to continue" can apear below our list.
Console.SetCursorPosition(0, currentCursorPositionTop);
}
更新: 为了维持更高的抽象水平,我建议将数据写入 列出然后更改最后一个的前缀:
class ContentItem {
public string Prefix {get;set; }
public int Depth {get;set; }
public string Name {get;set; }
public override string ToString() {
return $"{Prefix}{(new String("-", Depth))} {Name}";
}
}
因此,可以更改列表项前缀来代替更改控制台光标的位置:
var items[items.Count()-1].Prefix = "└";
,然后循环遍历这些项目,并将其传递给TextWriter,StreamWriter或其他任何对象。
答案 2 :(得分:0)
问题在于如何知道哪个文件或目录是最后一个要打印的文件。如果我知道,可以在打印前缀时简单地进行检查。
鉴于您发布的代码,知道您是否在最后一个条目很简单,因为您的递归方法具有一个depth
参数,并且为每个项目的循环编制了索引。这意味着您只需要查看自己的int
值,例如:
private void PrintDirectoryContent(DirectoryInfo currentDirectory, string searchPattern, SearchOption searchOption, int depth)
{
var directories = currentDirectory.GetDirectories(searchPattern, searchOption);
var directoriesCount = directories.GetLength(0);
for (var directoryIndex = 0; directoryIndex < directoriesCount; directoryIndex++)
{
var directoryInfo = directories[directoryIndex];
PrintDirectoryName(directoryInfo, depth + 1);
PrintDirectoryContent(directoryInfo, searchPattern, searchOption, depth + 1);
}
var files = currentDirectory.GetFiles(searchPattern, searchOption);
var filesCount = files.GetLength(0);
for (var fileIndex = 0; fileIndex < filesCount; fileIndex++)
{
var fileInfo = files[fileIndex];
PrintFileName(fileInfo, depth + 1, depth == 0 && fileIndex == filesCount - 1);
}
}
private void PrintDirectoryName(DirectoryInfo directoryInfo, int depth)
{
_textWriter.WriteLine($"{CreateDepthPrefix('├', depth)}{directoryInfo.Name}{Path.DirectorySeparatorChar}");
}
private void PrintFileName(FileInfo fileInfo, int depth, bool isLast)
{
_textWriter.WriteLine($"{CreateDepthPrefix(isLast ? '└' : '├', depth)}{fileInfo.Name}");
}
private string CreateDepthPrefix(char initialChar, int depth)
{
return $"{initialChar}{new string('─', 2 * depth)}";
}
即表达式depth == 0 && fileIndex == filesCount - 1
就是我添加到isLast
方法中的PrintFileName()
参数的确切值。
请注意,由于缺乏良好的Minimal, Complete, and Verifiable example,我不得不对您发布的代码进行一些调整,以使其能够编译和运行。您发布的代码也不会产生您的问题所说明的输出;具体来说,顶级目录名称也以'├'
字符结尾。我猜在您的实际代码中,该行是特例。
我没有花任何时间试图使输出与您所说的完全匹配,而是宁愿只关注眼前的问题。我假设您可以将上面的代码(包含该问题的答案)改编为实际使用的任何代码。