Grouping child objects in WPF TreeView - c#

I am trying to get my tree view to group a collection of similar items by what they are. To keep things generic, my object hierarchy could look like this:
Objects
Object Group #1
Item #1 (Type 'A')
Item #2 (Type 'A')
Item #3 (Type 'B')
Item #4 (Type 'B')
Right now my TreeView shows these objects exactly like the object model, but what I would like to do is insert a TreeView node for each object type so that it would look like this:
Objects
Object Group #1
Type A
Item #1
Item #2
Type B
Item #3
Item #4
I saw in a similar question here that someone recommended to have two separate HierarchicalDataTemplates so I created one for 'Object Group #1' level which contains a TreeView with a list of the types, but this is really clumsy since it is a whole separate TreeView inside of some nodes. I have also been trying to use a CollectionViewSource to filter out the items in each category, but this doesn't do me very much good since I can't figure out how to display them.
I guess my question boils down to this: How do I make a HierarchicalDataTemplate group it's children? If someone could point me in the right direction I would appreciate it a lot.
I can post some code if anyone wants to see, but I am really just trying to figure out how to do what I want so my code is just a pretty straight forward databound treeview right now.

Take a look at this article by Mr. Sumi. I'm sure it will help you.
The gist of the article:
My solution to that very problem requires the following ingredients:
A MultiBinding that allows you to combine different bindings.
A converter that helps us organizing the different bound collections
into sub folders, where necessary.
And of course: Data templates that provide a visual representation of your bound data.

You can achieve this effect by binding the ItemsSource on your HierarchicalDataTempalate using an IValueConverter. This converter is simply does the following:
public class MyConverter : IValueConverter
{
public object Convert(object value, ...)
{
return
from item in (IEnumerable<MyItem>)value
group item by item.Type into g
select new { Type = g.Key, Items = g }
}
...
}
Now your HierarchcialDataTemplate can be as follows:
<HierarchicalDataTemplate ItemsSource="{Binding SomePath, Converter={x:Static local:MyConverter}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Items}"
TargetType="{x:Type local:MyItem}"
ItemTemplate="{StaticResource MyItemTemplate}">
<!-- may omit ItemTemplate in prior line to use implicit template -->
<TextBlock Text="{Binding Type}" /> <!-- Header for type -->
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<!-- header for "Object Group #1" -->
</HierarchicalDataTemplate>

AFAIK, HierarchicalDataTemplate can't group its children.
View should just display whatever it gets, without really digging into objects kinds / groups... Why don't you create these groups in your object model?
And the view will just get smth like:
public interface ITreeNode
{
string Title;
IList<ITreeNode> ChildNodes;
}
and display it using the HierarchicalDataTemplate.

If this is a simple Grouping Method from a Flat Collection for display purpose that you are looking for maybe using a "CollectionViewSource" will be more suitable. Using LINQ could become a nightmare due to Property/Collection Change event propagation.
<CollectionViewSource x:Key="GroupedItems" Source="{Binding ItemsSource, ElementName=My_UserControl}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Type" Converter="{StaticResource GroupingConverter}" />
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Date"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<HierarchicalDataTemplate x:Key="GroupDataTemplate" ItemsSource="{Binding Items}" >
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<TreeView x:Name="ItemHolder" x:FieldModifier="private"
ItemsSource="{Binding Source={StaticResource GroupedItems}, Path=Groups}"
... />

Related

C# WPF reach an object in ObservableCollection by binding a TreeViewItem

