WPF TreeView binding multiple sub-collections - c#

I am building an application to manage connections, databases, tables, etc. I am needing to bind a collection of items with multiple sub-collections (see below). I am pretty new to WPF and am not sure if an answer to this question already exists. I've searched but haven't found any examples of the scenario I am faced with.
server1
-database1
--functions <- "static" node
---function1
---function2
--users <- "static" node
---user1
---user2
-database2
--functions <- "static" node
---function3
---function4
--users <- "static" node
---user3
---user4
When I try to bind it, I can get the data to display but it isn't in the format needed above. It's displayed like this.
server1
-database1
--function1
--function2
--user1
--user2
object hierarchy:
class DatabaseViewModel
{
public string Name
{
// normal getters and setters for 2way binding
}
public IObservableCollection<DbFunctionViewModel> Functions
{
// normal getters and setters for 2way binding
}
public IObservableCollection<DbUserViewModel> Users
{
// normal getters and setters for 2way binding
}
}
Markup:
<TreeView x:Name="Connections">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbConnectionViewModel}" ItemsSource="{Binding Databases}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbDatabaseViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbFunctionViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:DbUserViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Name" Text="{Binding UserName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I tried returning a CompositeCollection containing both and it didn't seem to work the way i need it to.
public IList Children
{
get
{
return new CompositeCollection
{
new CollectionContainer { Collection = Functions },
new CollectionContainer { Collection = Users }
};
}
}
My question is, how do you bind all the users to a node named users and all the functions to a node named functions? Any advice would be greatly appreciated. Thanks!

To get your Children property to work, you need to define a data template for CollectionContainer:
<HierarchicalDataTemplate DataType="{x:Type viewModels:CollectionContainer}"
ItemsSource="{Binding Collection}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
You should add a string Name property to CollectionContainer:
public IList Children
{
get
{
return new[]
{
new CollectionContainer("Functions", Functions),
new CollectionContainer("Users", Users),
};
}
}
A few hints:
Stack panels are redundant.
x:Name is redundant if you're binding using Text={Binding ...}.

Related

WPF TreeView - If I use binding, the ToString() is never called and the text is blank. If I set the items programmatically, it works fine

The Classes
public class TreeDiagram : INotifyPropertyChanged
{
public string CategoryName
{...}
public ObservableCollection<ImageInfo> DiagramsTRV
{...}
public class ImageInfo : INotifyPropertyChanged
{
public string Category
{ ...}
public override string ToString()
{
return DisplayName;
}
public string DisplayName
{...}
public ObservableCollection<TreeDiagram> TreeDiagrams {...}
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<TextBlock Text="Binding"/>
<TreeView x:Name="trvDiagrams" Width="300" Height="400" ItemsSource="{Binding TreeDiagrams}" HorizontalAlignment="Left">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="x:Type local:TreeDiagram" ItemsSource="{Binding DiagramsTRV, UpdateSourceTrigger=PropertyChanged}">
<TextBlock Width="250" Text="{Binding CategoryName, UpdateSourceTrigger=PropertyChanged}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBox Text="No Binding"/>
<TreeView x:Name="trvNoBind" Width="300" Height="400" HorizontalAlignment="Right">
</TreeView>
</StackPanel>
</StackPanel>
</StackPanel>
If I use binding, the Category Name is displayed, the The TreeDiagram ToString() is never called and the dropdown items are blank.
If I load the tree programmatically with the same data - everything is fine.
But for a variety of reasons, I need to use binding.
I have tried an IValueConverter to take the collection of objects and return a collection of strings - no joy.
Any suggestions as to what I am doing wrong?
Your template is incomplete. You must define a template for the child nodes too. You must either set the HierarchicalDataTemplate.ItemsTemplate property accordingly or define an implicit template e.g. in the ResourceDictionary of the TreeView.
You want to display a recursive or hierarchical data structure. Since your structure consists of different data types, you must define a template for each level.
Note that UpdateSourceTrigger.PropertyChanged is the default value of Binding.UpdateSourceTrigger and is therefore not explicitly required.
<TreeView>
<TreeView.Resources>
<!--
Because 'ImageInfo' has no children,
define a 'DataTemplate' (instaed of another 'HierarchicalDataTemplate').
If this 'DataTemplate' wasn't implicit, you would have to explicitly assign it
to the parent's 'HierarchicalDataTemplate.ItemTemplate' property.
-->
<DataTemplate DataType="{x:Type local:TreeDiagram}">
<TextBlock Text="{Binding}" /> <== Force the call to 'object.ToString' by binding to the object itself
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeDiagram}"
ItemsSource="{Binding DiagramsTRV}">
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>

How to correctly bound to a class with inheritance

I have a treeview. In the treeview I have Student - Homework
The Homework is a Class with Hineritance, so under Homerwork I have -> ScienceH, PhysicsH, MathH
How can I create a XAML that is bound to the Homework list but changes depending on the "child" specifics?
I'm blocked as I do not understand how to do the binding
class Student
{
public string Name;
List<Homework> homeworks;
}
class Homework
{
public DateTime Date;
public int Vote;
}
class ScienceH : Homework
{
public string Topic;
}
class PhysicsH : Homework
{
public int Points;
}
I expect to have an item that shows the additional fields depending on which child they have
You can define a TreeView in UWP XAML like this, as detailed here:
<TreeView ItemsSource="{x:Bind ListOfStudents}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="model:Student">
<TreeViewItem ItemsSource="{x:Bind homeworks}"
Content="{x:Bind Name}"/>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You would then need to define a DataTemplate for each of class you expect to display. Add this in your TreeView XAML:
<TreeView.Resources>
<DataTemplate x:DataType="model:ScienceH">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Date}" />
<TextBlock Text="{Binding Vote}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:DataType="model:PhysicsH ">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Date}" />
<TextBlock Text="{Binding Vote}" />
<TextBlock Text="{Binding Points}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
You can also read more on the DataTemplate class here.
Note that, as suggested in the comments, you need to implement change notifications in your classes to ensure correct binding. This is all detailed here.
Alternatively, you seem to have a TreeView with a tree structure of known, fixed depth: depth 0 would be the list of Student and depth 1 would be the list of Homework, so you could also do without TreeView and simply nest two ItemsControl within one another.

Nested Dynamic Content Controls with MVVM

I am interested in creating a system of nested content controls to visually represent a user created network of nodes in an automation system.
Simply, I have nodes 'x' and they each contain modules 'y' which host channels 'z'.
So far I have set up in the ViewModel a system for instantiating all of this.
I have a List<x> where x is a model that contains a List<y> (and attributes: name, ID),
where y is a model that contains List<z>(and attributes: name, index) where z is a model for a channel (attributes: name, state, command).
I would now like to display these in my View.
The way I would like to do this is as follows, for each model x in List<x> there should be a Headered Content Control (or some other control) whose item-source is the List<y> in this model x. The Content Control should also display the 'name' attribute of x.
The datatemplate for each y under this Content Control should be a similar Content Control where the item-source is the List<z> in this model y. The Content Control should also display the 'name' attribute of y.
Finally, each model z under this content control should be displayed as a CheckBox that binds it's "ischecked" state to the 'state' attribute of the model, it's content to the 'name' attribute, and it's command to the 'command' attribute.
My question is; is there a way to do this in MVVM? And if so, how would I go about setting it up?
As usual, there are several ways to accomplish this task. It strongly depends on what kind of visual result you want to achieve.
You can display your data as a tree:
<TreeView ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Node}"
ItemsSource="{Binding Modules}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Module}"
ItemsSource="{Binding Channels}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Channel}">
<CheckBox Content="{Binding Name}"
IsChecked="{Binding State}"
Command="{Binding Command}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
In this example both appearance and relation to nested items for your classes are defined by HierarchicalDataTemplates. TreeView control is "smart" enough to know what to do with those.
More general solution would be something along these lines:
<ItemsControl ItemsSource="{Binding Nodes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<HeaderedContentControl Header="{Binding Name}">
<ItemsControl ItemsSource="{Binding Modules}"
Margin="10,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<HeaderedContentControl Header="{Binding Name}">
<ItemsControl ItemsSource="{Binding Channels}"
Margin="10,0,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding State}"
Content="{Binding Name}"
Command="{Binding Command}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</HeaderedContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</HeaderedContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here a template for each class is defined explicitly by ItemTemplate property on corresponding ItemsControl. I used Margin property for nested items to introduce some indentation.
NOTE
I've replaced x, y and z class names with Node, Module and Channel respectively for the sake of readability. Also, I'm using corresponding collection names - Nodes, Modules and Channels - which I believe should be self-explanatory.

