需要帮助在我的TreeView(C#,WPF)中实现多线程

时间:2010-09-22 22:06:47

标签: c# wpf multithreading treeview directory

好的,我有一个TreeView作为Windows的目录树。我有它加载所有目录并正常运行,但它正在暂停我的GUI,同时它加载了很多孩子的目录。我正在尝试实现多线程,但我是新手,并且没有运气。

这就是我对TreeView的看法:

private readonly object _dummyNode = null;

public MainWindow()
{
    InitializeComponent();

    foreach (string drive in Directory.GetLogicalDrives())
    {
        DriveInfo Drive_Info = new DriveInfo(drive);                

        if (Drive_Info.IsReady == true)
        {
            TreeViewItem item = new TreeViewItem();
            item.Header = drive;
            item.Tag = drive;
            item.Items.Add(_dummyNode);
            item.Expanded += folder_Expanded;

            TreeViewItemProps.SetIsRootLevel(item, true);

            Dir_Tree.Items.Add(item);
        }
    }
}

private void folder_Expanded(object sender, RoutedEventArgs e)
{
    TreeViewItem item = (TreeViewItem)sender;

    if (item.Items.Count == 1 && item.Items[0] == _dummyNode)
    {
        item.Items.Clear();

        try
        {
            foreach (string dir in Directory.GetDirectories(item.Tag as string))
            {
                DirectoryInfo tempDirInfo = new DirectoryInfo(dir);

                bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);

                if (!isSystem)
                {
                    TreeViewItem subitem = new TreeViewItem();
                    subitem.Header = tempDirInfo.Name;
                    subitem.Tag = dir;
                    subitem.Items.Add(_dummyNode);
                    subitem.Expanded += folder_Expanded;
                    subitem.ToolTip = dir;
                    item.Items.Add(subitem);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

每当我展开具有大量子目录的目录时,程序似乎都会被冻结几秒钟。我想在处理时显示加载消息或动画,但我不知道如何从多线程开始。我知道我必须使用TreeView的Dispatcher.BeginInvoke方法,但除此之外,我有点迷失。

任何帮助都将非常感谢!!!

3 个答案:

答案 0 :(得分:2)

启动异步过程的最简单方法之一是使用带有BeginInvoke的匿名委托。作为示例,您可以将构造函数中的代码移动到单独的方法(比如RenderTreeView),然后异步调用它以开始新线程,如下所示:

Action action = RenderTreeView;
action.BeginInvoke(null, null);

这样做的诀窍是,每当您与异步进程中的任何UI元素进行交互时,您需要重新加入主UI线程,否则您将获得有关跨线程访问的异常。这也是相对直接的。

在Windows窗体中,它是:

if (InvokeRequired)
    Invoke(new MethodInvoker({item.Items.Add(subitem)}));
else
    item.Items.Add(subitem);

在WPF中,它是:

if (!Dispatcher.CheckAccess())
    Dispatcher.Invoke(new Action(() => item.Items.Add(subitem)));
else
    item.Items.Add(subitem);

你真的需要分解代码,使其在方法方面更加灵活。目前,所有内容都捆绑在一个方法中,这使得很难处理和重新考虑异步过程。

更新在这里你去:)

public partial class MainWindow : Window
{
    private readonly object dummyNode = null;

    public MainWindow()
    {
        InitializeComponent();

        Action<ItemCollection> action = RenderTreeView;
        action.BeginInvoke(treeView1.Items, null, null);
    }

    private void RenderTreeView(ItemCollection root)
    {
        foreach (string drive in Directory.GetLogicalDrives())
        {
            var driveInfo = new DriveInfo(drive);

            if (driveInfo.IsReady)
            {
                CreateAndAppendTreeViewItem(root, drive, drive, drive);
            }
        }
    }

    private void FolderExpanded(object sender, RoutedEventArgs e)
    {
        var item = (TreeViewItem) sender;

        if (item.Items.Count == 1 && item.Items[0] == dummyNode)
        {
            item.Items.Clear();
            var directory = item.Tag as string;
            if (string.IsNullOrEmpty(directory))
            {
                return;
            }
            Action<TreeViewItem, string> action = ExpandTreeViewNode;
            action.BeginInvoke(item, directory, null, null);
        }
    }

    private void ExpandTreeViewNode(TreeViewItem item, string directory)
    {
        foreach (string dir in Directory.GetDirectories(directory))
        {
            var tempDirInfo = new DirectoryInfo(dir);

            bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System);

            if (!isSystem)
            {
                CreateAndAppendTreeViewItem(item.Items, tempDirInfo.Name, dir, dir);
            }
        }
    }

    private void AddChildNodeItem(ItemCollection collection, TreeViewItem subItem)
    {
        if (Dispatcher.CheckAccess())
        {
            collection.Add(subItem);
        }
        else
        {
            Dispatcher.Invoke(new Action(() => AddChildNodeItem(collection, subItem)));
        }
    }

    private void CreateAndAppendTreeViewItem(ItemCollection items, string header, string tag, string toolTip)
    {
        if (Dispatcher.CheckAccess())
        {
            var subitem = CreateTreeViewItem(header, tag, toolTip);
            AddChildNodeItem(items, subitem);
        }
        else
        {
            Dispatcher.Invoke(new Action(() => CreateAndAppendTreeViewItem(items, header, tag, toolTip)));
        }
    }

    private TreeViewItem CreateTreeViewItem(string header, string tag, string toolTip)
    {
        var treeViewItem = new TreeViewItem {Header = header, Tag = tag, ToolTip = toolTip};

        treeViewItem.Items.Add(dummyNode);
        treeViewItem.Expanded += FolderExpanded;

        return treeViewItem;
    }
}

答案 1 :(得分:0)

多线程在这里可能没什么用,因为TreeView必须在它的Dispatcher线程上更新。

加载大量条目时,TreeView会暂停。解决此问题的一种方法是将内容存储到镜像TreeView结构的对象中,然后以编程方式仅加载TreeView的第一级。

当用户单击某个节点时,请加载下一级子节点并展开该节点。折叠该节点时,删除其子节点以节省TreeView内存。这对我来说效果很好。对于非常大的结构,我使用了本地Sqlite数据库(通过System.Data.Sqlite)作为我的后备存储,即使这样,TreeView也会快速加载并且响应迅速。

答案 2 :(得分:0)

您还可以查看使用BackgroundWorker;这是在单独的线程上执行操作的最简单方法(对我来说:))。

BackgroundWorker组件概述:http://msdn.microsoft.com/en-us/library/8xs8549b.aspx

BackgroundWorker类:​​http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

您可以使用命令模式,如此处所述 -

Asynchronous WPF Commands