【问题标题】:populate treeview from a list of path从路径列表中填充树视图
【发布时间】:2010-11-12 11:04:51
【问题描述】:

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

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”。我已经为此工作了将近两天,并且我的代码中有很多错误。希望我能从这里得到帮助。

谢谢。

埃里克

【问题讨论】:

  • 除非您发布代码,否则无法确定它有什么问题。与其花几天时间,为什么不使用第 3 方控件,例如 FolderView

标签: c# .net vb.net treeview recursive-datastructures


【解决方案1】:
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];
            }
        }
    }

【讨论】:

  • 我测试了这种方法,但它在大型树形结构中表现不佳。怀疑这是由于一次将每个节点添加到树视图中。我从我们的工作项管理系统中获得了近 20k 条路径,即使使用 BeginUpdate,这种方法最终也会引发 ContextSwitchDeadlock 异常。我测试了 ykm29 发布的 LINQ 方法,它在一秒钟内完成了所有 20k 个节点的处理。
  • ContextSwitchDeadlock 并不一定意味着你的代码有问题,只是有可能。如果您转到菜单中的 Debug > Exceptions 并展开 Managed Debugging Assistants,您会发现 ContextSwitchDeadlock 已启用。如果你禁用它,当项目需要很长时间来处理时,VS 将不再警告你。在某些情况下,您可能有效地进行了长时间运行的操作。如果您正在调试并且在处理过程中停止在线,这也很有帮助 - 在您有机会深入研究问题之前,您不希望它抱怨。
【解决方案2】:

对于 linq'y 版本:

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。
【解决方案3】:

ehosca answer correcr,但是有一点问题, 当我将路径更改为这样时

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

它将像这样填充树视图。

但是添加一些额外的代码我们可以避免这种情况。所以我更改了 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

            }
        }

现在它可以像这样正常工作

【讨论】:

  • 很久以前我就知道这很好,但是 Treeviews 的 WinForms 实现是如此糟糕,以至于这是唯一可以帮助我做我需要做的事情的代码。我需要填充复选框,所以我只是将另一个我想要检查的路径列表扔给它,然后 bam!你的代码就像一个魅力。谢谢!
【解决方案4】:

我拿走了你的代码,它工作得很好, 但我只做了一点修改以提高加载速度 当它用于大量文件时 好像是find操作,字符串操作一般都很慢

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 可能指向磁盘上的同一个文件夹 但拼写不同,例如: 窗户;窗口, 窗口

【讨论】:

  • TreeNodeCollection 的字符串索引器在内部调用 IndexOfKey,它对所有子节点进行线性遍历,直到找到键后跟 IsValidIndex。另一方面,Find 能够使用 searchAllChildren 参数控制搜索的深度。根据您的数据结构,您可以选择最佳方式。他们最终都调用了 WindowsFormsUtils.SafeCompareStrings。
【解决方案5】:

这是我曾经用来从代码创建 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;
}

【讨论】:

    【解决方案6】:
    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;
     }
    

    【讨论】:

      【解决方案7】:

      我已经设法仅使用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);
      }
      

      如果所有路径都相同,这将生成一棵空树。

      【讨论】:

        猜你喜欢
        • 2011-09-18
        • 2016-01-21
        • 1970-01-01
        • 1970-01-01
        • 2013-04-27
        • 1970-01-01
        • 2021-03-08
        • 2013-08-09
        相关资源
        最近更新 更多