从路径列表中填充树形视图

26

我正在尝试从文件夹路径列表中填充树形视图,例如:

C:\WINDOWS\addins
C:\WINDOWS\AppPatch
C:\WINDOWS\AppPatch\MUI
C:\WINDOWS\AppPatch\MUI\040C
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI\0409

带有这样的输出:

├───addins
├───AppPatch
│   └───MUI
│       └───040C
├───Microsoft.NET
│   └───Framework
│       └───v2.0.50727
│           └───MUI
│               └───0409

注意列表中没有'C:\WINDOWS\Microsoft.NET'或者'C:\WINDOWS\Microsoft.NET\Framework'。我已经在这个问题上忙了将近两天,我的代码有很多 bug。希望能从这里得到帮助。

谢谢。

Eric


请参考这个问题 - PaulB
除非您发布代码,否则无法确定出现了什么问题。与其花费数天时间,为什么不使用第三方控件,例如FolderView呢? - logicnp
8个回答

30
private void Form1_Load(object sender, EventArgs e)
    {
        var paths = new List<string>
                        {
                            @"C:\WINDOWS\AppPatch\MUI\040C",
                            @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727",
                            @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI",
                            @"C:\WINDOWS\addins",
                            @"C:\WINDOWS\AppPatch",
                            @"C:\WINDOWS\AppPatch\MUI",
                            @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI\0409"
                        };

        treeView1.PathSeparator = @"\";

        PopulateTreeView(treeView1, paths, '\\');
}


private static void PopulateTreeView(TreeView treeView, IEnumerable<string> paths, char pathSeparator)
    {
        TreeNode lastNode = null;
        string subPathAgg;
        foreach (string path in paths)
        {
            subPathAgg = string.Empty;
            foreach (string subPath in path.Split(pathSeparator))
            {
                subPathAgg += subPath + pathSeparator;
                TreeNode[] nodes = treeView.Nodes.Find(subPathAgg, true);
                if (nodes.Length == 0)
                    if (lastNode == null)
                        lastNode = treeView.Nodes.Add(subPathAgg, subPath);
                    else
                        lastNode = lastNode.Nodes.Add(subPathAgg, subPath);
                else
                    lastNode = nodes[0];
            }
        }
    }

alt text


1
我测试了这种方法,对于大型树形结构表现不佳。怀疑是因为每次将节点逐个添加到树视图中。我们的工作项管理系统中有近20k条路径,即使使用BeginUpdate,这种方法最终会抛出ContextSwitchDeadlock异常。我测试了ykm29发布的LINQ方法,它在不到一秒钟的时间内完成了所有20k个节点的处理。 - GrayDwarf
ContextSwitchDeadlock并不一定意味着您的代码存在问题,只是有潜在的问题。如果您进入菜单中的Debug > Exceptions并展开Managed Debugging Assistants,您会发现ContextSwitchDeadlock已启用。如果您禁用它,VS将不再在处理项目时警告您。在某些情况下,您可能确实需要长时间运行操作。如果您正在调试并已停在某一行上,同时正在进行处理,则这也很有帮助-您不希望在深入研究问题之前就收到警告。 - ehosca

13

使用 LINQ 的版本:

public static TreeNode MakeTreeFromPaths(List<string> paths, string rootNodeName = "", char separator = '/')
{
    var rootNode = new TreeNode(rootNodeName);
    foreach (var path in paths.Where(x => !string.IsNullOrEmpty(x.Trim()))) {
        var currentNode = rootNode;
        var pathItems = path.Split(separator);
        foreach (var item in pathItems) {
            var tmp = currentNode.Nodes.Cast<TreeNode>().Where(x => x.Text.Equals(item));
            currentNode = tmp.Count() > 0 ? tmp.Single() : currentNode.Nodes.Add(item);
        }
    }
    return rootNode;
}

如果这会导致重复您的根节点,您可以传入任何rootNodeName,例如“asdf”,然后更新返回语句以使用rootNode.FirstNode。 - GrayDwarf

12

ehosca的答案是正确的,但有一个小问题,当我将路径更改为像这样

C:\WINDOWS\AppPatch\MUI\040C
D:\WIS\Microsoft.NET\Framework\v2.0.50727
E:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI
C:\WINDOWS\addins
C:\WINDOWS\AppPatch
C:\WINDOWS\AppPatch\MUI
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI\0409

enter image description here

它会像这样填充树视图。

但是通过添加一些额外的代码,我们可以避免这种情况。因此我改变了PopulateTreeView中的代码。

