[C #] [WPF] How to make an asynchronous TreeView without freezing the user interface?

I would like to create a TreeView that displays servers and folders. According to my needs, I made 2 classes:

- folder

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 () imitates that the method may take some time.

- Server: folder

 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;
    }
}

Here is my TreeView code:

XAML part:

<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>

Code for:

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();

            });
        });
    }
}

I would like the user interface to not hang when the treeView element is consumed (2 cases: root is consumed, folder is consumed)

Currently 1- I do not see "Loading ..." when I spend the root node I tried something like this, but there is an exception: the thread must be in STA mode:

// 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- a node , "...", . : .

?

(PS: , ;))

+3
3

, , , :)

Fisrt, 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);
                }

            });
        });
    }

TreeViewClass (Custom, Folder, Server), HierarchicalDataTemplate.

+3

Folder:

        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;

                });
            });
+1

, : XAML . TreeViewItem.

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

Beind "root" . , "".

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 AND HierarchicalDataTemplates ObservableCollections. ! !

0

Source: https://habr.com/ru/post/1714197/


All Articles