Im working on a WPF project trying to get information from an ImpactElement-object. That object is inside a ObservableCollection called ElementList. This ElementList is located inside another ObservableCollection called ListOfCreatedStacks that holds objects of the Class Stack.
It only shows the toString from the element (ImpactElement) inside ElementList. I want the ImpactElements variable ElementMark to be shown from each elemnt in ElementList.
This is how it looks right now One element added in the ElementList
I can create any numbers of Stack and inside each stack there´s a ElementList with different numbers of ImpactElement. Two stacks with different amount of ImpactElements in the ElementList
From these pictures you can see that each ImpactElement from the ElementList is shown as "IMPACT_Visual_Stacker.Model.ImpactElement" but i want it to be the variable ElementMark that is inside ImpactElement Class.
Here´s the code from different Classes.
public class Controller
{
public ObservableCollection<Stack> ListOfCreatedStacks { get { return listOfCreatedStacks; } set { listOfCreatedStacks = value; } }
}
public class Stack
{
public ObservableCollection<ImpactElement> ElementList { get { return elementList; } set { elementList = value; } }
public string Id { get { return id; } set { id = value; } }
}
public class ImpactElement
{
private string elementMark;
private int id;
private Vector3 sizeLWH;
private Vector3 positionXYZ;
private Vector3 rotationXYZ;
private Mesh elementMesh;
}
Here´s the XAML part.
<ListView ItemsSource="{Binding ListOfCreatedStacks}" HorizontalAlignment="Left" Margin="350, 200,0,0" Width="300">
<ListView.ItemTemplate>
<DataTemplate>
<TreeViewItem
Header="{Binding Id}"
IsExpanded="True">
<TreeViewItem
ItemsSource="{Binding ElementList}" Header="{Binding ElementMark}" IsExpanded="True">
</TreeViewItem>
</TreeViewItem>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've tried a solution from the other StackOverflow i got from Ed.
The result ends up with only adding a Hiearchy object insted of my "ImpactElement"-objects.
This is what the tested XAML looks like :
<ListView ItemsSource="{Binding ListOfCreatedStacks}" HorizontalAlignment="Left" Margin="350, 200,0,0" Width="300">
<ListView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type System:String}"
ItemsSource="{Binding ListOfCreatedStacks}">
<TreeViewItem
Header="{Binding Id}"
IsExpanded="True">
<HierarchicalDataTemplate DataType="{x:Type System:String}"
ItemsSource="{Binding ElementList}">
<TreeViewItem
Header="{Binding ElementMark}"
IsExpanded="True">
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeViewItem>
</HierarchicalDataTemplate>
</ListView.ItemTemplate>
</ListView>
Feels like a miss some easy part that i dont understand. Thanks for the help.
First, Controller, ElementList, and ImpactElement should probably be implementing INotifyPropertyChanged. You can find a million examples of that on Stack Overflow and elsewhere. It's simple, and you can get help here if you run into any snags.
What that will do is cause the UI to update if you change any bound property values on those objects while they're visible in the UI. The ObservableCollections will notify the UI if you add or remove items, but they can't notify the UI if some property of one of items happens to change.
Second, ImpactElement has only private fields. Since the private fields were omitted on the other classes, I'm guessing this was just an oversight when you copied the code. But you can only bind to public properties. Must be public, must be a property with a get and usually a set.
I'm hoping that you've assigned an instance of Controller to your DataContext. If you didn't, the tree won't populate.
I found WPF's TreeView baffling at first, and I wasn't new to XAML when I first looked at it. Do a bunch of simple examples (and come here for help if you need it), don't just paste in the XAML I gave you. It'll snap into focus.
Finally, you did a lot of things in your XAML that weren't done in the example. I don't know why you did those things, but you can't just go doing stuff at random. It doesn't work well. But as I said the learning curve on this one is tough, so no harm done.
So here's the XAML:
<TreeView
ItemsSource="{Binding ListOfCreatedStacks}"
HorizontalAlignment="Left"
Margin="350, 200,0,0"
Width="300">
<TreeView.ItemContainerStyle>
<Style
TargetType="TreeViewItem"
BasedOn="{StaticResource {x:Type TreeViewItem}}"
>
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:Stack}"
ItemsSource="{Binding ElementList}"
>
<Label Content="{Binding Id}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:ImpactElement}"
>
<Label Content="{Binding ElementMark}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So the TreeView's ItemsSource is ListOfCreatedStacks.
We give the TreeView an ItemContainerStyle which will apply to every TreeViewItem in the tree. We only do that so we can set IsExpanded. There's no other way to get to the TreeViewItems. We could also give the top-level item template an ItemContainerStyle of its own, which would only affect its children, if we wanted to do something different with those. But we don't need to.
And we give the TreeView an ItemTemplate. That is how we show a TreeViewItem how to display one of the TreeView's children. When you look at the HierarchicalDataTemplate, it's a bit like a TreeView: It's got an ItemsSource, which we bind to a property of Stack which contains the children of the Stack. It has an ItemTemplate for its own children. It could, as I said, have an ItemContainerStyle, but we'll just let it inherit the one we set on the TreeView.
It's recursive: A tree node is very much like a whole tree.
If you don't give a HierarchicalDataTemplate an ItemTemplate of its own, the TreeViewItems it applies to will just use that one as the template for their own children, and the children will do the same. If you gave ImpactElement a child collection of ImpactElements, the tree could be two dozen levels deep without making a single change in the XAML above.
Any DataTemplate or HierarchicalDataTempate can have a DataType property. That is the type of viewmodel item being displayed by the template. I don't know where you got System:String from.
In this particular case1, DataType serves no purpose but to help you keep things straight when you're editing the XAML: It reminds you which template at which level of recursion applies to which collection item, and he's Intellisense help you out as well.
Finally, we have the content of the HierarchicalDataTempate:
<Label Content="{Binding Id}" />
That could be any single XAML element, but it could be a Grid or a StackPanel containing a whole UI if you wanted. You can put anything in there. You could put a whole different TreeView in there.
Lastly, you should learn to do layout with StackPanel and Grid columns and rows. This margin business is a nightmare if you want to rearrange anything. I presume you're using the Design mode in VS? All I use that for is to preview what I've done by hand in the XAML. Properly done, in XAML layout everything's positioned relative to its parent and siblings. Maybe it has a fixed width and/or height, usually a small margin -- single digits -- just to create space between neighbors, not for absolute positioning. Once you get used to it, it's a really nice way to work with stuff. A lot like HTML.
1 For the other uses of the DataType property of a DataTemplate or HierarchicalDataTemplate, see "implicit datatemplates", and here's a more complicated example for doing it with treeview items. If you're already struggling to digest all this, don't go look at that stuff, just focus on the case I gave you above. That's more than enough. Data templates are a learning curve, and adding recursion just makes everything weirder.

