Producing a hierarchy of treeview in xaml - c#

This seems like a common question but I have not been able to get it working from the answers of other questions.
I have a company, which has multiple projects, and each project can have multiple tasks. The company class contains some metadata and also a list of projects that it owns, the project class also has some metadata and a list of tasks it has. What I'm trying to do is to display this hierarchy in a tree view. I have made a sample object of a company which has 2 projects and project1 has 2 tasks. The tree view should only display one company at a time, so the root treeviewitems are the projects.
Heres my attempt at the problem, but nothing is displayed.
<TreeView ItemsSource="{Binding listOfProjects}" Margin="10" Height="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding listOfTasks}" DataType="{x:Type local:Task}">
<TreeViewItem Header="{Binding name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
How do I go about making it correct? What should I bind to ItemsSource? For the TreeViewItem headers, how do I let it know which field to bind? At the moment, I have {Binding name}, but how do I differentiate between where its coming from (because project has a field 'name', and so does task).
I will be extending the hierarchy further (a task can have multiple parts and so on) but I'm assuming that each additional level would be done the same as the first 2.
edit: here my company, project and task classes.
class Company
{
public List<Project> listOfProjects;
public string name { get; set; }
private int companyID { get; set; }
public Company()
{
companyID = 0;
name = "Default";
listOfProjects = new List<Project>();
}
public Company(string inName)
{
companyID = 0;
name = inName;
listOfProjects = new List<Project>();
}
}
class Project
{
private int projID { get; set; }
public string name { get; set; }
public DateTime startDate { get; set; }
public DateTime endDate { get; set; }
public string region { get; set; }
private int companyID { get; set; }
public List<Task> listOfTasks;
public Project()
{
projID = 0;
name = "Default";
startDate = new DateTime();
endDate = new DateTime();
region = "Default";
companyID = 0;
listOfTasks = new List<Task>();
}
public Project(string inName)
{
projID = 0;
name = inName;
startDate = new DateTime();
endDate = new DateTime();
region = "Default";
companyID = 0;
listOfTasks = new List<Task>();
}
}
Tasks atm doesnt have anything in it except a constructor. It has 2 fields, projectID and name.

Your hierarchical data template override your treeview. You must rename listofProject and listofTask to same name. Therefore you create Interface or Class.Then implement Company,Project,Task from this interface.
public interface ITreeItem
{
string Name { get; }
List<ITreeItem> childs { get; }
}
public class Company : ITreeItem
{
public string Name
{
get { return name; }
}
public List<ITreeItem> childs
{
get { return listofProjects; }
}
}
public class Project : ITreeItem
{
public string Name
{
get { return name; }
}
public List<ITreeItem> childs
{
get { return listofTask; }
}
}
public class Task : ITreeItem
{
public string Name
{
get { return name; }
}
public List<ITreeItem> childs
{
get { return null; }
}
}
Then your template is
<HierarchicalDataTemplate ItemsSource="{Binding childs}" DataType="{x:Type local:ITreeItem}">
<TreeViewItem Header="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>

So the top level will all be of type "project", and all branches will be of type "task"? You can use multiple HierarchicalDataTemplates like this:
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="TaskTemplate"
ItemsSource="{Binding Path=Childs}">
<TextBlock FontStyle="Italic" Text="{Binding Path=Name}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="ProjectTemplate"
ItemsSource="{Binding Path=ListOfTasks}"
ItemTemplate="{StaticResource TaskTemplate}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
And then declare your treeview like this ...
<sdk:TreeView ItemsSource="{Binding MyProjectList}"
ItemTemplate="{StaticResource ProjectTemplate}" x:Name="myTreeView" />

Related

WPF TreeView binding multiple different dictionaries in different levels and sorting them by TKey

