[C#] [WPF]如何在不冻结UI的情况下制作异步TreeView?

我想制作一个显示服务器和文件夹的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();

            });
        });
    }
}

我想让UI在冻结treeView Item时不冻结(2种情况:root扩展,文件夹扩展)

暂且
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-当节点扩展时,我有“正在加载…”,过一会儿,UI会更新.在此期间,UI被冻结:用户无法移动窗口.

请问你能帮帮我吗 ?

(PS:如果您还有其他意见,我很乐意在这里提出;))

解决方法:

谢谢加里,但我发现了另一个解决方案,几乎可以解决所有问题:)

首先,我添加一个空类CustomTreeViewITem

class CustomTreeViewItem
{ }

此类在XAML中使用.

我更改了文件夹类中的一些内容:
 -将CompositeCollection替换为ObservableCollection
 -使Folder继承自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);
                }

            });
        });
    }

多亏了HierarchicalDataTemplate,它使我能够自定义每个TreeViewClass(自定义,文件夹,服务器).

上一篇:嵌套的XAML树视图结构与混合对象类型


下一篇:javascript-ExtJS TreeGrid中的复选框列