WPF treeview datatemplate - c#

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
{
//...
}

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 Treeview and databinding a Directory Tree

I'm trying to implement a Directory Tree View that also shows all the files in my MVVM project. My Folder and Files structure in the Model is like this:
public class UserDirectory
{
private ObservableCollection<UserFile> files;
private ObservableCollection<UserDirectory> subfolders;
private String directoryPath;
//public getters and setters...
}
public class UserFile
{
private String filePath;
private Category category; //Archive, Document, Exe, etc...
//public getters and setters
}
I'd like to show them in a TreeView, but after reading this very helpful Josh Smith article, and various other sources, I still don't know how to work it out with HierarchicalDataTemplate.
Possible solution
I've figured out that maybe I have to create a specific type, like Item, that exists only for showing the name of the files and the directories,
public class Item
{
private List<String> directories;
private List<String> files;
}
but I'd like to reuse my class structure, because I need to show the Category data of the UserFile, for example.
The question
How can I show the files and the subfolders while maintening my current Data Structure?
This is an example of what I want to reach (I'm sorry but image uploading isn't working)
XAML
<TreeView
ItemsSource="{Binding RootDirectoryItems}"
>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:UserDirectory}" ItemsSource="{Binding Items}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:UserFile}">
<Label Content="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
RootDirectoryItems is presumed to be a property of the viewmodel, something like this:
public ObservableCollection<Object> RootDirectoryItems { get; }
= new ObservableCollection<object>();
In the C#, assume the presence of INotifyPropertyChanged boilerplate on all property setters. I added two properties to UserDirectory:
Name, a readonly property which returns just the name segment of DirectoryPath. If DirectoryPath may change at runtime, its setter should call OnPropertyChanged("Name");, so that bindings looking at the Name property will know they need to get the new value. UserFile gained a similar Name property, which comes with the same advice about raising PropertyChanged if that's a possibility.
Items: Again, a readonly property, and you should raise PropertyChanged appropriately if either of the constituent collections changes (handle ICollectionChanged.CollectionChanged, and do likewise in the setters if you have setters). Bindings don't care about the declared type of a property, so it just returns System.Collections.IEnumerable -- it could even return object, and the XAML wouldn't care. But let's be just specific enough, without being so specific as to encourage anybody in C# to try to use the property for anything.
If it were me, I'd almost certainly make UserDirectory and UserFile bare immutable "POCO" classes without INotifyPropertyChanged, and simply repopulate if anything changed on the disk. I might depart from immutability by giving UserDirectory a FileWatcher and having it repopulate itself, if I had some reason to expect directories to change a lot.
So here's the C#:
public class UserDirectory
{
public ObservableCollection<UserFile> Files { get; set; } = new ObservableCollection<UserFile>();
public ObservableCollection<UserDirectory> Subfolders { get; set; } = new ObservableCollection<UserDirectory>();
// Concat demands a non-null argument
public IEnumerable Items { get { return Subfolders?.Cast<Object>().Concat(Files); } }
public String DirectoryPath { get; set; }
public String Name { get { return System.IO.Path.GetFileName(DirectoryPath); } }
}
public class UserFile
{
public String FilePath { get; set; }
public Category Category { get; set; }
public String Name { get { return System.IO.Path.GetFileName(FilePath); } }
}
Your Item class isn't needed, because XAML works by "duck typing".
Here's a simpler variant that also works, because both UserDirectory and UserFile have a Name property, and UserFile's missing Items property is quietly shrugged off.
<TreeView
ItemsSource="{Binding RootDirectoryItems}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Can/Should I create instances of a UserControl in my viewmodel?

Question regarding UserControls and MVVM. I have my wpf app with a main View/ViewModel. The viewmodel has a ObservableCollection of my usercontrol(s) that a listbox is bound to. The UserControl instances are added to the collection at run time based on events.
My question is if it's within the MVVM pattern to create the usercontrol objects from my main viewmodel? Example below in the onSomeEvent method is the code I'm unsure of, if this is where I should handle it? This doesn't feel right to me, but I'm still wrapping my mind around mvvm. Should I be adding the user control viewmodel here instead? Thanks for any guidance.
private ObservableCollection<string> myList = new ObservableCollection<string>();
public ObservableCollection<string> MyList
{
get { return myList; }
set
{
myList = value;
RaisePropertyChangedEvent("MyList");
}
}
public void onSomeEvent(string someData1, string someData2)
{
this.MyList.Add(new Views.MyUserControl(someData1, someData2));
}
Ok, I mocked up some code based on feedback from BradleyDotNET and dymanoid as I wrap my mind around it. Pasting it here to see if I'm on the right track.
I modified the listbox in my mainview xaml to add a template:
<ListBox Name="lbMain" Margin="10" ItemsSource="{Binding MyList, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Test1}" FontWeight="Bold" />
<TextBlock Text="{Binding Test2}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I then created a simple class like this to populate a few fields.
public class MyData
{
public MyData(string test1, string test2)
{
this.Test1 = test1;
this.Test2 = test2;
}
private string test1;
public string Test1
{
get
{
return test1;
}
set
{
test1 = value;
}
}
private string test2;
public string Test2
{
get
{
return test2;
}
set
{
test2 = value;
}
}
}
Then in my mainviewmodel I did this:
public void onSomeEvent(string someData1, string someData2)
{
this.MyList.Add(new MyData(someData1, someData2));
}
No, your viewmodel should not create any UserControl instances, because they are views. Furthermore, your main viewmodel shouldn't contain any collections of any views. As #BradleyDotNET mentioned, DataTemplate is the right way for it.
You should change your main viewmodel collection. It shouldn't contain any UserControls (views), but rather their viewmodels. Assuming that you have defined DataTemplates for your sub-viewmodels in XAML, you will get your views automagically created by WPF.
This could look like:
<DataTemplate DataType = "{x:Type local:UserControlViewModel}">
<local:UserControl/>
</DataTemplate>
With this approach, WPF sets the DataContext property value to the sub-viewmodel instance automatically, so you can easily define your bindings in that UserControl.

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.

WPF TreeView does not apply DataTemplate accordingly

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.

Categories

Resources