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

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.

Related

TreeView with RadioButtons

I need to create TreeView with dynamicly node creation in my WPF project. There is one condition for TreeView Nodes. All leafs must contains Radiobuttons with work logic same as group of RadioButtons.
I have this XAML code, that describes treeview data template:
<HierarchicalDataTemplate x:Key="sko_ver_hdt">
<RadioButton Margin="0,0,10,0" Content="{Binding Version}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="sko_hdt"
ItemTemplate="{StaticResource sko_ver_hdt}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
I've tried to use CollectionViewSource to group nodes of TreeView like this:
<CollectionViewSource x:Key="cvs" Source="{Binding SKOs}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Version"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
but nothing happens.
I have a model that describes treeview data:
internal class SKOVM
{
public SKOVM(DBCommunication.SKO sko)
{
Name = sko.Name;
Version = sko.VersionCode;
Id = sko.Id;
Children = new ObservableCollection<SKOVM>(sko.SKO1.Select(x => new SKOVM(x)));
sko.SKOSystemInformationReference.Load();
if (sko.SKOSystemInformation != null)
Version = String.Format("{0} / {1}", Version, sko.SKOSystemInformation.Designer);
}
public long Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public ObservableCollection<SKOVM> Children { get; set; }
}
Any ideas how i can implement grouped radiobuttons behaviour logic to my treeview leaf nodes?
Thanks in advance.
Have you tried using the RadioButton.GroupName property?:
<HierarchicalDataTemplate x:Key="sko_ver_hdt">
<RadioButton Content="{Binding Version}" GroupName="{Binding GroupName}" />
</HierarchicalDataTemplate>
This property Gets or sets the name that specifies which RadioButton controls are mutually exclusive. So you'll need a property in the child item class where the child items of each group will have the same value. In this way, you can data bind that value to the GroupName property and the RadioButton of each child item should work in their individual groups.

How bind an object to treeview from Xaml

I am trying to bind an object to the treeviewcontrol WPF by XAML, I am getting the treview as empty. When i am doing that by treeview.items.add(GetNode()) then its working.
I am using MVVM Framework(caliburn.Micro) I wanted to do it in Xaml, how do I assign Item source property in xaml? I tried with creating a property of Node class and calling the Method GetNode() with in the property, and assigned that property as itemssource of the treeview and changed the List to Observable collection. Still issue is same.
Working Xaml when doing treeview.items.Add(GetNode()) which returns a Node and and i as assigning Nodes collection to Hireachial Template.
<TreeView Name="treeview2"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}" Width="300">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<DockPanel LastChildFill="True">
<TextBlock Padding="15,0,30,0" Text="{Binding Path=numitems}" TextAlignment="Right"
DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Text}" DockPanel.Dock="Left" TextAlignment="Left" />
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Server Side Code:
this.treeview2.Items.Add(GetNode());
GetNode recursively build a list of type Node.
public class Node
{
public string Text { get; set; }
public List<Node> Nodes { get; set; }
public ObservableCollection<Node> Nodes{get;set;} // with list and observable collection same results
public int numitems { get; set; }
}
In addition to the HierarchicalDataTemplate, which seems just fine, add a binding to the ItemsSource property of your TreeView:
public class ViewModel
{
private List<Node> _rootNodes;
public List<Node> RootNodes
{
get
{
return _rootNodes;
}
set
{
_rootNodes = value;
NotifyPropertyChange(() => RootNodes);
}
}
public ViewModel()
{
RootNodes = new List<Node>{new Node(){Text = "This is a Root Node}",
new Node(){Text = "This is the Second Root Node"}};
}
And in XAML:
<TreeView ItemsSource="{Binding RootNodes}"
.... />
Edit: Remove the call that does this.Treeview.... you don't need that. Try to keep to the minimum the amount of code that references UI Elements. You can do everything with bindings and have no need to manipulate UI Elements in code.

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 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.

Problem with TreeView in SilverLight

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.

Categories

Resources