Binding Dictionary<string,List<Class>> to multiple TreeViewItem - c#

So I've got some data that I want to store in a treeview and I have it working with 2 branches but I'm stuck on getting more than that. I think a dictionary would be best for this cause it'll be easiest to store the IDs in a key when I come to add in the 'sanity checks'
How I want my data to be presented:
--Internet Checks
----- 'Date, message, status'
--------- 'Details'
--Server Checks
----- 'Date, message, status'
--------- 'Details'
--Sanity Checks
----- 'Date, message, status'
--------- 'Details'
--------- 'Error1'
--------- 'Error2'
--------- 'Error3'
--------- 'etc'
--etc
And here's how it looks at the moment
--Internet Checks
----- 'Date, message, status'
--Server Checks
----- 'Date, message, status'
--Sanity Checks
----- 'Date, message, status'
--etc
So here's how I have my xaml at the moment:
<TreeView Name="treeView1" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value.Text}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here is my MenuItem class:
public class MenuItem
{
public MenuItem()
{
this.SubItems = new Dictionary<int, MenuItem>();
}
public Dictionary<int, MenuItem> SubItems { get; set; }
public string Text { get; set; }
}
And here is how I'm currently filling in the dictionary:
public Dictionary<int, MenuItem> Fill(string name, Dictionary<int, MenuItem> root, List<HealthCheckHourModel> list)
{
root.Add(nextNum, new MenuItem { Text = name + " Checks"});
foreach (HealthCheckHourModel hchm in list)
{
if (hchm.Message.Contains(name))
{
root[nextNum].SubItems.Add(hchm.ID, new MenuItem { Text = hchm.StartTime + " " + hchm.Message });
root[nextNum].SubItems[hchm.ID].SubItems.Add(-111, new MenuItem { Text = hchm.Details });
}
}
nextNum--;
return root;
}

There are two possibilities:
Delete ItemTemplate for HierarchicalDataTemplate.
<TreeView Name="treeView1" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
<!--<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value.Text}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>-->
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Leave ItemTemplate for HierarchicalDataTemplate, but replace DataTemplate with HierarchicalDataTemplate
<TreeView Name="treeView1" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Related

Display multiple TreeViews as an ObservableCollection of List of TreeViewItems in WPF