WPF TreeView - Confusing data binding and style for child nodes

I am confused of WPFs data binding and style declaration for TreeViews, especially for child nodes.
My ViewModel contains an object with the following hierarchy:
- Component1
- SubcomponentA
- SubcomponentB
- Component2
- SubcomponentX
- SubcomponentY
- SubcomponentZ
I would like to modify the XAML file so I do not have to do anything within the .cs file.
This piece of code actually works:
<TreeView Name="tvComponent" ItemsSource="{Binding BpModule.BpComponentPrototypes.Elements}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding BpSubcomponents.Elements}">
<StackPanel Orientation="Horizontal">
<CheckBox Name="cb_run"></CheckBox>
<TextBlock Text="{Binding ShortName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
However, I would like to create different styles for root and child nodes.
I tried different approaches with almost completely different XAML code. But the major problem was to describe the dependency of the binding of child nodes to their parent and so they remained empty during runtime.
Can you help me out?
Thank you.
In my opinion you can use the implicit DataTemplate mechanism (take a look here to the DataType property). In this way you can define more DataTemplates, each one of them "linked" to a specific Type.
<TreeView Name="tvComponent" ItemsSource="{Binding BpModule.BpComponentPrototypes.Elements}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:RootType}"ItemsSource="{Binding ...}">
...
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ComponentType}" ItemsSource="{Binding BpSubcomponents.Elements}">
<StackPanel Orientation="Horizontal">
<CheckBox Name="cb_run"></CheckBox>
<TextBlock Text="{Binding ShortName}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:SubcomponentType}">
...
</DataTemplate>
</TreeView.Resources>
</TreeView>
Otherwise you can always use a DataTemplateSelector to create your own logic which chooses the right template for you.
Well, somehow I achieved to provide different designs for root and children nodes. Here is the XAML code:
<TreeView Name="tvSoftwareComponentPrototypes" ItemsSource="{Binding Path=BpModule.BpComponentPrototypes.Elements}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding BpSubcomponents.Elements}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="cb_run"></CheckBox>
<Image Source="/Resources/SubComponent.png" Margin="5,0,3,0" />
<TextBlock Text="{Binding ShortName}" />
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="cb_swc" Checked="cb_swc_Checked" Unchecked="cb_swc_Unchecked"></CheckBox>
<Image Source="/Resources/Component.png" Margin="5,0,3,0" />
<TextBlock Text="{Binding ShortName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And the next problem is that I cannot access the Checkboxes at runtime.
WPF freaks me out :/
EDIT:
Hold on .. actually this is quite easy by changing the checkbox to the following XAML code:
<CheckBox Name="cb_swc" IsChecked="{Binding Path=PropertyName, Mode=TwoWay}"/>

