Нужна помощь в реализации многопоточности в моем TreeView (C #, WPF) - PullRequest
4 голосов
/ 23 сентября 2010

Хорошо, у меня есть TreeView, который служит деревом каталогов для Windows.Он загружает все каталоги и работает правильно, но он приостанавливает мой графический интерфейс, загружая каталоги со многими дочерними элементами.Я пытаюсь реализовать многопоточность, но я новичок в этом, и мне не везет.

Вот что у меня есть для моего 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);
        }
    }
}

Всякий раз, когда я расширяю каталог, которыйбольшое количество подкаталогов, программа, кажется, зависла на несколько секунд.Я хотел бы отобразить загрузочное сообщение или анимацию во время ее обработки, но я не уверен, как начать с многопоточности.Я знаю, что должен использовать метод Dispatcher.BeginInvoke TreeView, но в остальном я немного потерян.

Любая помощь будет принята с благодарностью !!!

Ответы [ 3 ]

2 голосов
/ 23 сентября 2010

Одним из самых простых способов запуска асинхронного процесса является использование анонимного делегата с BeginInvoke.Например, вы можете переместить ваш код в конструкторе в отдельный метод (скажем, RenderTreeView), а затем вызвать его асинхронно, чтобы начать новый поток следующим образом:

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

Фокус в том, что в любое время вывзаимодействовать с любыми элементами пользовательского интерфейса из асинхронного процесса, необходимого для повторного присоединения к основному потоку пользовательского интерфейса, в противном случае вы получите исключение о доступе между потоками.Это относительно просто.

В Windows Forms это:

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;
    }
}
0 голосов
/ 23 сентября 2010

Вы также можете посмотреть на использование BackgroundWorker;это самый простой способ выполнить операцию в отдельном потоке (для меня :)).

Обзор компонента BackgroundWorker: http://msdn.microsoft.com/en-us/library/8xs8549b.aspx

Класс BackgroundWorker: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Вы можете использовать его с шаблоном Command, как описано здесь -

Асинхронные команды WPF

0 голосов
/ 23 сентября 2010

Многопоточность здесь может не сильно помочь, потому что TreeView необходимо обновить в потоке Dispatcher.

TreeViews остановится при загрузке большого количества записей. Один из способов обойти это - сохранить содержимое в объекте, который отражает структуру TreeView, а затем программно загрузить только первый уровень TreeView.

Когда пользователь щелкает узел, загружает следующий уровень дочерних узлов и расширяет узел. Когда этот узел свернут, удалите его дочерние узлы, чтобы сохранить память TreeView. Это хорошо сработало для меня. Для очень больших структур я использовал локальную базу данных Sqlite (через System.Data.Sqlite ) в качестве резервного хранилища, и даже тогда TreeView загружался быстро и реагировал.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...