Since I can only ask one question per post, I will try again here.
I try to explain my problem as simple as I can. I would like to load a KML file and show its structure as a TreeView. The asynchronous load method populates the whole TreeView with root nodes and children nodes.
It works fine if I only load 1 KML file. But I would like to load multiple KML files and show the whole structure as a list of TreeViews if possible.
My View.xaml looks like that:
<ListView ItemsSource="{Binding TreeViews}">
<ListView.ItemTemplate>
<HierarchicalDataTemplate>
<TreeView Name="TreeView" MaxHeight="300" SelectedItemChanged="TreeView_SelectedItemChanged" ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeViewItem}" ItemsSource="{Binding ChildrenItems}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding IsChecked}"/>
<ContentPresenter Margin="2,0,0,0" Content="{Binding NodeName, Mode=OneTime}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</HierarchicalDataTemplate>
</ListView.ItemTemplate>
</ListView>
In my ViewModel I have two properties:
public ObservableCollection<List<TreeViewItem>> TreeViews { get; set; }
private List<TreeViewItem> treeViewItems;
public List<TreeViewItem> TreeViewItems
{
get { return treeViewItems; }
set
{
treeViewItems = value;
OnPropertyChanged();
}
}
For testing purposes I just added a few items in the load method:
List<TreeViewItem> temp = new List<TreeViewItem>();
TreeViewItem rootItem = new TreeViewItem(null, null);
TreeViewItem ChildItem1 = new TreeViewItem(null, null);
TreeViewItem ChildItem2 = new TreeViewItem(null, null);
rootItem.ChildrenItems.Add(ChildItem1);
rootItem.ChildrenItems.Add(ChildItem2);
temp.Add(rootItem);
TreeViewItems = temp;
TreeViews.Add(TreeViewItems);
TreeViews.Add(TreeViewItems);
Now it should show 2 identical TreeViews in the View. But unfortunately it only shows 2 empty list items with a CheckBox.
Does anybody have a hint for me how I can show multiple TreeViews?
Your question doesn't make much sense at present - why do you think you need TreeViews within TreeViews to handle this hierarchical data structure? What is local:TreeViewItem and how is it different to System.Windows.Controls.TreeViewItem?
You'd be much better off separating out the data structure from the visual controls. Something like this, with distinct classes for each level of items to be displayed in the TreeView.
public class KLMFile
{
public string FileName {get; set;}
...
public List<KLMItem> Children {get;} = new List<KLMItem>();
}
public class KLMItem
{
public string Description {get;set;}
...
public List<KLMSubItem> Children {get;} = new List<KLMSubItem>();
}
public class KLMSubItem
{
public string Description {get;set;}
...
// top level items - no children
}
Assign (or bind) a collection of KLMFile items as the ItemsSource property of your TreeView control.
Create a HierarchicalDataTemplate (or plain DataTemplate for the highest level items that don't have more children) for each item type within the TreeView.
<TreeView ItemsSource="{Binding KLMFileList}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:KLMFile}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding FileName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:KLMItem}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type local:KLMSubItem}">
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
More details on WPF TreeViews (but focussing more on using them within the MVVM design pattern) on my blog post.
The ListView is bound to an ObservableCollection<List<TreeViewItem>> but you are trying to bind to a non-existing TreeViewItems property of the List<TreeViewItem> in your ItemTemplate:
<TreeView Name="TreeView" MaxHeight="300" ItemsSource="{Binding TreeViewItems}">
This won't work. You should bind directly to the List<TreeViewItem>:
<TreeView Name="TreeView" MaxHeight="300" ItemsSource="{Binding}">

Cannot bind to ListBox child of ListBox

I'm binding to a ListBox, and within the ListBox, another ListBox.
My model looks like
public interface ICriteriaDetail
{
string Title { get; }
int NumberOfEvents { get; }
ICriteriaDetail ChildCriteria { get; }
}
So, I'm using a recursive approach for the children (not a list).
When I bind to my ListView, I get the items in a list, one under the other.
The issue is, the child item is not showing at all!
The dummy data
public static IEnumerable<ICriteriaDetail> GetCriteriaList()
{
var list = new List<CompoundCriteria.Ui.Model.Interfaces.ICriteriaDetail>();
list.Add(GetCriteriaDetail("My Title", 5, false));
list.Add(GetCriteriaDetail("Other", 3, false));
list.Add(GetCriteriaDetail("Biggy", 8, true));
var child = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("childAgain", 43, null);
var child2 = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("childAgainAgain", 13, child);
list.Add(new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("Really big", 86, new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("smaller", 15, child2)));
return list;
}
private static ICriteriaDetail GetCriteriaDetail(string title, int events, bool hasChild)
{
if (!hasChild)
return new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail(title, events, null);
var child = new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail("child" + title, 13 + events, null);
return new CompoundCriteria.Ui.Model.CriteriaDetail.CriteriaDetail(title, events, child);
}
The ViewModel
public MainWindowViewModel()
{
this.Criterias = DatasourceMockup.GetCriteriaList();
}
private IEnumerable<ICriteriaDetail> _criterias;
public IEnumerable<ICriteriaDetail> Criterias
{
get { return _criterias; }
set { _criterias = value; }
}
And the ListBox in the XAML
<ListBox ItemsSource="{Binding Criterias}" >
<ListBox.Resources>
<ControlTemplate x:Key="con">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="15">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="Events: ">
<Run Text="{Binding NumberOfEvents}" />
</TextBlock>
</StackPanel>
<ListBox ItemsSource="{Binding ChildCriteria} Visibility="{Binding ChildCriteria, Converter={StaticResource HideIfNull}}">">
<ListBox.Resources>
<DataTemplate DataType="{x:Type cDetail:CriteriaDetail}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
</StackPanel>
</ControlTemplate>
<HierarchicalDataTemplate DataType="{x:Type cDetail:CriteriaDetail}">
<Control Template="{StaticResource con}" />
</HierarchicalDataTemplate>
</ListBox.Resources>
</ListBox>
After reading TreeView, HierarchicalDataTemplate and recursive Data it appears that HierarchicalDataTemplate will suffice (no need for a DataTemplate as well) but, I'm lost as to why I'm not seeing the result I expected (even if the children are not in the desired place, I'd still hope to see them)
EDIT
I have just added a converter which makes the ListBox (the one inside the ControlTemplate) hidden if the bound data is null. I have an outline (an empty ListBox) for just 1 of the 3 (which is correct) but it doesn't bind any of the content (the Title). The output window shows nothing of any use...
Please note, there is no limit of chidren. In the example above, there is a parent child relationship, but it could be parent-child-child-child etc
1) It is hard to understand your purpose , why ListBox, if you have ChildCriteria in model, not IEnumerable<ICriteriaDetail>?
2) Also you have binding errors, i changed xaml to
<ListBox ItemsSource="{Binding Criterias}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Margin="15">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="Events: ">
<Run Text="{Binding NumberOfEvents}" />
</TextBlock>
</StackPanel>
<TextBox Text="{Binding Path=DataContext.ChildCriteria.Title,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem},
AncestorLevel=1}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and got
EDIT1:
1) I changed data model to
public class CriteriaDetail : ICriteriaDetail
{
public CriteriaDetail(string title, int numberOfEvents, IEnumerable<ICriteriaDetail> childCriteria)
{
Title = title;
NumberOfEvents = numberOfEvents;
ChildCriteria = childCriteria;
}
public string Title { get; set; }
public int NumberOfEvents { get; set; }
public IEnumerable<ICriteriaDetail> ChildCriteria { get; set; }
}
2) Changed xaml to
<TreeView ItemsSource="{Binding Criterias}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildCriteria}">
<TextBlock Foreground="Red" Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
3) Changed GetCriteriaDetail to
private static ICriteriaDetail GetCriteriaDetail(string title, int events, bool hasChild)
{
if (!hasChild)
return new CriteriaDetail(title, events, null);
var child3 = new CriteriaDetail("child3" + title, 13 + events, null);
var child2 = new CriteriaDetail("child2" + title, 13 + events, new ICriteriaDetail[] { child3 });
var child1 = new CriteriaDetail("child1" + title, 13 + events, new ICriteriaDetail[] { child2 });
return new CriteriaDetail(title, events, new ICriteriaDetail[] {child1});
}
and got this. I think it looks more like you want

