Reputation: 22
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
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
Reputation: 186
You can achieve this using a HierarchicalDataTemplate.
List<Node>
. This will contain a list of all the child nodes for this node. List<Node>
that will return a list of the top level nodes.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