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.
Related
Okay, to start, I'm pretty inexperienced with WPF and XAML, so any pointers or advice would be greatly appreciated.
I have a scheduling program that I'm working on that I need some help setting up. I had things working previously, but it wasn't organized correctly. I had UI elements in my ViewModels that I would add to a StackPanel at the initialization of the MainWindow. Generally not MVVM style coding. So I made some views (UserControls) to display the things I have, and most everything broke.
Basically, I have a Schedule ViewModel that has some parameters and a list of a different Room ViewModels. Each Room ViewModel has a RoomSchedule ViewModel that contains a list of RoomEvent ViewModels.
I'm trying to write controls for the things that need displaying. I've created a Schedule view, which has a list box of Room views, and the Room view uses the RoomEvent view to display the events of the room. The Room view uses the WPF Extended Toolkit's TimelinePanel, the rest of the controls are pretty much basic controls. The general idea has been: a model provides data to the ViewModel, which massages that data to what needs to be displayed. So an Event should know how to display itself, a Room should know how to display itself, and the Schedule should know how to display itself.
The problem I'm running into is: now that I've scooted everything from the xaml.cs or ViewModel files to their appropriate places, the controls aren't rendering at all. I've been reading other SO postings where people have the same problem, but none of them seem to work for beginner stuff like this. I think I'm close, it seems like all the controls are being created, and the DataContext's are being set correctly, but nothing is showing up.
This is, basically, what I have so far. I left some of the xaml boilerplate stuff off for succinctness:
Schedule.xaml:
<StackPanel>
<ListBox ItemsSource="{Binding Rooms}" >
<ListBox.ItemTemplate>
<DataTemplate>
<localcontrols:RoomView ScheduleStart="{Binding ElementName=ScheduleControl, Path=DataContext.Start}"
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
</StackPanel>
RoomView.xaml:
<extended:TimelinePanel BeginDate="{Binding localcontrols:ScheduleStart}" EndDate="{Binding localcontrols:ScheduleEnd}"
<ItemsControl ItemsSource="{Binding Path=mRoomSchedule.mScheduledEvents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<localcontrols:EventView />
</DataTemplate>
</ItemsControls.ItemTemplate>
</ItemsControl>
</extended:TimelinePanel>
EventView.xaml:
<Border BorderThickness="1" BorderBrush="Black" extended:TimelinePanel.Date="{Binding mStartTime}" extended:TimelinePanel.DateEnd="{Binding mEndTime}">
<TextBlock Background="{Binding mColor}" Text="{Binding mEventID}" />
</Border>
The ScheduleStart and ScheduleEnd are dependency properties defined in RoomView.xaml.cs. My thinking was that Schedule would have Start and End properties that would be set in its constructor, and the RoomViews in the ListBox would bind to those properties to set the TimelinePanel's BeginDate and EndDate.
Maybe your bindings are wrong. When I need to bind to a dependency property I use the ElementName feature of binding to say which control I want and I give the root node a name, in this case Root. It's one way to solve it.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Weingartner.Controls"
x:Class="RoomView"
x:Name="Root">
<extended:TimelinePanel
BeginDate="{Binding ElementName=Root, Path=ScheduleStart}"
EndDate="{Binding ElementName=Root, Path=ScheduleEnd}"
>
<ItemsControl ItemsSource="{Binding Path=mRoomSchedule.mScheduledEvents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<localcontrols:EventView />
</DataTemplate>
</ItemsControls.ItemTemplate>
</ItemsControl>
</extended:TimelinePanel>
</UserControl>
Hi this should be faily simple, however I don't know what I am doing wrong. I've been looking all over the internet seeing people make this work, even followed the tutorial on MSDN still nothing has worked for me.
I want to Iterate over a ListBox, and get the ListBoxItems so I can find the DataTemplate that I have added to it.
This is my code behind.
private void SetListBoxDataTemplate(ListBox MyListBox)
{
try
{
foreach (CustomDataTemplateObject dataobject in MyListBox.Items)
{
ListBoxItem lbi = (ListBoxItem)(MyListBox.ItemContainerGenerator.ContainerFromItem(dataobject));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(lbi);
DataTemplate dt = myContentPresenter.ContentTemplate;
TextBlock tb = (TextBlock)dt.FindName("ListBoxItemTextBlock1", myContentPresenter);
ComboBox cb = (ComboBox)dt.FindName("ListBoxItemComboBox1", myContentPresenter);
tb.Text = dataobject.Text;
cb.ItemsSource = dataobject.ListColors;
}
}
catch (Exception ex)
{
MessageBox.Show(""+ex);
}
}
XAML looks like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Name="ListBoxItemTextBlock1" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox Name="ListBoxItemComboBox1" />
</StackPanel>
</DataTemplate>*
<StackPanel>
<ListBox Name="ListBoxTest1" ItemTemplate="{DynamicResource ListBoxItemDataTemplate1}" />
</StackPanel>
I have tried with setting my itemtemplate to static to see if it works, and the method i'm calling from code behind, is called after I have populated my ListBoxs
My dataobject is NOT null, however when i call the line in my code behind, my lbi, ends up being null.
Any suggestions? thanks in advance!
FIRST UPDATE
This problem only occurs if i call the method in my constructor, so perhaps it's because it hasn't initialized the full group element section yet. However I want to do this as soon as possible. Am I perhaps forced to do it in a WindowLoaded event?
SECOND UPDATE
Code updated, Rachel's answer worked for iterating over my ListBoxItems, however the Listbox Has not fully rendered since i'm unable to reach the Datatemplate at this time. So MyListBox_GeneratorStatusChanged is not working for this problem, but it does get the ListBoxItems.
WPF's main thread runs items at different priority levels. Code that runs in the Constructor all gets run at Normal priority, while things like rendering the ListBox and it's items run at the Render priority level, which occurs after all Normal priority operations have finished.
This means that your entire Constructor gets run (including SetListBoxDataTemplate()) before your ListBox is even rendered and the items get generated.
If you want to run some code after the items are generated, use the ItemsContainerGenerator.StatusChanged event
// Constructor
MyListBox.ItemContainerGenerator.StatusChanged += MyListBox_GeneratorStatusChanged;
...
void MyListBox_GeneratorStatusChanged(object sender, EventArgs e)
{
// return if containers have not been generated yet
if (MyListBox.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
// remove event
MyListBox.ItemContainerGenerator.StatusChanged -= MyListBox_GeneratorStatusChanged;
// your items are now generated
SetListBoxDataTemplate(MyListBox);
}
What are you trying to accomplish with this method anyways? It is a bit unusual for WPF, and there may be a much better WPF way of accomplishing your task.
Updated based on new code added to Question
A much better method of setting your Text and ItemsSource properties is to make use of WPF's data bindings.
Your DataTemplate should look like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Text="{Binding Text}" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox ItemsSource="{Binding ListColors}" />
</StackPanel>
</DataTemplate>*
A DataTemplate is like a cookie cutter. It's used to make the UI objects, but is not part of the UI object itself. All it does is tell WPF that "When you go to render this object, render it using this XAML". So the way your XAML gets rendered is
<ListBoxItem>
<StackPanel>
<Border>
<TextBlock Text="{Binding Text}" />
</Border>
<ComboBox ItemsSource="{Binding ListColors}">
</StackPanel>
</ListBoxItem>
In addition, the DataContext behind your ListBoxItem is the item from the collection bound to ListBox.ItemsSource, which based on your code should be CustomDataTemplateObject. That allows the bindings from the DataTemplate to work
If you're new to WPF and struggling to understand how exact the DataContext works, I'd recommend reading this article of mine: What is this "DataContext" you speak of?.
To summarize, WPF has two layers to an application: the UI layer and the Data Layer (DataContext). When you perform a basic binding like above, you are pulling data from the data layer into the UI layer.
So your ListBoxItem has a data layer of CustomDataTemplateObject, and the TextBlock.Text and ComboBox.ItemsSource bindings are pulling data from the data layer for use in the UI layer.
I'd also highly recommend using a utility like Snoop which lets you view the entire Visual Tree of a running WPF application to see how items get rendered. Its very useful for debugging or learning more about how WPF works.
You're confusing two jobs and mixing them into one. First, get access to the ListBoxItem:
private void SetListBoxDataTemplate(ListBox MyListBox)
{
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
}
}
Now you can get the DataTemplate from the ListBoxItem:
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
ContentPresenter presenter = FindVisualChild<ContentPresenter>(listBoxItem);
DataTemplate dataTemplate = presenter.ContentTemplate;
if (dataTemplate != null)
{
// Do something with dataTemplate here
}
}
The FindVisualChild method can be found in the How to: Find DataTemplate-Generated Elements page on MSDN.
UPDATE >>>
To answer your edit, yes, the constructor will be too early to try to access these DataTemplates because the Framework won't have applied them to all of the objects by then. It is best to use the FrameworkElement.Loaded Event to do these kinds of things, as that is the first event that can be called after the controls have all been initialised.
I need to display hierarchical data like:
public class Element
{
public string Name { get; private set; }
public Element[] Elements { get; private set; }
}
It would be just vertical panel with rectangle (with Name) for each element. If element is clicked, its child elements are displayed below it (element is expanded). If one of them is clicked, its elements appear and so on.
I already googled this and found out that there is no HierarchicalDataTemplate and no treeview in WinRT.
So I started to do it by myself.
I created ItemsControl and DataTemplate DataTemplate1 for it. In DataTemplate1 I also create ItemsControl and set DataTemplate2 as ItemTemplate. In DataTemplate2, ItemTemplate is DataTemplate3 and so on. The last DataTemplate is without ItemsControl.
In buttons Click event I change Elements IsVisible property for any elements in DataModel (that is Element[]), so it is easy to perform any custom logic to expand/collapse elements.
<DataTemplate x:Key="DataTemplate2">
<StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Style="{StaticResource ItemButtonStyle}"
Click="MenuElement_Click">
<TextBlock Style="{StaticResource ItemTextBlockStyle}" Text="{Binding Name}"/>
</Button>
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource DataTemplate3}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DataTemplate1">
<StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Style="{StaticResource ItemButtonStyle}"
Click="MenuElement_Click">
<TextBlock Style="{StaticResource ItemTextBlockStyle}" Text="{Binding Name}"/>
</Button>
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource DataTemplate2}"/>
</StackPanel>
</DataTemplate>
It works fine, but the problem is that if I want to enable 10 levels of hierarchy, I have to copypast 10 datatemplates. And 11 level still will not be available.
I also tried to create DataTemplate in C# and manually apply DataTemplate for its ItemSource and so on, in recursive method.
But I found 2 problems.
I don't know actually how to create DataTemplate in metro (C#), because it has no VisualTree property. I can only make (var dt= new Datatemplate();) and I don't know how to change it.
If I read DataTemplate from XAML (var dateTemplateRoot = (DataTemplate)this.Resources["DataTemplate1"];)
I still can't find ItemsControl in it and change its DataTemplate.
Actually, I can use var content = dateTemplateRoot.LoadContent(); and then find ItemsControl by VisualTreeHelper, but I can't use content after that as DataTemplate (content has type DependencyObject).
So, actually I have 2 questions.
Is it a good approach to perform hierarchical dropdown list by "binding" all items and only switch Visibility property?
The second is - how to enable unlimited level of hierarchical nesting?
WinRT XAML Toolkit has a TreeView control now. Check it out: http://winrtxamltoolkit.codeplex.com/SourceControl/changeset/view/b0ee76bd6492#WinRTXamlToolkit/Controls/TreeView/TreeView.cs
Take care though - this is just a rough port from Silverlight Toolkit and might not work so well. Also if you are planning on releasing it as part of a Windows Store application - you would need to heavily restyle it unless your app is desktop-only since it is not very touch-friendly.
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).
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}"
... />