Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Speed up the loading a list of strings to a Treeview

Tags:

Stackflow and C# newbie here!

I have some code below which loads a list of strings into a treeview control and it works well. The only problem is the speed. When the lists are large it takes time to load, which isn't a problem except it locks up the UI for a while.

So an example would be a list of strings like this (but much larger):

c:\drivers\test1.txt
c:\drivers\test2.txt
c:\drivers\folder\test1.txt
c:\brother\tester\text1.zip
c:\brother\another\text2.zip
c:\data\company1\accounts.rar
c:\data\company2\accounts.rar

The treeview splits the strings using the backslash marker and puts them neatly in a explorer view - this is good!.

tvRestore is the Treeview control in example.

foreach (string path in lstKeys)
        {

            lastNode = null;
            subPathAgg = string.Empty;
            foreach (string subPath in path.Split(new string[] { "\\" }, StringSplitOptions.None))
            {
                foreach (string item in subPath.Split(new string[] { "\\" }, StringSplitOptions.None))
                {
                    if (item == "" || item == null)
                    {
                        continue;
                    }
                    subPathAgg += item + "\\";

                    TreeNode[] n = tvRestore.Nodes.Find(subPathAgg, true);

                    if (n.Length > 0)
                    {
                        lastNode = n[0];

                        continue;
                    }
                    else
                    {
                        // lastNode = null;
                    }

                    TreeNode[] nodes = tvRestore.Nodes.Find(subPathAgg, true);
                    if (nodes.Length == 0)
                        if (lastNode == null)
                            lastNode = tvRestore.Nodes.Add(subPathAgg, item);
                        else
                            lastNode = lastNode.Nodes.Add(subPathAgg, item);
                    else
                        lastNode = nodes[0];
                }
            }
        }

The only problem is the speed. I tried to use Threads but the code exceptions because the control is on a different Thread. I believe I have to Invoke the Nodes.Add but I can't figure out how to do this.

Ideally the code will start to populate the treeview when the application starts although I don't want the app to lockup for 30-40 seconds or longer for larger lists.

What is the best way to speed this process up?

like image 398
acurachris Avatar asked Oct 12 '16 22:10

acurachris


1 Answers

There are a couple of things you can do, including running this in the background - I like to use Task.Run() for this.

private void Form1_Load(object sender, EventArgs e)
{
    // Show the user something
    treeView1.Nodes.Add("Loading...");
    // Run the tree load in the background
    Task.Run(() => LoadTree());
}

Then your Task can build a TreeNode containing all your new nodes and Invoke the TreeView to add the new nodes as a Range, as well as using BeginUpdate...EndUpdate to prevent visual updates until all the nodes are loaded.

private void LoadTree()
{
    // Get a list of everything under the users' temp folder as an example
    string[] fileList;
    DirectoryInfo df = new DirectoryInfo(Path.GetTempPath());
    fileList = df.GetFiles("*.*",SearchOption.AllDirectories).Select<FileInfo, string>((f) => f.FullName).ToArray();

    // Parse the file list into a TreeNode collection
    TreeNode node = GetNodes(new TreeNode(), fileList);

    // Copy the new nodes to an array
    int nodeCount = node.Nodes.Count;
    TreeNode[] nodes = new TreeNode[nodeCount];
    node.Nodes.CopyTo(nodes, 0);

    // Invoke the treeview to add the nodes
    treeView1.Invoke((Action)delegate ()
    {
        treeView1.BeginUpdate(); // No visual updates until we say 
        treeView1.Nodes.Clear(); // Remove existing nodes
        treeView1.Nodes.AddRange(nodes); // Add the new nodes
        treeView1.EndUpdate(); // Allow the treeview to update visually
    });
}

This is how I would build the list of TreeNodes.

private TreeNode GetNodes(TreeNode parent, string[] fileList)
{
    // build a TreeNode collection from the file list
    foreach (string strPath in fileList)
    {
        // Every time we parse a new file path, we start at the top level again
        TreeNode thisParent = parent;

        // split the file path into pieces at every backslash
        foreach (string pathPart in strPath.Split('\\'))
        {
            // check if we already have a node for this
            TreeNode[] tn = thisParent.Nodes.Find(pathPart, false);

            if (tn.Length == 0)
            {
                // no node found, so add one
                thisParent = thisParent.Nodes.Add(pathPart,pathPart);
            }
            else
            {
                // we already have this node, so use it as the parent of the next part of the path
                thisParent = tn[0];
            }
        }

    }
    return parent;
}

On my machine, it takes about 1.5 seconds to load 56,000 nodes into the treeview this way, and the UI is not blocking.

like image 200
Mister Magoo Avatar answered Sep 22 '22 16:09

Mister Magoo