private static void PopulateTreeView(TreeView treeView, string[] paths, char pathSeparator)
        {
            TreeNode lastNode = null;
            string subPathAgg;
            foreach (string path in paths)
            {
                subPathAgg = string.Empty;
                foreach (string subPath in path.Split(pathSeparator))
                {
                    subPathAgg += subPath + pathSeparator;
                    TreeNode[] nodes = treeView.Nodes.Find(subPathAgg, true);
                    if (nodes.Length == 0)
                        if (lastNode == null)
                            lastNode = treeView.Nodes.Add(subPathAgg, subPath);
                        else
                            lastNode = lastNode.Nodes.Add(subPathAgg, subPath);
                    else
                        lastNode = nodes[0];
                }
                lastNode = null; // This is the place code was changed

            }
        }

现在它像这样运行得很好

输入图像描述在此处


我知道这是很久以前的事了,但是WinForms实现Treeviews的方式非常糟糕,以至于这是唯一能帮助我完成所需操作的代码。我需要复选框来填充,所以我只是把另一个我想要选中的路径列表扔进去,然后就万事大吉了!你的代码非常好用,谢谢! - Brian Truman

5

我使用了你的代码,它工作得很好,但我做了一点小修改以提高加载速度,特别是在使用大量文件时。似乎查找操作和字符串操作通常很慢。

private TreeNode PopulateTreeNode2(string[] paths, string pathSeparator)
    {
        if (paths == null)
            return null;

        TreeNode thisnode = new TreeNode();
        TreeNode currentnode;
        char[] cachedpathseparator = pathSeparator.ToCharArray();
        foreach (string path in paths)            {
            currentnode = thisnode;
            foreach (string subPath in path.Split(cachedpathseparator))
            {
                if (null == currentnode.Nodes[subPath])
                    currentnode = currentnode.Nodes.Add(subPath, subPath);
                else
                    currentnode = currentnode.Nodes[subPath];                   
            }
        }

        return thisnode;
    }

接下来你可以使用:

string[] paths =  {
                        @"C:\WINDOWS\AppPatch\MUI\040C",
                        @"D:\WINDOWS\Microsoft.NET\Framework\v2.0.50727",
                        @"E:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI",
                        @"C:\WINDOWS\addins",
                        @"C:\WINDOWS\AppPatch",
                        @"C:\WINDOWS\AppPatch\MUI",
                        @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MUI\0409"
                    };
TreeView treeview = new TreeView();
treeview.Nodes.Add(PopulateTreeNode2(paths, "\\"));

注意:在两个解决方案中可能需要进行一些字符串敏感性检查,以防止重新创建某些文件夹。

因为某些url可能指向磁盘上相同的文件夹,但拼写不同,例如: Windows ; WinDOWs , WINDOWS


2
TreeNodeCollection的字符串索引器在内部调用IndexOfKey,该方法会线性遍历所有子节点,直到找到键,然后再调用IsValidIndex。而Find方法则可以通过searchAllChildren参数控制搜索深度。根据数据结构的不同,您可以选择最优的方式。它们最终都会调用WindowsFormsUtils.SafeCompareStrings。 - ehosca

1
我尝试了ykm29的代码,但它无法正确填充子文件夹。这里的一些其他答案也使用单个路径部分来标识创建的TreeNodes。如果不同目录中存在重复的文件夹名称,可能会引起问题,我想。
所以在我的代码中,我将"currentPath"的值设置为我获取的文件夹的TreeNodes的键。下面的代码中的"ExtendedFileInfo"通过几个附加属性扩展了"FileInfo"类,以供我自己使用。在调用此函数之前,请创建您的根节点并将其分配给您的TreeView。
private void MakeTreeViewFromPaths(TreeNode rootNode, List<ExtendedFileInfo> files, string rootNodeName = "C:\\", char separator = '\\')
    {
        foreach (var file in files)
        {
            TreeNode currentNode = rootNode;
            string[] relPath = file.RelativePath.Split(separator);
            string currentPath = rootNodeName;  

            //**Split operation always generates an empty entry so we use "-1" to avoid iterating through the last empty array item
            for (int i = 0; i < relPath.Length - 1; i++)
            {
                currentPath += "\\" + relPath[i];
                TreeNode[] tmp = currentNode.Nodes.Find(currentPath, false);
                if (tmp.Length > 0)
                {
                    currentNode = tmp[0];
                    continue;
                }
                //Create Folder Nodes
                currentNode = currentNode.Nodes.Add(currentPath, relPath[i]);
            }

            //Create File Nodes
            TreeNode newNode = new TreeNode(file.FileInfo.Name);
            newNode.Tag = file;
            currentNode.Nodes.Add(newNode);
        }
    }

