Search code examples
c#winformsrecursiontreeviewbackgroundworker

Using BeginInvoke in a recursive function adding nodes to a TreeView in a BackgroundWorker


My code has a function that scans a root directory and showing txt files (for example) in a TreeView:

private TreeNode DirectoryToTreeView(TreeNode parentNode, string path,
                                     string extension = ".txt")
{
    var result = new TreeNode(parentNode == null ? path : Path.GetFileName(path));
    foreach (var dir in Directory.GetDirectories(path))
    {
        TreeNode node = DirectoryToTreeView(result, dir);
        if (node.Nodes.Count > 0)
        {
            result.Nodes.Add(node);
        }
    }
    foreach (var file in Directory.GetFiles(path))
    {
        if (Path.GetExtension(file).ToLower() == extension.ToLower())
        {
            result.Nodes.Add(Path.GetFileName(file));
        }
    }
    return result;
}

This function should by called from the button like: treeView1.Nodes.Add(DirectoryToTreeView(null, @"C:\Users\Tomer\Desktop\a")); Its obviously freezing the UI. I am new to this and I have searched the web and nothing seemed relevant to my problem because no one used recursive function and I cant simply call BeginInvoke on the entire function because it will have no effect. What path should I take? Maybe change the function to work with a while loop and then calling BeginInvoke inside the if statements? Creating a TreeNode object in memory to populate (which may be too large)?


Solution

  • You could convert the DirectoryToTreeNode method to an asynchronous method, and offload any blocking I/O operation to the ThreadPool, by using the Task.Run method:

    private async Task<TreeNode> DirectoryToTreeNodeAsync(string path,
        TreeNode parentNode = null)
    {
        var node = new TreeNode(parentNode == null ? path : Path.GetFileName(path));
        string[] subdirectories = await Task.Run(() => Directory.GetDirectories(path));
        foreach (string dirPath in subdirectories)
        {
            TreeNode childNode = await DirectoryToTreeNodeAsync(dirPath, node);
            node.Nodes.Add(childNode);
        }
        string[] files = await Task.Run(() => Directory.GetFiles(path));
        foreach (string filePath in files)
        {
            node.Nodes.Add(Path.GetFileName(filePath));
        }
        return node;
    }
    

    Notice that no UI control is touched while running on the ThreadPool (inside the Task.Run delegate). All UI controls should be manipulated exclusively by the UI thread.

    Usage example:

    private async void Button1_Click(object sender, EventArgs e)
    {
        Button1.Enabled = false;
        Cursor = Cursors.WaitCursor;
        try
        {
            TreeView1.Nodes.Clear();
            TreeView1.Nodes.Add(
                await DirectoryToTreeNodeAsync(@"C:\Users\Tomer\Desktop\a"));
        }
        finally
        {
            Cursor = Cursors.Default;
            Button1.Enabled = true;
        }
    }