How to bind a list of list to a ListView control

I have The following structure
QPage class Object contains a List<List<QLine>>
QLine object contains List<Qword>
every list of words constructs a line and every list of lines consists a group(paragraph) and every list of paragraphs consists a page.
I want to bind the page to structure like this in XAML
<ListView>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock>
</TextBlock>
</StackPanel>
</StackPanel>
</ListView>
where each item of the ListView is a paragraph(List<QLine>) and each vertical stack panel holds an item of the List<QLine> and each item of the horizontal stack panel holds an item of the List<Qword> and the texblock is bound to Qword.text property. I have no idea how to do such binding from the XAML code.
Hopefully I did not miss some list but this should work. Basically it's a ListBox that hosts List<List<QLine>> (called it QPageList). Then you have ItemsControl that hosts each List<QLine> in vertical panel and finally there is another ItemsControl that hosts List<Qword> from QLine (called it QwordList) where each QWord is displayed as TextBlock on horizontal StackPanel
<!-- ItemsSource: List<List<QLine>>, Item: List<QLine> -->
<ListBox ItemsSource="{Binding QPageList}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: List<QLine>, Item: QLine -->
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- ItemsSource: QLine.List<QWord>, Item: QWord -->
<ItemsControl ItemsSource="{Binding QwordList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Item: QWord -->
<TextBlock Text="{Binding text}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What you are looking for is ListView.ItemTemplate. Basically, you need to provide your list with a way to understand the nested data structure of your rows.
Here is a good tutorial to get you started on ItemTemplates.
Once your list has an item template then you just bind the ListView directly to your data source and that's it.
Hopefuly this will prove helpful. Taken from here
Authors (list)
- - Name
- - Books (list)
| - - Title
| - - Contents
Sample code :
<Window.Resources>
<DataTemplate x:Key="ItemTemplate1">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate2">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource AuthorDataSource}}">
<ListBox x:Name="AuthorList" ItemTemplate="{DynamicResource ItemTemplate1}" ItemsSource="{Binding Authors }" >
<ListBox x:Name="BookList" DataContext="{Binding SelectedItem, ElementName=AuthorList}" ItemsSource="{Binding Books }" ItemTemplate="{DynamicResource ItemTemplate2}" />
<TextBlock x:Name="BookContent" DataContext="{Binding SelectedItem, ElementName=BookList}" Text="{Binding Contents }" />
</Grid>
Forward to what Alex is saying, it'd be a good idea to Encapsulate (If that's the correct word here) the object within classes, i.e.:
public class Page
{
public List<Paragraph> Paragraphs { get; set; }
}
public class Paragraph
{
public List<QWord> Sentences { get; set; }
}
public class Sentence
{
public List<QWord> Words { get; set; }
}
That'd help when you bind data in your XAML, i.e. if you look at the tutorial which Alex provided:
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Age}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding Mail}" TextDecorations="Underline" Foreground="Blue" Cursor="Hand" />
<TextBlock Text=")" />
Hope that helps you.

Categories