0
private void Form2_Load(object sender, EventArgs e)
{
    treeView1.CheckBoxes = true;

    foreach (TreeNode node in treeView1.Nodes)
    {
        node.Checked = true;
    }

    string[] drives = Environment.GetLogicalDrives();

    foreach (string drive in drives)
    {
        // treeView1.Nodes[0].Nodes[1].Checked = true;
        DriveInfo di = new DriveInfo(drive);
        int driveImage;

        switch (di.DriveType)   
        {
            case DriveType.CDRom:
                driveImage = 3;
                break;
            case DriveType.Network:
                driveImage = 6;
                break;
            case DriveType.NoRootDirectory:
                driveImage = 8;
                break;
            case DriveType.Unknown:
                driveImage = 8;
                break;
            default:
                driveImage = 2;
                break;
        }

        TreeNode node = new TreeNode(drive.Substring(0, 1), driveImage, driveImage);
        node.Tag = drive;

        if (di.IsReady == true)
             node.Nodes.Add("...");

        treeView1.Nodes.Add(node);          
    }

    foreach (TreeNode node in treeView1.Nodes)
    {
        node.Checked = true;
    }
}

private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
    {
        if (e.Node.Nodes.Count > 0)
        {
            if (e.Node.Nodes[0].Text == "..." && e.Node.Nodes[0].Tag == null)
            {
                e.Node.Nodes.Clear();

                string[] dirs = Directory.GetDirectories(e.Node.Tag.ToString());

                foreach (string dir in dirs)
                {
                    DirectoryInfo di = new DirectoryInfo(dir);
                    TreeNode node = new TreeNode(di.Name, 0, 1);
                    node.Checked = true;

                    try
                    {
                        node.Tag = dir;
                        if (di.GetDirectories().Count() > 0)
                            node.Nodes.Add(null, "...", 0, 0).Checked = node.Checked;
                    }
                    catch (UnauthorizedAccessException)
                    {
                        node.ImageIndex = 12;
                        node.SelectedImageIndex = 12;
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message, "DirectoryLister", MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                    }
                    finally
                    {
                        node.Checked = e.Node.Checked;
                        e.Node.Nodes.Add(node);
                    }
                }
            }
        }
    }              
}

private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
    button1.Enabled = false;
    TreeNode node = e.Node;
    bool is_checked = node.Checked;
    foreach (TreeNode childNode in e.Node.Nodes)
    {
        childNode.Checked = e.Node.Checked;
    }
    treeView1.SelectedNode = node;
 }

0

这里是我曾经用来创建一个 ASP.NET 树状视图的非常古老的代码(假设 TreeView 的 ID 为 TreeViewFolders):

protected void Page_Load(object sender, EventArgs e)
{
    GenerateTreeView(@"C:\WINDOWS\");
}

private void GenerateTreeView(string rootPath)
{
    GetFolders(System.IO.Path.GetFullPath(rootPath), TreeViewFolders.Nodes);
    TreeViewFolders.ExpandDepth = 1;
}

// recursive method to load all folders and files into tree
private void GetFolders(string path, TreeNodeCollection nodes)
{
    // add nodes for all directories (folders)
    string[] dirs = Directory.GetDirectories(path);
    foreach (string p in dirs)
    {
        string dp = p.Substring(path.Length);
        nodes.Add(Node("", p.Substring(path.Length), "folder"));
    }

    // add nodes for all files in this directory (folder)
    string[] files = Directory.GetFiles(path, "*.*");
    foreach (string p in files)
    {
        nodes.Add(Node(p, p.Substring(path.Length), "file"));
    }

    // add all subdirectories for each directory (recursive)
    for (int i = 0; i < nodes.Count; i++)
    {
        if (nodes[i].Value == "folder")
            GetFolders(dirs[i] + "\\", nodes[i].ChildNodes);
    }
}

// create a TreeNode from the specified path, text and type
private TreeNode Node(string path, string text, string type)
{
    TreeNode n = new TreeNode();
    n.Value = type;
    n.Text = text;
    return n;
}

0

我已经成功使用仅有的for循环从路径列表中创建了一棵树。目前看来,这是这个问题最简单的答案。

使用以下代码块创建一棵树。 list 是您的文件或文件夹列表,treeView1 是您的TreeView。

//Creates a tree from given path list
foreach (string path in list)
{
    TreeNodeCollection nodes = treeView1.Nodes;

    foreach (string path_part in path.Split('\\'))
    {
        //Here it adds a new node (file or folder)
        if (!nodes.ContainsKey(path_part))
            nodes.Add(path_part, path_part);
        //Go one node deeper
        nodes = nodes[path_part].Nodes;
    }
}

注意 - 如果您使用以路径分隔符(例如/home/user)开头的路径,可能会导致此代码块出现错误。

如果您想删除路径中的常见部分(或删除单个父节点),请在前面的代码块之后使用以下代码块:

//This removes "single" TreeNodes (common paths)
while (treeView1.Nodes.Count == 1)
{
    //This "unpacks" child TreeNodes from the only parent TreeNode
    for (int i = 0; i < treeView1.Nodes[0].Nodes.Count; i++)
        treeView1.Nodes.Add(treeView1.Nodes[0].Nodes[i]);
    //This removes parent TreeNode
    treeView1.Nodes.RemoveAt(0);
}

如果您的所有路径都相同,则此操作将生成一个空树。

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