Binding a WPF TreeView to multiple Lists

I'd like to display the following structure in a WPF Treeview:
public class Group{
public string Groupname;
public IEnumerable<Group> Groups;
public Ienumerable<User> Member;
}
My ViewModel looks like this:
public class ViewModel{
public Group RootGroup;
}
I think the XAML Code should look like this:
<TreeView>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource={Binding ViewModel.RootGroup}>
<TextBlock Text={Binding Groupname}/>
<HierarchicalDataTemplate ItemsSource={Binding Member}>
<TextBlock Text={Binding Displayname}/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
What I want it to look like:
RootGroup-Name
-Member1
-Member2
-Member3
-Member4
-SubGroup1
-Member1
-Sub-SubGroup1
-Member1
-SubGroup2
-Sub-SubGroup2
-Sub-Sub-SubGroup1
-Member1
I've bound the DataContext to itself so this shouldn't be the reason why my TreeView wont show anything.
Finally I just found the solution by myself:
<TreeView Grid.Row="1" ItemsSource="{Binding MVM.RootGroup}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Groups}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Members}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Displayname}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
But the Users from the first level get lost.
To achieve that behavior you should change ViewModel and ItemTemplate. Here some code that I've used in my project.
<HierarchicalDataTemplate DataType="local:GItemViewModel" ItemsSource="{Binding Path=Nodes}">
<DockPanel>
<TextBlock Margin="2" VerticalAlignment="Center" Text="{Binding Path=Name}" FontSize="12" />
</DockPanel>
</HierarchicalDataTemplate>
ViewModel for this. Here Name is name for group and name for member depends on the object. Member is the node with Nodes is set to null.
public sealed class GItemViewModel : INotifyPropertyChanged
{
private string _name = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged( "Name" ); }
}
public ObservableCollection<GItemViewModel> Nodes { get; set; }
...
}
I hope this helps.
Update
To set root element Name you can add into the ViewModel elemet like this. And bind the root collection to the treeView like this.
GItemViewModel vm = new GItemViewModel();
GItemViewModel root = new GItemVieModel() { Name = "Root" };
vm.Nodes.Add( root );
treeView.ItemsSource = vm.Nodes;

Hierarchical-DataBinding in TreeView