move nested items from one listbox to another listbox in asp.net

I know how to move items from one listbox to another, but not when they are nested like this:
Can anyone tell me how can I nest them in a list box and move them from one listbox to another just like in the image above?
Thank you.
Items that are shown in a nested way are just like regular items, only the DataTemplate changes.
Say your item class contains 'title' (string) and 'members' (List(of String)), then your
dataTemplate will look like :
<DataTemplate TargetType={x:Type l:MyClass}>
<StackPanel>
<TextBox Text:{Binding title} />
<ItemsControl ItemsSource={Binding members} />
</StackPanel>
</DataTemplate>
So to switch a nested item between two Listbox, just... switch the item !

WPF implicit datatemplate with observablecollection

I'm new to WPF and using MVVM. I have a view in which I want to display different content according to what a user selects on a menu. One of those things is another user control Temp which has a view model (TempVM) so I am doing this:
<ContentControl Content="{Binding Path=TempVM}"/>
and TempVM (of type TempViewModel)is null until the user clicks a button. Its data template is this
<DataTemplate DataType="{x:Type vm:TempViewModel}">
<view:Temp />
</DataTemplate>
That's fine, but the other thing I want to do is show a listbox when a user clicks a different menu item. So I am trying to do
<ContentControl Content="{Binding Path=Missions}"/>
(Missions is an observable collection of MissionData) and trying to template it like this:
<DataTemplate DataType="{x:Type ObservableCollection(MissionData)}">
<StackPanel>
<ListBox ItemsSource="{Binding}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go"/>
</StackPanel>
</DataTemplate>
But the compiler doesn't like the type reference. If I try doing it by giving the template a key and specifying that key in the ContentControl it works but obviously I see the ListBox and button when there's no Missions. Obviously I could make a user control and viewmodel and follow the same pattern as I did for the TempVM but it seems over the top. Am I going the right way about this and what do I need to do?
From what i see is that you try to use a Collection as a dataobject which is in my opinion bad practice. Having a DataTemplate for a collection is also problematic, like you already have witnessed. I would advice you to use a ViewModel for your missions collection.
class MissionsSelectionViewModel
{
public ObservableCollection<Mission> Misssions;
public MissionData SelectedMission;
public ICommand MissionSelected;
}
and modify your datatemplate to
<DataTemplate DataType="{x:Type MissionsSelectionViewModel}">
<StackPanel>
<ListBox ItemsSource="{Binding Missions}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go" Command="{Binding MissionSelected}/>
</StackPanel>
</DataTemplate>
If I were to follow your pattern of implicit templates, I would derive a custom non-generic collection MissionDataCollection from ObservableCollection<MissionData> and use it to keep MissionData items. Then I would simply reference that collection in DataType. This solution gives other advantages like events aggregation over the collection that are useful.
However, it seems to me that the best solution is the following.
Add a IsMissionsListVisible property to your VM.
Bind the Visibility property of the ContentControl showing the list to the IsMissionsListVisible property.
Use a keyed DataTemplate resource.
Implement the logic that determines if IsMissionsListVisible. Supposedly it should be true when there is at least one mission in the selected item. But the logic may be more complex.
I would do it this way. In fact, I do it this way usually, and it gives several benefits. The most important is that I can explicitly control the logic of content visibility in various situations (e.g. async content refresh).

Why do I receive this message: "items collection must be empty before using itemssource" in a treeview?

