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>
Related
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
Xaml code for listview is some thing like this. I populate Id,name and email in each item of the listview.Listview name is resultview.There is a textbox to search.
<TextBox Text="{Binding SearchText, Mode=TwoWay,Source=PropertyChanged}"/>
<ListView Name="ResultsView" IsItemClickEnabled="True" Margin="0,65,4,0" SelectionChanged="ResultsView_SelectionChanged_1" ItemsSource="{Binding contacts}" Background="White" ItemClick="ResultsView_ItemClick" Loaded="ResultsView_Loaded">
<ListView.ItemTemplate>
<TextBlock x:Name="st1" Text="{Binding id}" FontSize="28" Grid.Row="0" Foreground="Black"/>
<TextBlock x:Name="st2" Text="{Binding name}" FontSize="22" Grid.Row="1" Foreground="Black"/>
<TextBlock x:Name="st3" Text="{Binding email}" FontSize="22" Grid.Row="2" Foreground="Black"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is the class
public class Phone
{
public string mobile { get; set; }
public string home { get; set; }
public string office { get; set; }
}
public class Contact
{
public string id { get; set; }
public string name { get; set; }
public string email { get; set; }
public string address { get; set; }
public string gender { get; set; }
public Phone phone { get; set; }
}
public class RootObject
{
public ObservableCollection<Contact> contacts { get; set; }
}
The data context for Resultview is set as followed in my application.
var _Data = JsonConvert.DeserializeObject<RootObject>(jsonString);
ResultsView.DataContext = _Data;
Now the thing i need is when the text in the textbox changes listview items should filter according to the name.
As you told i added this part. bt it shows errors.errors are in comment lines
public string SearchText
{
get
{
return SearchText;
}
set
{
if(SearchText == value)
return;
SearchText = value;
Contacts.Clear();//Here it says contacts doesnt exists in the current context.
foreach(var item in ResultsView.where(contact => contact.Name.Contains(SearchText)))//it shows windows.UI.XAML.Listview does not contaain definion for where
{
Contacts.Add(item);
}
}
}
I am getting those two errors.And i am workin on windows store app not on windows phone.this is all what i have done. if it doesnot work out please provide me an alternate solution.
-Thanks
Here is what you need:
A ViewModel (you should be using Mvvm)
A CollectionViewSource
A method of selecting what to filter by
A method of filtering your data
Create a ViewModel. This is where your Contacts are. Create an ObservableCollection<Contact> Contacts. At initialization, fill this ObservableCollection as you would like. Save off a copy of the base list as _baseContactList;
In your View, create a CollectionViewSource as a StaticResource in your Page.Resources. Bind its contents to your Collection.
Set your ItemsSource of your List to the CVS.
Now, you need to create a method of determining what to sort. You may do something like bind a ComboBox to a string property in your ViewModel. The setter of that property will change the values of your backing ObservableCollection similar to this:
public string SearchText
{
get
{
return _searchText;
}
set
{
if(_searchText == value)
return;
_searchText = value;
Contacts.Clear();
foreach(var item in _baseContactList.Where(contact =>
contact.Name.Contains(_searchText))
{
Contacts.Add(item);
}
}
Now, this is pretty horribly inefficient and may not be idea for you. Firstly, it clears the search after every change. You'll probably want to do something like throttling it with ReactiveExtensions so that it only refills half a second after the last keystroke. Secondly, you'll likely want to replace the condition (contact.Name.Contains(_searchText)) with your custom criteria, possibly including checking the email as well. You may also want to remove case sensitivity (by comparing the ".ToLower()" version of Name and _searchText, or whichever two you're comparing).
So, in order to make this happen, you'll need a TextBox which connects its current string to SearchText. Something like this:
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Binding statement targets the SearchText property, makes sure that any changes to the TextBox gets reflected in SearchText (via Mode=TwoWay) and then tells it to send the change every time the text changes (at each keystroke) by setting the UpdateSourceTrigger.
Some future work you may want to add would be to have the clearing and refilling be both throttled and asynchronous, along with using an IncrementallyLoadingObservableCollection. Using the incremental loader will actually make it so that you don't necessarily need to throttle as much, as it should only load about 5 at a time. You can look up guides for making one.
That should be it (aside from styling each item). Hope this gets you on your way!
Quick description of my setup:
Disclaimer: The code is only to show what I want to do. The command binding for instance is done with event triggers etc. I'm pretty sure this wouldn't even build, but I didn't want to waste space.
My View:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding IsFavorite, Converter={StaticResource FavoriteConverter}" Tap="{Binding FavoriteCommand, CommandParameter={Binding}}"/>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My ViewModel:
public class ViewModel : ViewModelBase
{
public IList<Item> Items { get; set; }
public RelayCommand<Item> Favorite
{
get
{
return new RelayCommand<Item>(item =>
{
item.IsFavorite = !item.IsFavorite;
RaisePropertyChanged(string.Empty);
};
}
}
}
My Model:
public class Item
{
public string Title { get; set; }
public bool IsFavorite { get; set; }
}
*My Question:*
How can I get the IsFavorite-Property to update without implementing INotifyPropertyChanged? It's a model class, and I wouldn't like creating a ViewModel for the sole purpose of updating one property. I thought that calling PropertyChanged with an empty string would update everything, but alas it didn't.
*My Solution:*
public class ItemViewModel : ViewModelBase
{
public Item Model { get; private set; }
public bool IsFavorite
{
get { return Model.IsFavorite; }
set
{
Model.IsFavorite = value;
RaisePropertyChanged("IsFavorite");
}
}
}
If you are binding to the value and expect it to be changing at runtime, then you should really implement INotifyPropertyChanged since WPF's binding system uses that interface to know when it needs to update.
But with that said, you might be able to get away with raising a PropertyChange notification for the entire Items collection, although I'm not entirely sure that would work because the actual collection itself hasn't changed, only a property of one of the items inside the collection. And WPF usually knows not to bother re-evaluating a property if it doesn't actually change.
RaisePropertyChanged("Items");
If it doesn't work, you could probably remove the item and re-add it to trigger a CollectionChange notification, however that also might not work and may cause other problems depending on your application design.
// I may have the exact syntax wrong here
var index = Items.IndexOf(item);
var tmp = Items[index];
Items.Remove(tmp);
Items.Add(tmp, index);
But it would really just be best to implement INotifyPropertyChanged on your Item class.
Try to use: RaisePropertyChanged("Items"); and make Items collection ObservableCollection.
Implementing INotifyPropertyChanged is better.
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
{
//...
}
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.