WPF TreeView does not apply DataTemplate accordingly - c#

I have a business object project, which contains composite structure:
public class Tree
{ public IProductComponent TreeRoot { get; set; } }
public interface ITreeComponent
{ public string Name { get; set; } }
public class ContainerComponent : ITreeComponent
{ public BindingList<ITreeComponent> Children { get; set; } }
public class LeafComponent : ITreeComponent
{ }
I need to bind this structure to a TreeView in my WPF project. The tree view first:
<TreeView x:Name="treeView" Grid.ColumnSpan="2">
<TreeView.Resources>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}"
DataType="{x:Type businessObjects:ContainerComponent}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type businessObjects:LeafComponent}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And the code for binding:
bTreeView = new Binding();
bTreeView.Source = MyTree;
bTreeView.Path = new PropertyPath("TreeRoot.Children");
treeView.SetBinding(TreeView.ItemsSourceProperty, bTreeView);
The problem is that the TReeView does not actually use those templates (it displays only the top level of hierarchy and calls .ToString() to display those items. Please tell me where have I gone wrong. Otherwise, if I set the it is working, but I cannot define two templates there.
Thanks.

Well I notice you are putting the template in resources, not under TreeVeiw.ItemTemplate.
TreeView should have an ItemTemplate (the Hierarchical) and the ItemsSource set. Shouldn't need anything more than that.
Would help with example data for us to test though.

My bad - the Main assembly was loading the dll with entities two times instead of one. That caused it to go crazy - as soon as I fixed it and the assembly loaded once the problems went away.

Related

Q: How should I handle Datatemplates for Models?

In the MVVM pattern, the view shouldn't know anything about the models, but what if I wanna display different types differently?
For example I have two classes. The class Message and the class AttachmentMessage which inherits from Message.
Message
public class Message
{
public string Content { get; set; }
}
AttachmentMessage
public class AttachmentMessage : Message
{
public string Filename { get; set; }
}
Now when I use them in an ObservableCollection<Message>, I have both models in this collection, but I can't tell WPF which Datatemplate it has to use, without knowing which Models there are.
So what are solutions for this problem?
The most common and recommended way would be to create a data template for each type you need and put that in your resources.
The following code assumes your observable collection has the name Messages.
Example:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:Message}">
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:AttachmentMessage}">
<TextBlock Text="{Binding Filename}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
The other way is to create a DataTemplateSelector. Let's say your messages all had a property that indicated their priority. You could create a template selector like the below. DataTemplateSelector can be used when you need more fine-grained control over which template is selected.
public class MyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is Message m && container is FrameworkElement fm)
{
if (m.Priority == Priority.High)
{
return fm.FindResource("HighPriorityTemplate") as DataTemplate;
}
else
{
return fm.FindResource("NormalPriorityTemplate") as DataTemplate;
}
}
return null;
}
}
And use it in xaml like the following:
<Window.Resources>
<!-- Put your templates here-->
<local:MyDataTemplateSelector x:Key="MyDataTemplateSelector"/>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Messages}" ItemTemplateSelector="{StaticResource MyDataTemplateSelector}">
As I side note, in the MVVM pattern, usually you have three parts, Model, View and ViewModel. Some people take the shortcut of binding directly to the Model, but I generally would avoid this. You can find a discussion about this here.

WPF databinding to a TreeView from Code behind