I have these Models:
public class CustomerGroup
{
public string GroupName { get; set; }
public string GroupKey { get; set; }
}
public class Customer
{
public string GroupKey { get; set; }
public string CustomerKey { get; set; }
public string CustomerName { get; set; }
// and more irrelevant stuff for my question...
}
public class ConstructionSite
{
public string CustomerKey { get; set; }
public string GroupeKey { get; set; }
public string ConstructionSiteName { get; set; }
// and more irrelevant stuff for my question...
}
And i have these Dictionaries:
public class CustomerGroupList : List<CustomerGroup>
{
// Methods for Serialization etc.
}
public class CustomersDictionary : SortedDictionary<string, List<Customer>> // TKey is GroupKey
{
// Methods for Serialization etc.
}
public class ConstructionSiteDictionary : SortedDictionary<string, List<ConstructionSite>> // TKey is customerKey
{
// Methods for Serialization etc.
}
So the customer's dictionary contains the groupKey as TKey and a list of customers for this group as value.
And the construction sites dictionary contains the CustomerKey as TKey and a list of construction sites for this customer as value.
And the Dictionaries are stored in a static class
Now I want to bind these dictionaries as source of an treeview and it should look like this:
-CustomerGroupOne
-CustomerOne
-Construction site A
-Construction site B
-CustomerTwo
-ConSite C
-GroupTwo
-CustomerThree
So I found a solution to Bind the data, BUT I can't figure out how to get the list from the dictionary with the key of the upper node's key.
I Expected it would work like this:
<TreeView x:Name="treeView" ItemsSource="{Binding Source={x:Static dataServices:DataProviderService.CustomerGroupsList}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<Label Content="{Binding GroupName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Source={x:Static dataServices:DataProviderService.CustomersDictionary[GroupKeyOfUpperNodesCurrentGroup]}}">
<Label Content="{Binding CustomerName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Source={x:Static dataServices:DataProviderService.ConstructionSiteDictionary[CustomerKeyOfUpperNodesCorrentCustomer]}}">
<TextBlock Text="{Binding ConstructionSiteName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I hope anyone can help me. Thanks for any help!
Sorry for the bad English...
I'm going to school right now and just started with C# for a little more than one year...
As the name TreeView implies, the control displays a tree data structure. But a dictionary is a flat collection of key-value pairs. Obviously the Dictionary is from data source. You have to convert the dictionary to the proper hierarchical data structure. It's recommended to have your data entities reflect the hierarchical data structure. A hierarchical data structure can be achieved by adding child collections to the data models:
Hierarchical data structure
public class CustomerGroup
{
// Define a child collection
public ObservableCollection<Customer> Customers { get; set; }
public string GroupName { get; set; }
public string GroupKey { get; set; }
}
public class Customer
{
// Define a child collection
public ObservableCollection<ConstructionSite> ConstructionSites { get; set; }
public string GroupKey { get; set; }
public string CustomerKey { get; set; }
public string CustomerName { get; set; }
// and more irrelevant stuff for my question...
}
public class ConstructionSite
{
public string CustomerKey { get; set; }
public string GroupeKey { get; set; }
public string ConstructionSiteName { get; set; }
// and more irrelevant stuff for my question...
}
DataSourceViewModel.cs
class DataSourceViewModel : INot6ifyPropertyChanged
{
public ObservableCollection CustomerGroups { get; }
public DataSourceViewModel()
{
this.CustomerGroups = new ObservableCollection()
{
new CustomerGroup
{
CustomerGroupName = "CustomerGroupOne",
Customers = new Customer
{
CustomerName = "CustomerOne",
ConstructionSites = new ConstructionSite {ConstructionSiteName = "Construction site A"}
}
}
};
}
MainWindow.xaml
Define HieratchicalDataTemplates to display the structure and
assign DataSourceViewModel to the DataContext as non-static binding source.
<Window>
<Window.DataContext>
<DataSourceViewModel />
</Window.DataContext>
<TreeView ItemsSource="{Binding CustomerGroups}">
<TreeView.Resources>
<!-- Root node -->
<HierarchicalDataTemplate DataType={x:Type CustomerGroup}"
ItemsSource="{Binding Customers}">
<TextBlock Text="{Binding GroupName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType={x:Type Customer}"
ItemsSource="{Binding ConstructionSites}">
<TextBlock Text="{Binding CustomerName}" />
</HierarchicalDataTemplate>
<!-- Leaf node -->
<DataTemplate DataType={x:Type ConstructionSite}>
<TextBlock Text="{Binding ConstructionSitesName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>

Displaying hierarchical data in TreeView WPF

I've got the following class... my ViewModel has a property IEnumerable<ApplicationDetection> ApplicationDetections and I'm trying to display it in a TreeView with the XAML below.
public class ApplicationDetection
{
public ApplicationDetection(string name, IEnumerable<DeploymentTypeDetection> detectionMethods)
{
Name = name;
DetectionMethods = detectionMethods;
}
public string Name { get; }
public bool Detected => DetectionMethods.Any(x => x.Detected);
public IEnumerable<DeploymentTypeDetection> DetectionMethods { get; }
}
public class DeploymentTypeDetection
{
public DeploymentTypeDetection(string name, Condition condition)
{
Name = name;
Condition = condition;
}
public string Name { get; }
public bool Detected => Condition.Detected;
public Condition Condition { get; }
}
public class Condition
{
public Condition(string name, bool detected, IEnumerable<Condition> conditions)
{
Name = name;
Detected = detected;
Conditions = conditions;
}
public string Name { get; }
public bool Detected { get; }
public IEnumerable<Condition> Conditions { get; }
}
<Grid>
<TreeView ItemsSource="{Binding ApplicationDetections}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type detectors:ApplicationDetection}" ItemsSource="{Binding DetectionMethods}">
<TreeViewItem Header="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type detectors:DeploymentTypeDetection}" ItemsSource="{Binding Condition}">
<TreeViewItem Header="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type detectors:Condition}" ItemsSource="{Binding Conditions}">
<TreeViewItem Header="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
The problem I have is... I can see the ApplicationDetection.Name (1st tier) and DeploymentTypeDetection.Name (2nd tier) but then I can't see anything below that, what am I doing wrong?
DeploymentTypeDetection.Condition needs to be an enumerable type, even if it has only one item.
e.g.
public IEnumerable<Condition> ConditionEnumerable => new [] { Condition }
and then bind to ConditionEmumerable for the ItemsSource in the data template for DeploymentTypeDetection.

Create TreeView from binding (child node as List<MyType>) in MVVM

I need my TreeView to display playlists and their songs, it should look something like this:
-Playlist1
--Song1
--Song2
--Song3
-Playlist2
--Song1
--Song2
--Song3
--Song4
-Playlist3
--Song1
--Song2
There can be an unlimited amount of playlists in the TreeView, with each node having differing amounts of songs. I want to achieve this using MVVM.
MainWindow.xaml
<TreeView ItemsSource="{Binding PlayLists}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type types:Playlist}" ItemsSource="{Binding PlayLists}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type types:Song}">
<TextBlock Text="{Binding Path=Title}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Playlist.cs
public class Playlist
{
public string Name { get; set; }
public List<Song> Songs { get; set; }
public static ObservableCollection<Playlist> Playlists { get; set; } = new ObservableCollection<Playlist>();
}
Song.cs
public class Song
{
public string FullPath { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
public string Genre { get; set; }
public string Length { get; set; }
public string Size { get; set; }
}
MainViewModel.cs
public ObservableCollection<Playlist> PlayLists { get; set; }
public MainViewModel()
{
/* Get Play-lists */
string listsPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AppName", "Playlists");
if (!Directory.Exists(listsPath))
{
Directory.CreateDirectory(listsPath);
}
string[] playlists = Directory.GetDirectories(listsPath);
foreach (var playlist in playlists)
{
var songs = new List<Song>();
string[] songsInPath = Directory.GetFiles(playlist);
foreach (var song in songsInPath)
{
songs.Add(NewSong(song));
}
Playlist newPlaylist = new Playlist()
{
Name = playlist.Split(Path.DirectorySeparatorChar).Last(),
Songs = songs
};
Playlist.Playlists.Add(newPlaylist);
}
PlayLists = Playlist.Playlists;
}
This is what it looks like:
There should be a sub-nodes for every song in the playlist.
The TreeView only shows the Playlist's Name, but not the Song's Title. I realized this is because the Playlist's property Songs is actually a List<Song>. I am not sure how I can display each Song's Title from the property Playlist.Songs.
Can someone please help me with this.
Your structure for Songs is wrong
Have a look at this example:
<HierarchicalDataTemplate DataType="{x:Type self:Family}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/group.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:FamilyMember}">
<StackPanel Orientation="Horizontal">
<Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" />
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" (" Foreground="Green" />
<TextBlock Text="{Binding Age}" Foreground="Green" />
<TextBlock Text=" years)" Foreground="Green" />
</StackPanel>
</DataTemplate>
Given this Model
public class Family
{
public Family()
{
this.Members = new ObservableCollection<FamilyMember>();
}
public string Name { get; set; }
public ObservableCollection<FamilyMember> Members { get; set; }
}
public class FamilyMember
{
public string Name { get; set; }
public int Age { get; set; }
}
Take a look at this for reference:
TreeView, data binding and multiple templates

How to add an editable TreeViewItem with c#

I have a program that exibits the file system content (files & folders) with code.
each file or folder reflected by a TreeViewItem.
i want to able editing each TreeViewItem from the UI.
Yes, quite easy. Here is an example...
<TreeView x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="0" Background="Beige">
<TreeViewItem Header="Files" ItemsSource="{Binding Root}">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<TextBox Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeView>
Code behind...
public interface INamedObject
{
string Name { get; set; }
}
public class FileObject : INamedObject
{
public string Name { get; set; }
};
public class FolderObject : ObservableCollection<INamedObject>, INamedObject
{
public string Name { get; set; }
};
FolderObject _root = new FolderObject() { Name = "root" };
public FolderObject Root
{
get
{
return _root;
}
}

Data Binding list to listbox C# XAML (Azure mobile services) UWP

So I have an online database. And I want the query results to be displayed in a listbox. I tried to use data binding but I think I'm doing it totally wrong.
Files
[![enter image description here][1]][1]
Table
class ProductTable
{
public string id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public string Type { get; set; }
public string Seller { get; set; }
public int Expiration { get; set; }
}
MainViewModel.cs
namespace Test
{
class MainViewModel
{
IMobileServiceTable<ProductTable> product = App.MobileService.GetTable<ProductTable>();
//In this method, load your data into Products
public async void Load()
{
// This query filters out completed TodoItems.
MobileServiceCollection<ProductTable, ProductTable> Products = await product
.Where(ProductTable => ProductTable.Price == 15)
.ToCollectionAsync();
// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl = Products;
}
}
}
XAML
<Page x:Name="PAGE"
xmlns:local="clr-namespace:Test;assembly=Version1">
<Grid>
<Grid.DataContext>
<local:MainViewModel />
</Grid.DataContext>
<ListBox Margin="10,10,10,100" x:Name="lb" ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Title}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Description}" FontSize="10"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Page>
Solution
Table needs JSON property name explicitly declared otherwise you won't be able to use data binding.
Table.cs
public class ProductTable
{
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "Title")]
public string Title { get; set; }
[JsonProperty(PropertyName = "Description")]
public string Description { get; set; }
[JsonProperty(PropertyName = "Price")]
public double Price { get; set; }
[JsonProperty(PropertyName = "Type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "Seller")]
public string Seller { get; set; }
[JsonProperty(PropertyName = "Expiration")]
public int Expiration { get; set; }
}
XAML.cs
You don't need to declare a new listbox, only use itemsource.
private async void button1_Click(object sender, RoutedEventArgs e)
{
IMobileServiceTable<ProductTable> productTest = App.MobileService.GetTable<ProductTable>();
// This query filters out completed TodoItems.
MobileServiceCollection<ProductTable, ProductTable> products = await productTest
.Where(ProductTable => ProductTable.Type == "Test")
.ToCollectionAsync();
lb.ItemsSource = products;
}
XAML
IN THE STACKPANEL, NEVER EVER USE HEIGHT"*" this will cause a critical error!
<ListBox x:Name="lb" Margin="10,10,10,100" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="300">
<TextBlock Text="{Binding Name}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Title}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Description}" FontSize="10"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You are creating two ListBoxes - one in code that isn't being displayed and one in XAML which is bound to whatever your DataContext is.
Remove the last two lines in your code, and change your XAML to:
ItemsSource="{Binding Products}"
Next, expose Products as a Property on a class (ViewModel if you want to use MVVM), and ensure that the DataContext of your ListBox is set to said ViewModel/class.
I'm not familiar with MobileServiceCollection, so I assume it is bindable. If it is not, expose Products as a ObservableCollection (if the values in it change over time), or any other supported collection type.
Elaboration on Properties
Create a class:
public class MainViewModel {
//This is a property
public ObservableCollection<ProductTable> Products { get; set; }
//In this method, load your data into Products
public void Load(){
//Products = xxx
}
}
In your XAML:
<Page [...]
xmlns:local="clr-namespace:YourNamespace;assembly=YourProjectName">
<Grid>
<Grid.DataContext>
<local:MainViewModel />
</Grid.DataContext>
<!-- ListBox goes in here somewhere, still with ItemsSource bound to Products -->
</Grid>
</Page>
Read more about namespaces here.
By doing this, your Windows' DataContext will be set to the MainViewModel (could also be named ProductsViewModel). Since your ListBox will be within your Window, it will inherit (due to Property Value Inheritance) the same DataContext.
The Binding will look for a property 'Products' on the ViewModel.
You will need to call the Load() method somewhere.
Part 2 - After looking at the code
Mind you, I am not able to run the code, so I'm flying somewhat blind.
Buy.xaml.cs
public sealed partial class Buy : Page
{
private readonly MainViewModel _viewModel;
public Buy()
{
this.InitializeComponent();
_viewModel = new MainViewModel();
DataContext = _viewModel;
}
private async void button1_Click(object sender, RoutedEventArgs e)
{
_viewModel.Load();
}
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
IMobileServiceTable<ProductTable> product = App.MobileService.GetTable<ProductTable>();
public List<ProductTable> Products { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public async void Load()
{
Products = await product
.Where(ProductTable => ProductTable.Price == 15)
.ToListAsync();
//Notify that the property has changed to alert to UI to update.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Products)));
}
}
You need to make ProductTable public, or you will get a compilation error.
Hopefully, the above will enable you to bind your ListBox using the ItemsSource binding described above.
And please note that the above code is not necessarily best practice.
you don't need that line in your code behind
ListBox lb = new ListBox();
and you can keep that line
lb.ItemsSource = Products;
or you can sit the Binding to the MobileServiceCollection in the XAML
<ListBox Margin="10,10,10,100" x:Name="lb" ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories