Problem with TreeView in SilverLight - c#

I have the following class structure:
class Organization
{
string Name;
List<User> users;
List<Organization> Children;
}
class User
{
string Name;
}
I cannot modify these classes.
I need to display all the information about organizations and users in one TreeView control. I.e., organization nodes should contain suborganization and user nodes.
The question is how can I do this having no CompositeCollections or Multibindings in Silverlight?

The tricky part of this solution is having to deal with two collections underneath each node and TreeView's HierarchicalDataTemplate only supports binding to a single ItemsSource.
One option is to create a ViewModel that merges the collections into a single class that represents an entry in the TreeView that you can then bind to inside your HierarchicalDataTemplate.
First I created my ViewModel class:
public class TreeViewEntry
{
public string Name { get; set; }
public IEnumerable<TreeViewEntry> Children { get; set; }
public object Model { get; set; }
}
Then I used a function, some Linq and some recursion to pull all the objects into a single collection:
private IEnumerable<TreeViewEntry> OrganizationsToTreeViewEntries(IEnumerable<Organization> orgs)
{
return (from o in orgs
select new TreeViewEntry
{
Name = o.Name,
Model = o,
Children = (from u in o.Users
select new TreeViewEntry
{
Name = u.Name,
Model = u
}
).Concat(OrganizationsToTreeViewEntries(o.Children))
});
}
public MainPage()
{
InitializeComponent();
var items = OrganizationsToTreeViewEntries(existingOrganizationData);
OrgTree.ItemsSource = items;
}
Now that I have a merged ItemsSource it's easy to style my HierarchicalDataTemplate:
<UserControl.Resources>
<common:HierarchicalDataTemplate x:Key="OrgTemplate" ItemsSource="{Binding Children}">
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</common:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<controls:TreeView x:Name="OrgTree" HorizontalAlignment="Left" Margin="8,8,0,8" Width="225" ItemTemplate="{StaticResource OrgTemplate}" />
</Grid>
You can use a ValueConverter to tweaks things like FontWeight if you want to adjust the visual style of certains elements (for example in my testing I created a ValueConverter on FontWeight that was bound to the Model property of TreeViewEntry).

I misread the question - I don't know of a way for the SL tree to display both the Children property and the Users property as child nodes. You may want to create a wrapper class with an AllChildren property that returns Users and Organizations in the same collection. The SL tree's HierarchialDataTemplate has a single property called ItemsSource that should be bound to a single child collection. Sorry I couldn't be more help - I'd delete this answer but I don't see a way to do that.

Related

How do I update a series of user controls based on a collection of data structures?

