DChi Shaggy
DChi Shaggy

Reputation: 22

WPF TreeView - How do I bind a Parent/Child class

I have a table that I would like to bind to a TreeView.

NODE    NAME        PARENT
----    -----       -------
1       Bill        NULL
2       Jane        NULL
3       John        1
4       Mike        2
5       Bob         4
6       Jody        1
7       Larry       5
8       Heather     2
9       Steve       8

Here is my class:

public class Node
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int? Parent { get; set; }

    public Node()
    {
    }

    public Node(int id, string name)
    {
        ID = id;
        Name = name;
        Parent = null;
    }

    public Node(int id, string name, int? parent)
    {
        ID = id;
        Name = name;
        Parent = parent;
    }

    public override string ToString()
    {
        return string.Format("[{0}] {1}", ID, Name);
    }

}

So the final result would look something like this:

---[1] Bill
  |-----[3] John
  |-----[4] Jody

---[2] Jane
  |-----[4] Mike
  |    |------[5] Bob
  |          |------[7] Larry
  |-----[8] Heather
       |------[9] Steve

Currently I am building this with code behind. First I find all of the items that do not have parents. Then add them to the TreeView from code:

    var TopNodes = Nodes.Where(n => n.Parent == null);

    foreach (Node n in TopNodes)
    {
        TreeViewItem newItem = new TreeViewItem();
        newItem.Tag = n.ID;
        newItem.Header = n.Name;

        newItem.Items.Add("..Loading..");
        tvTest.Items.Add(newItem);
    }

Then when the item is expanded I populate the parent, like this:

private void tvTest_Expanded(object sender, RoutedEventArgs e)
{
                item.Items.Clear();

        int parentID = (int)item.Tag;

        var children = Nodes.Where(x => x.Parent == parentID);

        foreach (Node n in children)
        {
            TreeViewItem newItem = new TreeViewItem();
            newItem.Tag = n.ID;
            newItem.Header = n.Name;

            newItem.Items.Add("..Loading..");
            item.Items.Add(newItem);
        }

}

I feel I would have more flexibility, if I could bind this data directly. Especially once I start working on implementing CRUD functions on the data.

Thank you for any advice that you can give me.

Upvotes: 0

Views: 4395

Answers (2)

Kumareshan
Kumareshan

Reputation: 1341

I hope the following example helps you

Xaml Front-end

           <TreeView Grid.Row="1" Background="Transparent" ItemsSource="{Binding Directories}" Margin="0,10,0,0" Name="FolderListTreeView"
                Height="Auto" HorizontalAlignment="Stretch" Width="300"  local:ControlBehaviour.ControlEvent="TreeView.SelectedItemChanged" >
                <TreeView.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type local:FileSystem}" ItemsSource="{Binding SubDirectories}">
                        <Label Content="{Binding Path= Name}" Name="NodeLabel" />
                    </HierarchicalDataTemplate>
                </TreeView.Resources>
            </TreeView>

The class which holds Sub-directories and its Children

public class FileSystem :NotifyChange, IEnumerable
{
    #region Private members
    private ObservableCollection<FileSystem> subDirectoriesField;
    #endregion

    #region Public properties
    /// <summary>
    /// Gets and sets all the Files in the current folder
    /// </summary>
    public ObservableCollection<FileSystem> SubDirectories
    {
        get
        {
            return subDirectoriesField;
        }
        set
        {
            if (subDirectoriesField != value)
            {
                subDirectoriesField = value;
                RaisePropertyChanged("SubDirectories");
            }
        }
    }
    /// <summary>
    /// Gets or sets name of the file system 
    /// </summary>
    public string Name
    {
        get;
        set;
    }
    /// <summary>
    /// Gets or sets full path of the file system
    /// </summary>
    public string FullPath
    {
        get;
        set;
    }
    /// <summary>
    /// object of parent, null if the current node is root
    /// </summary>
    public FileSystem Parent
    {
        get;
        set;
    }
    public FileSystem(string fullPath, FileSystem parent)
    {
        Name = fullPath != null ? fullPath.Split(new char[] { System.IO.Path.DirectorySeparatorChar },
            StringSplitOptions.RemoveEmptyEntries).Last()
        FullPath = fullPath;
        Parent = parent;
        FileType = type;
        AddSubDirectories(fullPath);
    }

    public IEnumerator GetEnumerator()
    {
        return SubDirectories.GetEnumerator();
    }

    private void AddSubDirectories(string fullPath)
    {
        string[] subDirectories = Directory.GetDirectories(fullPath);
        SubDirectories = new ObservableCollection<FileSystem>();
        foreach (string directory in subDirectories)
        {
            SubDirectories.Add(new FileSystem(directory, this));
        }
    }
}

Then the view model will look as below

public class ViewModel:NotifyChange
{
   private ObservableCollection<FileSystem> directories;
   public ObservableCollection<FileSystem> Directories
    {
        get
        {
            return directoriesField;
        }
        set
        {
            directoriesField = value;
            RaisePropertyChanged("Directories");
        }
    }
    public ViewModel()
    {
       //The below code has to be moved to thread for better user expericen since when UI is loaded it might not respond for some time since it is looping through all the drives and it;s directories
       Directories=new  ObservableCollection<FileSystem>();
       Directories.Add(new FileSystem("C:\\", null);
       Directories.Add(new FileSystem("D:\\", null);
       Directories.Add(new FileSystem("E:\\", null);
    }
}

Set the DataContext to ViewModel

Upvotes: 1

Richard Vella
Richard Vella

Reputation: 186

You can achieve this using a HierarchicalDataTemplate.

  1. Modify your Node class so that it contains a property called Children of type List<Node>. This will contain a list of all the child nodes for this node.
  2. Then in your viewmodel expose a publicproperty called TopNodes of type List<Node> that will return a list of the top level nodes.
  3. Finally in the xaml view, define the TreeView as follows:

    <TreeView Name="TreeViewNodes" ItemsSource="{Binding TopNodes}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Path=Name}" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    

You will still have to populate your node objects accordingly from the viewmodel but this way you avoid having to reference the TreeViewItem directly from the viewmodel as this is not the recommended way when following the MVVM pattern.

I hope this answers your question :)

Upvotes: 3

Related Questions