views:

2235

answers:

3

Hi,

I would like to make 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() simulates that the method can take a while.

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

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 to make the UI not freeze while expending a treeView Item ( 2 cases : root expending, folder expending)

For the time being 1- I don't see the "Loading..." when I expend the root node I tried something like this, but there is a 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- When a node is expended, I have the "Loading..." and after a while, the UI is updated. During this time, the UI is frozen : the user is unable to move the window.

Could you help me please ?

(PS : If you have any other comments, I'd be glad to here them ;) )

A: 

Remove this from the Folder class :

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

Allowing you to do :

            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;

                });
            });
Gary W
+1  A: 

Thank you Gary, but I found another solution that solves almost everything :)

Fisrt, I add an empty Class CustomTreeViewITem

class CustomTreeViewItem
{ }

This class is used in XAML.

I changed a few things in the folder class : - replace CompositeCollection to an ObservableCollection - make Folder inherits from CustomTreeViewItem - change the constructor

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

I didn't change the Server Class

Now My XAML looks like this :

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

And my code behind :

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

            });
        });
    }

It enables me to custom every single TreeViewClass (Custom, Folder, Server), thanks to HierarchicalDataTemplate.

Wilhelm Peraud
A: 

I think it needs some corrections: The XAML needs to point to the expander event method. I put it in the TreeViewItem element.

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

And the Code Beind refers to 'root' in two places. I think it should be 'item'.

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

becomes

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

Man, I think I have found my TreeView gold mine here. Thanks for the direction!!!!!! I was able to make this work nicely illustrating both threading in the TreeView AND HierarchicalDataTemplates AND ObservableCollections. Very Nice! SWEET!

JerDog