so I have a treeView which works fine. But my problem is that I want to display different trees, of different Types without having to create one for every scenario in XAML. I know how to set the content of a listView from code behind, is the same possible for a treeView? My treeView right now looks like the following but obviously only works for Items of the Type CAN-Message:
<TreeView TreeViewItem.Selected="OnItemSelected" MouseDoubleClick="Tree_MouseDoubleClick" Name="tree">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type srcM:CANMessage}" ItemsSource="{Binding Signals}">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Name}"/>
<TextBlock Text=" (0x"/>
<TextBlock Text="{Binding CANid}"/>
<TextBlock Text=")"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The thing is I have different scenarios. One for example where I only want to display the Signals List of the class ADTF (shown below) which is of the Type string.
And in another case I want to display the CANMessages list of the CAN Class and for each Message Element I want to set the signal list it contains as subelements (Which is implemented in the XAML example). I have a short version of said classes following:
public class ADTF : ISignalSource
{
public string Id { get; set; }
public List<string> Signals { get; set; }
}
and
public class CAN: ISignalSource
{
public string Id { get; set; }
public List<CANMessage> Signals { get; set; }
}
public class CANMessage
{
public string Id { get; set; }
public List<string> Signals { get; set; }
}
So what I think I need to do is create a HierarchicalDataTemplate for every scenario. But I want to do it in code behind because it seems to take less code than implementing a HierarchicalDataTemplate in XAML for every scenario. Is there a way to o this?
If i understood correctly, You can use Interface to get this working.
Similar question: wpf Treeview with three levels in

Dynamically grouping/nesting flat data in a TreeView

I would like to take a flat list of objects and present them in a TreeView using custom groups.
public enum DocumentType { Current, Inactive, Transition, Checkpack, TechLog, Delivery }
public enum Status { Approved, Rejected, Pending }
public class Document
{
public string Name { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime Modified { get; set; }
public string ModifiedBy { get; set; }
public DocumentType Type { get; set; }
public Status Status { get; set; }
}
For example... The user might want to see this list, with the top level group being "Status" and the second level being "Name". This all needs to be configurable from the UI, and I'm struggling to find the best way to achieve it.
I've had a brief look at the CollectionViewSource object, but couldn't find a good way to get it to dynamically build a TreeView.
My gut feeling is that i'll need to do some clever templating in XAML - this is as far as i've got...
<Window.Resources>
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding Name}" />
</DockPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="GroupTemplate"
ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource DocumentTemplate}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Documents.View.Groups}"
ItemTemplate="{StaticResource GroupTemplate}"/>
</Grid>
public CollectionViewSource Documents
{
get
{
var docs = new CollectionViewSource();
docs.Source = DocumentFactory.Documents;
docs.GroupDescriptions.Add(new PropertyGroupDescription("CreatedBy"));
return docs;
}
}
Of course this only displays the Top-level group ("CreatedBy").
After reading a question below, I managed to come up with a better question...
My question: Is it possible to have a generic HierarchicalDataTemplate for a TreeView that displays custom groups applied to a CollectionViewSource.
Honestly this should be marked as a bug in WPF. I too tried and found that Documents.View.Groups throws binding error on View property being null.
Also
<TextBlock Text="{Binding Path=Name}" />
is correct in the GroupTemplate but not in the DocumentTemplate. Note that Groups are of special type GroupItem where Name is one such property that holds the value on which grouping has taken place.
On the other hand in DocumentTemplate, we should refer the property that we need to display on the leaf nodes items e.g. in my example I used Employee.FirstName (I grouped on Gender).
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding FirstName}" />
</DockPanel>
</DataTemplate>
Now for binding to take effect I had to introduce a converter which simply returns Groups.
public class GroupsConverter : IValueConverter
{
public object Convert(object value, ...)
{
return ((CollectionViewSource)value).View.Groups;
}
....
}
And tree view binding was changed this way...
<TreeView x:Name="treeView"
ItemsSource="{Binding Path=Documents,
Converter={StaticResource GroupsConverter}}"
ItemTemplate="{StaticResource GroupTemplate}" />
Then this worked for me.
Does this help you?

WPF treeview datatemplate