I have a user control BookViewControl which contains only a StackPanel, I also have a control named BookInfoControl which contains two TextBlocks in a WrapPanel. In the Codebehind for BookViewControl I have an Observable collection (Books) of a Data Structure (named BookInfo which contains two string properties, one for the author and the other for the books name).
What I want is to set it up in such a way that when I add or remove an item to the observable collection (Books) the Stack Panel in BookViewControl would update by adding or removing an instance of the BookInfoControl (where txtName of BookInfoControl is bound to the corresponding BookInfo's BookName property, and the txtAuthor of BookInfoControl would be bound to the same BookInfos AuthorName property).
Thus my problem is two-fold:
1) How do I dynamically create / remove UserControls based on a collection.
2) How to do I add those controls to another Controls StackPanel.
I know that I can bind an instance of BookInfo and it's properties to an instance of BookInfoControls TextBlocks (either in XAMl with Text={Binding SomeDataMember.Path} or in the code behind with instanceName.SetBinding(new binding{ Source = SomeDataMember, Path = new PropertyPath("Path") }); ). However doing so wouldn't cause new members of my collection Books to create a new instance of BookInfoControl nor would it solve the issue of adding it to the StackPanel.
I also considered putting in a kludge to manage a secondary collection of BookInfoControls and then sync it by hand but this seems too messy and I believe there should be an easier and cleaner way with WPF.
BookViewControl.xaml
<UserControl x:Class="GenericNamespace.BookViewControl "
... Default Generated Namespace Definitions ...
>
<Grid>
<StackPanel x:Name="pnlCollectionOfBookInfos" />
</Grid>
</UserControl>
BookInfoControl.xaml
<UserControl x:Class="GenericNamespace.BookInfoControl "
... Default Generated Namespace Definitions ...
>
<Grid>
<WrapPanel>
<TextBlock x:Name="txtName"/>
<TextBlock x:Name="txtAuthor"/>
</WrapPanel>
</Grid>
</UserControl>
BookInfo.cs
public class BookInfo
{
public string AuthorName { get; set; } = "";
public string BookName { get; set; } = "";
}
BookViewControl.cs
public partial class BookViewControl: UserControl
{
public ObservableCollection<BookInfo> Books { get; set; } = new ObservableCollection<BookInfo>();
public BookViewControl()
{
this.DataContext = this;
InitializeComponent();
}
}
In the end, modifying Books should cause a BookInfoControl to be displayed or removed in the StackPanel of BookViewControl where the text properties of BookInfoControls TextBlocks reflect the properties exposed in BookInfo.
Edit: The following link is something I found following the answer I got here and goes more into depth about this subject, hopefully it'll be of use to the next person who asks this: https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview
Welcome to SO!
Whenever you want to render a collection of items in WPF using data binding you have to use an ItemsControl or one of it's derived controls:
<ItemsControl ItemsSource="{Binding Books}" />
If you do this you'll see each book displayed as a string, you change that by declaring a DataTemplate for your Book type:
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:BookInfo}">
<local:BookInfoControl />
</DataTemplate>
</UserControl.Resources>

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

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>

Recursive TreeView does not retain structure when items added to underlying ObservableCollection

Am building a recursive WPF TreeView out of this model:
public class Category
{
public virtual ICollection<Category> Category1 { get; set; }
public virtual Category Category2 { get; set; }
}
So (badly named) Category1 is a collection of child categories and Category2 is the parent Category. This latter can be null if it's at the top level of the tree.
TreeView is being rendered like this:
<TreeView ItemsSource="{Binding Categories}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Category1}">
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ViewContentsCommand}"
CommandParameter="{Binding}" Width="100" HorizontalAlignment="Stretch">
<TextBlock FontWeight="Bold" Text="{Binding Name}"></TextBlock>
</Button>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Categories is an ObservableCollection so that it should update when users add or remove items from the collection via the UI. In terms of display, this all works as expected.
What I can't figure out is what happens when a user adds an item to the collection. Here's the code:
public void AddCategory()
{
Category c = new Category { Description = "New Category", Category2Id = SelectedCategory.CategoryId };
Categories.Add(c);
OnPropertyChanged("Categories"); //to refresh the UI
}
If you stick a breakpoint here and examine the Categories collection, it's as expected - the new item has slotted into the tree where it should, underneath its assigned ParentId. At the top level (i.e. items with null for Category2Id) there are the same number of items as before. But in the UI, this new item gets rendered as though it's at the top level instead of the correct place in the tree.
At first, I wondered if this was a peculiarity of working with the ObservableCollection directly, so I tried this:
public void AddCategory()
{
List<Category> cats = Categories.ToList();
Category c = new Category { Description = "New Category", Category2Id = SelectedCategory.CategoryId };
cats.Add(c);
Categories = cats.ToObservableCollection();
}
Which I expected to have no effect but which, to my surprise, resulted in nothing happening at all - i.e. the new category doesn't appear in the UI at all.
What am I doing wrong?
Calling the Add method on a collection will not alert the INotifyPropertyChanged interface of any change... just manually call (your implementation of the interface method)...
NotifyPropertyChanged("Categories");
... at the end of your AddCategory method to alert the interface to the fact that the Categories collection has changed.

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