I'm receiving this error: "items collection must be empty before using itemssource" in a treeview.
My XAML code just contains:
<TreeView ItemsSource="{Binding Groups}">
</TreeView>
Groups is a class where contains only two properties: GroupID and GroupName. I'm sure that my collection has items, but I'm not sure what's the reason for thix exception.
This error occurs when you have items added to the Items collection and use the ItemsSource, e.g.
<ListBox ItemsSource="{Binding Data}">
<ListBoxItem Content="A concrete item"/>
</ListBox>
You can of course create such a problem in code too. So make sure you did not add anything manually somewhere.
Because it can't get its items from two places. You have to pick one. Do you want your items to come from a datasource, or from a manual list?
I would bet you have items inside the tree view like this:
<TreeView ItemsSource="{Binding Groups}">
<!-- An Item being defined in the treeview -->
</TreeView>
Or you have code that does something like this
myTreeView.Items.Add(item)
Only one source of items can exist. You have to pick one.

Treeview - Hierarchical Data Template - Binding does not update on source change?

Greetings!
I ran into this problem in my project (Silverlight 3 with C#):
I have a TreeView which is data bound to, well, a tree.
This TreeView has a HierarchicalDataTamplate in a resource dictionary, that defines various controls. Now I want to hide (Visibility.Collapse) some items depending on wether a node has children or not. Other items shall be visible under the same condition.
It works like charm when I first bind the source tree to the TreeView, but when I change the source tree, the visibility in the treeview does not change.
XAML - page:
<controls:TreeView x:Name="SankeyTreeView"
ItemContainerStyle="{StaticResource expandedTreeViewItemStyle}"
ItemTemplate="{StaticResource SankeyTreeTemplate}">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="This is just for loading and will be replaced directly after the data becomes available..."/>
</DataTemplate>
</controls:TreeViewItem.HeaderTemplate>
</controls:TreeViewItem>
</controls:TreeView>
XAML - ResourceDictionary
<!-- Each node in the tree is structurally identical, hence only one Hierarchical
Data Template that'll use itself on the children. -->
<Data:HierarchicalDataTemplate x:Key="SankeyTreeTemplate"
ItemsSource="{Binding Children}">
<Grid Height="24">
<TextBlock x:Name="TextBlockName" Text="{Binding Path=Value.name, Mode=TwoWay}"
VerticalAlignment="Center" Foreground="Black"/>
<TextBox x:Name="TextBoxFlow" Text="{Binding Path=Value.flow, Mode=TwoWay}"
Grid.Column="1" Visibility="{Binding Children,
Converter={StaticResource BoxConverter},
ConverterParameter=\{box\}}"/>
<TextBlock x:Name="TextBlockThroughput" Text="{Binding Path=Value.throughput, Mode=TwoWay}"
Grid.Column="1" Visibility="{Binding Children,
Converter={StaticResource BoxConverter}, ConverterParameter=\{block\}}"/>
<Button x:Name="ButtonAddNode"/>
<Button x:Name="ButtonDeleteNode"/>
<Button x:Name="ButtonEditNode"/>
</Grid>
</Data:HierarchicalDataTemplate>
Now, as you can see, the TextBoxFlow and the TextBlockThroughput share the same space.
What I aim at: The "Throughput" value of a node is how much of something 'flows' through this node from its children. It can't be changed directly, so I want to display a text block. Only leaf nodes have a TextBox to let someone enter the 'flow' that is generated in this leaf node. (I.E.: Node.Throughput = Node.Flow + Sum(Children.Throughput), where Node.Flow = 0 for each non-leaf.)
What the BoxConverter (silly name -.-) does:
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if ((value as NodeList<TreeItem>).Count > 1) // Node has Children?
{
if ((parameter as String) == "{box}")
{
return Visibility.Collapsed;
}
else ((parameter as String) == "{block}")
{
return Visibility.Visible;
}
}
else
{
/*
* As above, just with Collapsed and Visible switched
*/
}
}
The structure of the tree that is bound to the TreeView is essentially stolen from Dan Vanderboom (a bit too much to dump the whole code here), except that I here of course use an ObservableCollection for the children and the value items implement INotifyPropertyChanged.
I would be very grateful if someone could explain to me, why inserting items into the underlying tree does not update the visibility for box and block.
Thank you in advance!
What is happening is that Converter is called whenever the property is changed.
However adding items to a collection doesn't constitute changing the property. It is still the same collection after all. What you need to do is for the ViewModel to NotifyPropertyChanged when the collection changes. That'll cause the converter to re-evaluate the collection.

Categories