Let's say I have something like this:
public class TopicFolder
{
#region Constants and Fields
private readonly List<TopicInfo> folderContent;
private readonly List<TopicFolder> subFolders;
#endregion
...
}
How do I implement a data template for such type? Currently I have:
<HierarchicalDataTemplate DataType="{x:Type local:TopicFolder}" ItemsSource="{Binding SubFolders}" >
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TopicInfo}" ItemsSource="{Binding FolderContent}">
<TextBlock Text="{Binding TopicName}"/>
</HierarchicalDataTemplate>
But this does not show any folder content. It seems that second's template DataType should be local:TopicFolder, but this is not allowed by WPF.
Any suggestions?
UPD : TreeView is bound to ObservableCollection<TopicFolder> this way:
ItemsSource="{Binding Path=Folders}"
P.S: It is definitely not a private/public/properties problem. I have corresponding public properties for posted fields. No binding errors in output, it is just not showing any FolderContent items.
Edit:
To show both sub-folders and content one can either use a MultiBinding or if you don't mind that folders and content can appear in a certain order I'd suggest using the composite pattern, for that you remove your SubFolders and FolderContent and replace it with a collection of objects which implement the composite interface (read the wiki article).
Creating a property to merge the two collections, so you can bind to it, is bad practice.
Example for composite pattern:
public interface ITopicComposite
{
// <Methods and properties folder and content have in common (e.g. a title)>
// They should be meaningful so you can just pick a child
// out of a folder and for example use a method without the
// need to check if it's another folder or some content.
}
public class TopicFolder : ITopicComposite
{
private readonly ObservableCollection<ITopicComposite> children = new ObservableCollection<ITopicComposite>();
public ObservableCollection<ITopicComposite> Children
{
get { return children; }
}
//...
}
public class TopicInfo : ITopicComposite
{
//...
}

treeview Multibinding in wpf

I want to bind a treeview to a class like this one:
public class Folder : Base_FileFolder
{
public Folder()
{
Folders = new ObservableCollection<Folder>();
Files = new ObservableCollection<File>();
}
public ObservableCollection<Folder> Folders { get; set; }
public ObservableCollection<File> Files { get; set; }
}
the other classes ares:
public class File : Base_FileFolder
{
}
public class Base_FileFolder : DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Base_FileFolder), new UIPropertyMetadata(""));
}
How can I create a treeview that shows Files and Folders collection
I want to use something like this:
<HierarchicalDataTemplate
DataType="{x:Type model:Folder}"
ItemsSource="{Binding Childs}">
<DockPanel>
<Label Content="{Binding Name}"/> </DockPanel>
</HierarchicalDataTemplate>
so I get Somethign like this:
rootFolder
|
|-File
|-File
|-Folder
|-File
|-File
|-Folder
|-File
What exactly is your question? How to combine them? CompositeCollection.
EDIT: as mentioned in the comments, my Intuipic application does something very similar to what you're requesting. Here's a screenshot:
This is quite easy, considering your constellation.
First: Adjust your classes. You do not need two separate Lists for files and folders in the folders class. Just use one IList<Base_FileFolder> inside the Base_FileFolder class (good OOP) and call it Children!
Then you'll need only two more steps:
Two HierarchicalDataTemplates
<HierarchicalDataTemplate DataType="{x:Type FolderNode}" ItemsSource="{Binding Path=Children}">
<Grid>
<TextBlock Text="{Binding FolderName}" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type FileNode}" ItemsSource="{Binding Path=Children}">
<Grid>
<TextBlock Text="{Binding FileName}" />
</Grid>
</HierarchicalDataTemplate>
And a TreeView like this
<TreeView Name="TreeViewFileTree" ItemsSource="{rootFolder.Children}" />
That's it. WPF's strength is its simplicity.
You need to use
You'll need 3 things:
a HierarchicalDataTemplate, like you have, to do parent+children, and template the folders. you MIGHT be able to use a CompositeCollection here to merge the folders+files, but i'm not sure about that...you might have to add another property to your folder class that returns the union of files and folders and call it "Children" or whatever...
A DataTemplate to template files in the tree
A TemplateSelector to tell the tree to switch between templates depending on the item in the tree. Instead of setting an ItemTemplate on the tree, set the ItemTemplateSelector to this.

Categories

Resources