[C #] [WPF] Как сделать асинхронный TreeView без зависания интерфейса? - PullRequest
3 голосов
/ 03 августа 2009

Я хотел бы сделать TreeView, который отображает серверы и папки. В соответствии с моими потребностями я сделал 2 занятия:

- папка

class Folder
{
    // Hidden attributes
    public String ElementID { get; set; }

    // Attributes displayed in the treeview
    public String ElementName { get; set; }

    // This collection is binded with the GUI defined in XAML
    public CompositeCollection Children { get; set; }

    public BitmapImage Image {get; set; }

    // Constructor
    public Folder()
    {
       // Fill the treeview with a temporary child as text
       Children = new CompositeCollection();
       Children.Add(new TextBlock()
       {
          Text = "Loading...",
          FontStyle = FontStyles.Italic
       });
    }

    // Fill the Children collection
    public void LoadChildren()
    {
        // Clear the Children list
        Children.Clear();

        // Populate the treeview thanks to the bind
        foreach (Folder folder in this.GetChildren())
        {
            Children.Add(folder);
        }
    }

    // Get the Folder Children as Folder
    protected List<Folder> GetChildren()
    {
        System.Threading.Thread.Sleep(1000);

        List<Folder> resu = new List<Folder>();

        Folder f1 = new Folder();
        f1.ElementID = "1";
        f1.ElementName = "folder 1";

        Folder f2 = new Folder();
        f2.ElementID = "2";
        f2.ElementName = "folder 2";

        Folder f3 = new Folder();
        f3.ElementID = "3";
        f3.ElementName = "folder 3";

        Folder f4 = new Folder();
        f4.ElementID = "4";
        f4.ElementName = "folder 4";

        resu.Add(f1);
        resu.Add(f2);
        resu.Add(f3);
        resu.Add(f4);

        return resu;
    }

Thread.Sleep () имитирует, что метод может занять некоторое время.

- Сервер: папка

 class Server : Folder
{
    // Get the Servers list
    public static List<Server> GetServers()
    {
        System.Threading.Thread.Sleep(1500);

        // Create a list of Servers
        List<Server> servers = new List<Server>();

        Server s1 = new Server();
        s1.ElementID = "1";
        s1.ElementName = "Server 1";

        Server s2 = new Server();
        s2.ElementID = "2";
        s2.ElementName = "Server 2";

        Server s3 = new Server();
        s3.ElementID = "3";
        s3.ElementName = "Server 3";

        Server s4 = new Server();
        s4.ElementID = "4";
        s4.ElementName = "Server 4";

        servers.Add(s1);
        servers.Add(s2);
        servers.Add(s3);
        servers.Add(s4);

        return servers;
    }
}

Вот мой код TreeView:

XAML часть:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ar="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Browser" Height="327" Width="250">
<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type ar:Folder}" ItemsSource="{Binding Path=Children}" >
        <StackPanel Orientation="Horizontal">
            <Image Source="Images\TreeView\folder.png" Height="15" Width="15" />
            <TextBlock Text="{Binding Path=ElementName}" />
        </StackPanel>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type ar:Server}" ItemsSource="{Binding Path=Children}">
        <StackPanel Orientation="Horizontal">
            <Image Source="Images\TreeView\server.png" Height="15" Width="15" />
            <TextBlock Text="{Binding Path=ElementName}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>
<Grid>
        <TreeView HorizontalAlignment="Stretch" Width="230" Name="treeView">
        <TreeViewItem Header="Servers" x:Name="root" x:FieldModifier="private">
                <TreeViewItem TextBlock.FontStyle="Italic" 
                        Header="Loading..."/>
        </TreeViewItem>
    </TreeView>        
</Grid>

Код сзади:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        // Add an event in order to know when an TreeViewItem is Expanded
        AddHandler(TreeViewItem.ExpandedEvent, new RoutedEventHandler(treeItemExpanded), true);
    }

    // Event when a treeitem expands
    private void treeItemExpanded(object sender, RoutedEventArgs e)
    {          
        // Get the source
        var item = e.OriginalSource as TreeViewItem;

        // If the item source is a Simple TreeViewItem
        if (item == null)
           return;

        if (item.Name == "root")
        {
           List<Server> servers = new List<Server>();

           servers = Server.GetServers();

           root.Items.Clear();

           // Fill the treeview with the servers
           root.ItemsSource = servers;                 
        }        

        // Get data from item as Folder (also works for Server)
        var treeViewElement = item.DataContext as Folder;

        // If there is no data
        if (treeViewElement == null)
           return;

        // Load Children ( populate the treeview )
        ThreadPool.QueueUserWorkItem(delegate
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate
            {
                treeViewElement.LoadChildren();

            });
        });
    }
}

Я бы хотел, чтобы пользовательский интерфейс не зависал при расширении элемента treeView (2 случая: расширение корневого каталога, расширение папки)

Пока 1- Я не вижу «Загрузка ...», когда я трачу корневой узел Я пробовал что-то подобное, но есть исключение: поток должен быть в режиме STA:

// Load Children ( populate the treeview )
ThreadPool.QueueUserWorkItem(delegate
{
   List<Server> servers = Server.GetServers();

   Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate
   {
      root.Items.Clear();

      // Fill the treeview with the servers
      root.ItemsSource = servers;
   });
});

2- Когда узел расходуется, у меня появляется «Загрузка ...» и через некоторое время пользовательский интерфейс обновляется. В течение этого времени пользовательский интерфейс заблокирован: пользователь не может переместить окно.