I have a class called ClassWithPupils which looks like:
public class ClassWithPupils : ViewModelBase
{
public ClassWithPupils(IClass #class)
{
Class = #class;
Pupils = new ObservableCollection<IPupil>();
}
public IClass Class
{
get { return Get<IClass>(); }
set { Set(value); }
}
public ObservableCollection<IPupil> Pupils
{
get { return Get<ObservableCollection<IPupil>>(); }
set { Set(value); }
}
}
And I have a ViewModel which contains an ObservableCollection<ClassWithPupils>.
private ObservableCollection<ClassWithPupils> classesWithPupils;
public ObservableCollection<ClassWithPupils> ClassesWithPupils
{
get { return classesWithPupils ?? (classesWithPupils = new ObservableCollection<ClassWithPupils>()); }
}
This collection is filled correct with items from a Database.
Now I want to display all items from ClassesWithPupils hierarchical in a TreeView.
My View so far looks like:
<TreeView ItemsSource="{Binding ClassesWithPupils, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<Label Content="{Binding Class.Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So the Class-Names are displayed correct.
Unfortunately I have no clue how to bind the Pupils-Collection of each ClassWithPupils-Entry to the correct item in the TreeView as children.
I tried something like:
<TreeView ItemsSource="{Binding ClassesWithPupils, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<Label Content="{Binding Class.Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Pupils}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
With no success...
So my question is: How can I display the Name of the Pupils as children of the classes in the TreeView?
After looking at your code I have spotted only one error in binding, that could cause the described problem, you should bind to Pupils inside the hierarchical template like this:
<TreeView ItemsSource="{Binding ClassesWithPupils, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Pupils}">
<Label Content="{Binding Class.Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Simple Nested TreeView Xaml structure?

I am trying to build a WPF TreeView with three layers. CountryReportTitle is a string property and ArticleCategoryTitlesList is a collection, both exposed from my ViewModel. There is no class hierarchy defined. This is the structure I'm looking for:
This is my attempted Xaml but I'm getting an exception in the Xaml at runtime:
{"Item has already been added. Key in dictionary: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)' Key being added: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)'"}
Xaml:
<TreeView ItemsSource="{Binding CountryReportTitle}">
<TreeView ItemsSource="{Binding CountryReportTitle}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ReportViewModel}"
ItemsSource="{Binding ArticleCategoryTitlesList}">
<TextBlock Text="{Binding CategoryTitle}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ReportViewModel}"
ItemsSource="{Binding ArticleCatagoryTypesList}">
<TextBlock Text="{Binding ArticleTitle}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ReportViewModel}">
<TextBlock Text="{Binding ArticleTitle}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</TreeView>
Local: is a namespace to my ViewModel:
xmlns:local="clr-namespace:MyApp.ViewModels"
What am I doing wrong, what is the best approach for this problem?
Here's my go-to example for treeviews.
Use a HierarchicalDataTemplate for elements in the tree. Note that there are three layers, and each layer is its own type. This is for convenience, but you could define one type and use one template or any mix of types for your tree. Having different types represent different things in the tree makes using templates extremely convenient.
The data classes
public class ViewModel
{
public ObservableCollection<ItemA> ItemsA { get; set; }
public ViewModel()
{
ItemsA = new ObservableCollection<ItemA>(new[]{
new ItemA{Name = "A one"},
new ItemA{Name = "A Two"},
new ItemA{Name = "A Three"},
});
}
}
public class ItemA
{
public ObservableCollection<ItemB> ItemsB { get; set; }
public string Name { get; set; }
public ItemA()
{
ItemsB = new ObservableCollection<ItemB>(new[]{
new ItemB{Name = "B one"},
new ItemB{Name = "B Two"},
new ItemB{Name = "B Three"},
});
}
}
public class ItemB
{
public ObservableCollection<ItemC> ItemsC { get; set; }
public string Name { get; set; }
public ItemB()
{
ItemsC = new ObservableCollection<ItemC>(new[]{
new ItemC{Name = "C one"},
new ItemC{Name = "C Two"},
new ItemC{Name = "C Three"},
});
}
}
public class ItemC
{
public string Name { get; set; }
}
And the UI
<TreeView ItemsSource="{Binding ItemsA}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type t:ItemA}"
ItemsSource="{Binding ItemsB}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type t:ItemB}"
ItemsSource="{Binding ItemsC}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type t:ItemC}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
gives you a simple treeview
You need to bind the TreeView Resources inside your TreeView:
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:FirstLayer}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding ChildrenName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:SecondLayer}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding ChildrenName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
I think you'll need to modify your project to bind your Treeview with your ViewModel instead of a list of string
Here is a good Example

Categories

Resources