使用简单的WPF应用程序的多线程问题

时间:2010-08-18 21:09:08

标签: wpf multithreading wpf-controls

我是WPF的新手,我创建了一个简单的WPF应用程序,它将整个驱动器结构(文件夹,文件)列出到TreeView,因为这个过程需要一段时间我试图使用一个线程来运行GetFolderTree()方法并且防止UI变得没有响应,但是我遇到了一些问题,我创建了一个名为FolderBrowser的类,其中我有所有的驱动结构收集代码,在该类中我创建了一个新的TreeViewItem实例,它在最后保存驱动器结构用作填充TreeView的返回值,这是代码:

using System.IO;
using System.Windows.Controls;

namespace WpfApplication  
{
  public class FolderBrowser  
  {  
    private TreeViewItem folderTree;
    private string rootFolder;

    public FolderBrowser(string path)
    {
        rootFolder = path;
        folderTree = new TreeViewItem();
    }

    private void GetFolders(DirectoryInfo di, TreeViewItem tvi)
    {
        foreach (DirectoryInfo dir in di.GetDirectories())
        {
            TreeViewItem tviDir  = new TreeViewItem() { Header = dir.Name };         

            try
            {
                if (dir.GetDirectories().Length > 0)
                    GetFolders(dir, tviDir);

                tvi.Items.Add(tviDir);
                GetFiles(dir, tviDir);
            }
            //catch code here
        }

        if (rootFolder == di.FullName)
        {
            folderTree.Header = di.Name;
            GetFiles(di, folderTree);
        }
    }

    private void GetFiles(DirectoryInfo di, TreeViewItem tvi)
    {
        foreach (FileInfo file in di.GetFiles())
        {
            tvi.Items.Add(file.Name);
        }
    }

    public TreeViewItem GetFolderTree()
    {
        DirectoryInfo di = new DirectoryInfo(rootFolder);
        if (di.Exists)
        {                
            GetFolders(di, folderTree);                                
        }

        return folderTree;
    }
  }
}

如何在这个新线程中创建新的控件实例?

提前致谢

4 个答案:

答案 0 :(得分:2)

如果来自Merkyn Morgan-Graham的解决方案不起作用(请参阅我的评论,我不确定),我建议创建一个包含目录对象的独立对象结构。

使用BackgroundWorker执行此操作。如果它已经完成,请使用此结构直接构建TreeViewItem节点(因为如果你有几百个它们,这不是那么慢)或者将它用作ViewModel(更好)。

BackgroundWorker bgWorker = new BackgroundWorker();
bgWorker.DoWork += (s, e) => {
    // Create here your hierarchy
    // return it via e.Result                
};
bgWorker.RunWorkerCompleted += (s, e) => {
    // Create here your TreeViewItems with the hierarchy from  e.Result                
};
bgWorker.RunWorkerAsync();

答案 1 :(得分:1)

您无法在UI线程的任何线程中与UI交互,但您可以使用UI Dispatcher对象在UI线程内执行回调:

System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => { /* your UI code here */ }));

获取调度程序的一种更“干净”的方法是在创建调度程序时将其从UI对象传递给生成线程的线程/类。

修改

我推荐HCL的解决方案。但是,你在评论中询问如何在不重复这个令人讨厌的代码块的情况下使其工作:

在构造函数中,引用Dispatcher对象,并将其存储在您的类中。

然后制作一个这样的方法:

private void RunOnUIThread(Action action)
{
    this.dispatcher.Invoke(action);
}

并称之为:

RunOnUIThread(() => { /* UI code */ });

您可以这样包装大块代码:

RunOnUIThread(() =>
{
  Console.WriteLine("One statement");
  Console.WriteLine("Another statement");
});

如果你试图将过多的代码推回到UI中,那么它与你在UI线程中执行所有代码并没有什么不同,并且仍然会挂起UI。

但是,HCL建议填充自定义树结构,而不是让代码知道有关UI控件的任何信息,这样做要好得多:)

答案 2 :(得分:0)

我建议您查看分层模板,而不是手动构建树。您可以在后台线程中构建整个结构,然后将生成的数据结构绑定到树上。

答案 3 :(得分:0)

这是使用MVVM(好吧,至少是视图/视图模型部分)和后台工作线程的答案。这使用后台工作程序(递归地)填充视图模型,并使用分层数据模板将视图绑定到视图模型。

请注意,我们仍然遇到相同的线程问题,因为工作线程无法更改ObservableCollection。因此,我们使用RunWorkerCompleted事件处理程序(在UI线程中执行)来填充集合。

<强> MainWindow.xaml:

<Window
    x:Class="WpfApplication.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication">
    <StackPanel>
        <TextBlock Text="Contents:" />
        <TreeView ItemsSource="{Binding BaseDirectory.Contents}">
            <TreeView.Resources>
                <HierarchicalDataTemplate
                      DataType="{x:Type local:FileSystemEntry}"
                      ItemsSource="{Binding Contents}">
                    <TextBlock Text="{Binding Name}" />
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </StackPanel>
</Window>

<强> MainWindowViewModel.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;

namespace WpfApplication
{
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            this.BaseDirectory = new FileSystemEntry("C:\\");
            this.BaseDirectory.Populate();
        }

        public FileSystemEntry BaseDirectory { get; private set; }
    }

    public class FileSystemEntry
    {
        public FileSystemEntry(string path)
            : this(new DirectoryInfo(path))
        {
        }

        private FileSystemEntry(DirectoryInfo di)
            : this()
        {
            this.Name = di.Name;
            this.directoryInfo = di;
        }

        private FileSystemEntry(FileInfo fi)
            : this()
        {
            this.Name = fi.Name;
            this.directoryInfo = null;
        }

        private FileSystemEntry()
        {
            this.contents = new ObservableCollection<FileSystemEntry>();
            this.Contents = new ReadOnlyObservableCollection<FileSystemEntry>(this.contents);
        }

        public string Name { get; private set; }

        public ReadOnlyObservableCollection<FileSystemEntry> Contents { get; private set; }

        public void Populate()
        {
            var bw = new BackgroundWorker();

            bw.DoWork += (s, e) =>
            {
                var result = new List<FileSystemEntry>();

                if (directoryInfo != null && directoryInfo.Exists)
                {
                    try
                    {
                        foreach (FileInfo file in directoryInfo.GetFiles())
                            result.Add(new FileSystemEntry(file));

                        foreach (DirectoryInfo subDirectory in
                            directoryInfo.GetDirectories())
                        {
                            result.Add(new FileSystemEntry(subDirectory));
                        }
                    }
                    catch (UnauthorizedAccessException)
                    {
                        // Skip
                    }
                }

                System.Threading.Thread.Sleep(2000); // Todo: Just for demo purposes

                e.Result = result;
            };

            bw.RunWorkerCompleted += (s, e) =>
            {
                var newContents = (IEnumerable<FileSystemEntry>)e.Result;

                contents.Clear();
                foreach (FileSystemEntry item in newContents)
                    contents.Add(item);

                foreach (FileSystemEntry subItem in newContents)
                    subItem.Populate();
            };

            bw.RunWorkerAsync();
        }

        private ObservableCollection<FileSystemEntry> contents;
        private DirectoryInfo directoryInfo;
    }
}