Не могли бы вы помочь мне, пожалуйста?

(PS: Если у вас есть другие комментарии, я буду рад их здесь;))

Ответы [ 3 ]

3 голосов
/ 05 августа 2009

Спасибо, Гэри, но я нашел другое решение, которое решает почти все:)

Во-первых, я добавляю пустой класс CustomTreeViewITem

class CustomTreeViewItem
{ }

Этот класс используется в XAML.

Я изменил несколько вещей в классе папок: - заменить CompositeCollection на ObservableCollection сделать папку наследует от CustomTreeViewItem - изменить конструктор

class Folder : CustomTreeViewItem
{
    // Hidden attributes
    public String ElementID { get; set; }

    // Attributes displayed in the treeview
    public String ElementName { get; set; }

    public ObservableCollection<CustomTreeViewItem> Children { get; set; } // This collection is binded with the GUI defined in XAML

    // Constructor
    public Folder()
    {
        // Fill the treeview with a temporary child as text
        Children = new ObservableCollection<CustomTreeViewItem>();
        Children.Add(new CustomTreeViewItem());
    }

    // Get the Folder Children as Folder
    // Method overriden by the Server class
    public List<Folder> GetChildren()
    {
        System.Threading.Thread.Sleep(5000);

        List<Folder> resu = new List<Folder>();

        Folder f1 = new Folder();
        f1.ElementID = "1";
        f1.ElementName = "folder 1";

        Folder f2 = new Folder();
        f2.ElementID = "2";
        f2.ElementName = "folder 2";

        Folder f3 = new Folder();
        f3.ElementID = "3";
        f3.ElementName = "folder 3";

        Folder f4 = new Folder();
        f4.ElementID = "4";
        f4.ElementName = "folder 4";

        resu.Add(f1);
        resu.Add(f2);
        resu.Add(f3);
        resu.Add(f4);

        return resu;
    }

Я не изменил класс сервера

Теперь мой XAML выглядит так:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ar="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="Browser" Height="327" Width="250">
<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type ar:Folder}" ItemsSource="{Binding Path=Children}">
        <TextBlock Text="{Binding Path=ElementName}" />
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type ar:Server}" ItemsSource="{Binding Path=Children}">
        <TextBlock Text="{Binding Path=ElementName}" />
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type ar:CustomTreeViewItem}">
            <TextBlock Text="Loading..." />
    </HierarchicalDataTemplate>
</Window.Resources>
<Grid>
    <TreeView HorizontalAlignment="Stretch" Width="230" Name="treeView">
        <TreeViewItem Header="Servers" x:Name="root" x:FieldModifier="private">
            <ar:CustomTreeViewItem/>
        </TreeViewItem>
    </TreeView>
</Grid>

И мой код:

private void treeItemExpanded(object sender, RoutedEventArgs e)
    {
        // Get the source
        var item = e.OriginalSource as TreeViewItem;

        // If the item source is a Simple TreeViewItem
        if (item == null)
        // then Nothing
        { return; }

        if (item.Name == "root")
        {
            // Load Children ( populate the treeview )
            ThreadPool.QueueUserWorkItem(delegate
            {
                List<Server> servers = Server.GetServers();

                Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate
                {
                    root.Items.Clear();

                    // Fill the treeview with the servers
                    root.ItemsSource = servers;
                });
            });
        }

        // Get data from item as Folder (also works for Server)
        Folder treeViewElement = item.DataContext as Folder;

        // If there is no data
        if (treeViewElement == null)
        {
            return;
        }
        // Load Children ( populate the treeview )
        ThreadPool.QueueUserWorkItem(delegate
        {

            // Clear the Children list
            var children = treeViewElement.GetChildren();

            // Populate the treeview thanks to the bind

            Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate
            {
                treeViewElement.Children.Clear();

                foreach (Folder folder in children)
                {
                    treeViewElement.Children.Add(folder);
                }

            });
        });
    }

Это позволяет мне настраивать каждый TreeViewClass (Custom, Folder, Server) благодаря HierarchicalDataTemplate.

1 голос
/ 05 августа 2009

Удалите это из класса Папки:

        Children.Add(new TextBlock()
        {
            Text = "Loading...",
            FontStyle = FontStyles.Italic
        });

Позволяет вам делать:

            ThreadPool.QueueUserWorkItem(delegate
            {
                List<Server> servers = new List<Server>();

                servers = Server.GetServers();

                this.Dispatcher.Invoke((Action)delegate
                {
                    item.Items.Clear();

                    // Fill the treeview with the servers
                    item.ItemsSource = servers;

                });
            });
0 голосов
/ 02 октября 2009

Я думаю, что нужно внести некоторые коррективы: XAML должен указывать на метод события экспандера. Я положил его в элемент TreeViewItem.

<TreeViewItem Header="Servers" x:Name="root" x:FieldModifier="private" Expanded="treeItemExpanded">

И Кодовое обозначение относится к «корню» в двух местах. Я думаю, что это должен быть «товар».

root.Items.Clear();
// Fill the treeview with the servers
root.ItemsSource = servers;

становится

item.Items.Clear();
// Fill the treeview with the servers 
item.ItemsSource = servers;

Чувак, я думаю, что нашел свой золотой рудник TreeView здесь. Спасибо за направление !!!!!! Мне удалось сделать эту работу, прекрасно иллюстрируя как многопоточность в TreeView, так и HierarchicalDataTemplates и ObservableCollections. Очень хорошо! СЛАДКИЙ!

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