c# - [C#][WPF]如何在不卡住UI的情况下制作异步TreeView?

标签 c# wpf asynchronous 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 项目时不卡住(2 种情况:根目录扩展,文件夹扩展)

暂时 1- 当我扩展根节点时,我没有看到“正在加载...” 我试过这样的事情,但有一个异常(exception):线程必须处于 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 - 使文件夹继承自 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(自定义、文件夹、服务器),感谢 HierarchicalDataTemplate。

关于c# - [C#][WPF]如何在不卡住UI的情况下制作异步TreeView?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1221180/

相关文章:

java的synchronized关键字的C#版本?

c# - 在 ClaimsPrincipal 中添加多个身份

c# - 中继器未获取客户端更新的项目

c# - .Net6 WPF 应用程序的 PublishReadyToRun 问题

c# - 带有 BeginReceive\EndReceive 的异步套接字意外数据包

c# - 如何转义 Razor 页面中属性内的引号

c# - 调用 Imaging.CreateBitmapSourceFromHIcon 后可以安全地处理图标吗?

绑定(bind)到 DataGridRow.IsSelected 属性时出现 Wpf DataGrid 虚拟化问题

javascript - 如何从异步调用返回响应?

javascript - Node.js 处理异步