I have been looking to find the solution to this problem but still, after so much research, I am unable to figure out an elegant solution. Finally, I have decided to ask it here to get an experts opinion.
It's a WPF application where I have created my domain/model classes like:
public abstract class Node : Entity
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public Node Parent { get; set; }
private List<Node> _children = new List<Node>();
public IEnumerable<Node> Children => _children.AsEnumerable();
public void AddChild(Node node)
{
_children.Add(node);
}
public void RemoveChild(Node node)
{
_children.Remove(node);
}
public void MoveUp(Node node)
{
var index = _children.IndexOf(node);
Move(index, index - 1);
}
public void MoveDown(Node node)
{
var index = _children.IndexOf(node);
Move(index, index + 1);
}
}
According to this domain, Node class has a parent-child relationship.
The important point to note here is Children collection type is exposed as IEnumerable to encapsulate it to avoid undesired changes to my collection. I want to bind this collection to a TreeView control to show N-level of the hierarchy.
<HierarchicalDataTemplate x:Key="ContainerTemplate"
DataType="{x:Type fsx:Node}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<fsx:TreeViewTemplateSelector x:Key="TreeViewTemplateSelector"
ContainerTemplate="{StaticResource ContainerTemplate}" />
<TreeView x:Name="TreeViewNodes"
ItemsSource="{Binding Nodes}"
ItemTemplateSelector="{StaticResource TreeViewTemplateSelector}">
</TreeView>
Imagine, Nodes that is bound to TreeView ItemSource property is ObservableCollection in ViewModel which is getting the data from Model.
I want to allow UI operations like adding and removing nodes and moving them up and down and correspondingly I want to update those operations in my domain object. Now the problem is that since my collection is not ObservableCollection, any operations I am performing is not reflected in UI.
Question is how to make it all work instantly showing the operation result on UI as well as updating the domain behind the scene effectively?
Note: I will update the question to provide more details if required!
Related
I want to add a child item in a TreeViewItem that was added previously. The problem with code like this:
How to insert a child node in a TreeView Control in WPF?
or with many other variations that I have tried to use like:
for (int i = 1; i <= dataTreeview.Items.Count; i++)
{
TreeViewItem tempTVI = (TreeViewItem)dataTreeview.Items.GetItemAt(i);
}
is that I get an InvalidCastException exception because items(in the other stackoverflow question) or tempTVI are strings not TreeViewItem
I don't know why this is and I ran out of ideas.
If it helps I am using Visual Studio 2015 Community in preview.
Thank you for your help.
The TreeView in WPF is an extension of ItemsControl. There are basically two ways to work with these controls, one that makes dynamically changing trees easy to manage, and one that makes completely static trees trivial to setup.
Dynamic Trees
The way TreeView was designed to be used is following the MVVM design pattern. Here is a quick example of this.
First, when working with MVVM, you always want some sort of base class for viewmodels that implements property change notification. Here is the most basic example:
internal class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, you need a class that represents the data for a single node in the tree. For example:
internal class Node : ObservableObject
{
private ObservableCollection<Node> mChildren;
// Add all of the properties of a node here. In this example,
// all we have is a name and whether we are expanded.
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyPropertyChanged();
}
}
}
private string _name;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded != value)
{
_isExpanded = value;
NotifyPropertyChanged();
}
}
}
private bool _isExpanded;
// Children are required to use this in a TreeView
public IList<Node> Children { get { return mChildren; } }
// Parent is optional. Include if you need to climb the tree
// from code. Not usually necessary.
public Node Parent { get; private set; }
public Node(Node parent = null)
{
mChildren = new ObservableCollection<Node>();
IsExpanded = true;
Parent = parent;
}
}
Now, create a viewmodel for your control with a collection of these nodes in it. In this example, the viewmodel is for the main window of the application:
internal class MainWindowVM : ObservableObject
{
private ObservableCollection<Node> mRootNodes;
public IEnumerable<Node> RootNodes { get { return mRootNodes; } }
public MainWindowVM()
{
mRootNodes = new ObservableCollection<Node>();
// Test data for example purposes
Node root = new Node() { Name = "Root" };
Node a = new Node(root) { Name = "Node A" };
root.Children.Add(a);
Node b = new Node(root) { Name = "Node B" };
root.Children.Add(b);
Node c = new Node(b) { Name = "Node C" };
b.Children.Add(c);
Node d = new Node(b) { Name = "Node D" };
b.Children.Add(d);
Node e = new Node(root) { Name = "Node E" };
root.Children.Add(e);
mRootNodes.Add(root);
}
}
Finally, create the TreeView instance and set it up to use your data. In this example, the TreeView is the only thing in the main application window:
<Window x:Class="WpfTreeViewExample.MainWindow"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTreeViewExample"
SizeToContent="WidthAndHeight"
Title="MainWindow">
<Window.DataContext>
<local:MainWindowVM />
</Window.DataContext>
<TreeView
Margin="10"
ItemsSource="{Binding RootNodes}">
<ItemsControl.ItemContainerStyle>
<Style
TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
<!-- Could also put IsSelected here if we needed it in our Node class -->
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:Node}"
ItemsSource="{Binding Children}">
<!-- Can build any view we want here to be used for each node -->
<!-- Simply displaying the name in a text block for this example -->
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</ItemsControl.ItemTemplate>
</TreeView>
</Window>
Once you have this setup, all you have to do from now on is manipulate the data in your viewmodel, and the TreeView will automatically update to reflect those changes. This way, you never need to manipulate the control directly.
Here is the resulting view:
Static Trees
If the entire tree is known ahead of time and will never change, you can set it up pretty simply like this:
<TreeView
x:Name="mTreeView"
Margin="10">
<TreeViewItem Header="Root">
<TreeViewItem Header="Node A" />
<TreeViewItem Header="Node B">
<TreeViewItem Header="Node C" />
<TreeViewItem Header="Node D" />
</TreeViewItem>
<TreeViewItem Header="Node E" />
</TreeViewItem>
</TreeView>
The problem with this approach is that when you want to modify the tree programmatically, it becomes hard to manage because you have to deal with the TreeView itself. However, it is doable from a code-behind. For example, if I want to add a new child node under "Node C" called "New Node", I could do something like this:
((TreeViewItem)((TreeViewItem)((TreeViewItem)mTreeView.Items[0]).Items[1]).Items[0]).Items.Add(new TreeViewItem() { Header = "New Node" });
Working this way gets messy though. Since we don't have a parallel representation of the tree in data, we have to keep accessing things through the control and casting them.
Some Other Setup
Looking at your question, it looks like you are not following either of these approaches, but instead have a TreeView that is setup basically like this:
<TreeView>
<sys:String>Node A</sys:String>
<sys:String>Node B</sys:String>
</TreeView>
So, you have a TreeView full of strings. Internally, an ItemsControl can take any object and wrap it in an item container. TreeView will wrap those strings in TreeViewItem instances. However, the items are still stored as strings, and accessing TreeView.Items will return the strings that you added.
Getting the TreeViewItem associated with an arbitrary item in a TreeView is actually fairly difficult because you have to get the containers for each item at the root level, then dig into each of those and get containers for their items, and so on all the way through the tree until you find the item you are looking for.
You can find an example for how to lookup item containers here. Note that you cannot use virtualization in your TreeView in order for this to work reliably. Also, I would recommend against working this way because you are making things harder on yourself.
You are getting String because that is what the source of the TreeView must have been bound to.
Using this method allows you to iterate over the items and retrieve the TreeViewItem containers that they are inside:
List<TreeViewItem> GetChildren(TreeViewItem parent)
{
List<TreeViewItem> children = new List<TreeViewItem>();
if (parent != null)
{
foreach (var item in parent.Items)
{
TreeViewItem child = item as TreeViewItem;
if (child == null)
{
child = parent.ItemContainerGenerator.ContainerFromItem(child) as TreeViewItem;
}
children.Add(child);
}
}
return children;
}
Please notice that they check if the TreeViewItem is null after casting it. This is good practice as it prevents the null reference exception from crashing your application if something does happen to go wrong.
source: https://social.msdn.microsoft.com/Forums/vstudio/en-US/595f0c84-01e7-4534-b73b-704b41713fd5/traversing-the-children-of-a-treeviewitem
Maybe you forgot the tags?
If you have the following in your xaml file:
<TreeView x:Name="MyTreeView">
<TreeViewItem>Hello</TreeViewItem>
World
</TreeView>
And the following in the code-behind:
var a = MyTreeView.Items.GetItemAt(0) as string;
var b = MyTreeView.Items.GetItemAt(0) as TreeViewItem;
var c = MyTreeView.Items.GetItemAt(1) as string;
var d = MyTreeView.Items.GetItemAt(1) as TreeViewItem;
Variables a and d will be null whereas b will be a TreeViewItem and c will be a string.
i have a TreeView with same in xaml createt TreeViewItems. And one note has a ObservableCollection as ItemSource. This works like a Charm. But now i want same Notes to every item of the list (for better organization). So i do this:
This is my HierarchicalDataTemplate for the liste
<HierarchicalDataTemplate DataType="{x:Type classes:Connection}" ItemsSource="{Binding Source={StaticResource ConnectionChilds}}" >
<TextBlock Text="{Binding DisplayName}" />
</HierarchicalDataTemplate>
And the ItemsSource:
<collections:ArrayList x:Key="ConnectionChilds">
<classes:TreeItemObject ItemsSourcePath="Child1" />
<classes:TreeItemObject ItemsSourcePath="Child2" />
<classes:TreeItemObject ItemsSourcePath="Child3" />
</collections:ArrayList>
TreeItemObject is a simple Class:
public class TreeItemObject
{
public string ItemsSourcePath { get; set; }
}
And last but not least HierarchicalDataTemplate for TreeItemObject:
<DataTemplate DataType="{x:Type classes:TreeItemObject}">
<TextBlock Margin="5,0" Text="{Binding Path=ItemsSourcePath}"/>
</DataTemplate>
Looked like this
Connection 1
Child1
Child2
Child3
Connection 2
Child1
Child2
Child3
Connection 3
Child1
Child2
Child3
Works perfekt. But now if i select "Connection 2\Child3" i got the same object like "Connection 1\Child3" or "Connection 3\Child3". Ok make sense because based on same object. With that situation i have no chance to find out the parent-note on OnSelectedItemChanged.
Because if i search with this extension-Class. I only get the first expanded Connection-Note.
http://social.msdn.microsoft.com/Forums/silverlight/en-US/84cd3a27-6b17-48e6-8f8a-e5737601fdac/treeviewitemcontainergeneratorcontainerfromitem-returns-null?forum=silverlightnet
Is there a way to find the real parent in the TreeView?
I personally do not like the idea of creating clones within a converter but I do not know the full scope of your problem. So working with what you have presented here, we can achieve the assignment of a parent to each TreeItemObject via a MultiValueConverter.
WPF has an awesome feature called MultiBinding. It processes 1 or more source values into a single target. To do this, it needs a multivalue converter.
So, change the TreeItemObject to
public class TreeItemObject
{
public string ItemsSourcePath { get; set; }
public WeakReference Parent { get; set; }
}
The hierarchicalDataTemplate for the Connection type would become
<HierarchicalDataTemplate DataType="{x:Type classes:Connection}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource items2Clones}">
<Binding Source="{StaticResource ConnectionChilds}" />
<Binding />
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<TextBlock Text="{Binding DisplayName}" />
</HierarchicalDataTemplate>
Based on the above binding, to set the parent in the converter, the Convert method in your convert will be along the lines
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var destinationItems = new Collection<TreeItemObject>();
var items = values[0] as Collection<TreeItemObject>;
var parent = values[1] as Connection;
// null checks are required here for items and parent
foreach (var item in items)
{
var destinationItem = item.Clone(); // Assumed extension method
destinationItem.Parent = new WeakReference(parent);
destinationItems.Add(destinationItem);
}
return destinationItems;
}
Finally, the SelectedItemChanged event handler would be something like
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var item = (TreeItemObject)e.NewValue;
if ((item != null) && (item.Parent != null) && (item.Parent.IsAlive))
{
// do stuff - Console.WriteLine(((Connection)item.Parent.Target).DisplayName);
}
}
I have removed exception management and some null checking for brevity.
I hope this helps
I think your only choice is to clone your children before you add them to the TreeView, allowing at least a binary difference between the child nodes.
If you do this, instead of handling the OnSelectedItemChanged event and traversing the object graph, add a WeakReference of the parent to each of its children. This will allow you to immediately reference the parent from the child and also allow .Net to clean up the object graph correctly.
An example of using a WeakReference property from a SelectedItemChanged event handler is as follows
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = sender as TreeView;
var item = treeView.SelectedItem as TreeItemObject;
if (item.Parent.IsAlive)
{
var parent = item.Parent.Target;
}
}
I have removed exception management and null checking for brevity.
I hope this helps.
It's difficult to get the parent from the treeView item so what i did is that, i had a member property of the parent of the parent type which holds the reference to the parent as below
public class FileSystem :NotifyChange, IEnumerable
{
#region Private members
private ObservableCollection<FileSystem> subDirectoriesField;
#endregion
#region Public properties
/// <summary>
/// Gets and sets all the Files in the current folder
/// </summary>
public ObservableCollection<FileSystem> SubDirectories
{
get
{
return subDirectoriesField;
}
set
{
if (subDirectoriesField != value)
{
subDirectoriesField = value;
RaisePropertyChanged("SubDirectories");
}
}
}
/// <summary>
/// Gets or sets name of the file system
/// </summary>
public string Name
{
get;
set;
}
/// <summary>
/// Gets or sets full path of the file system
/// </summary>
public string FullPath
{
get;
set;
}
/// <summary>
/// object of parent, null if the current node is root
/// </summary>
public FileSystem Parent
{
get;
set;
}
public FileSystem(string fullPath, FileSystem parent)
{
Name = fullPath != null ? fullPath.Split(new char[] { System.IO.Path.DirectorySeparatorChar },
StringSplitOptions.RemoveEmptyEntries).Last()
FullPath = fullPath;
Parent = parent;
AddSubDirectories(fullPath);
}
public IEnumerator GetEnumerator()
{
return SubDirectories.GetEnumerator();
}
private void AddSubDirectories(string fullPath)
{
string[] subDirectories = Directory.GetDirectories(fullPath);
SubDirectories = new ObservableCollection<FileSystem>();
foreach (string directory in subDirectories)
{
SubDirectories.Add(new FileSystem(directory, this));
}
}
}
And my viewModel is as below
public class ViewModel:NotifyChange
{
private ObservableCollection<FileSystem> directories;
public ObservableCollection<FileSystem> Directories
{
get
{
return directoriesField;
}
set
{
directoriesField = value;
RaisePropertyChanged("Directories");
}
}
public ViewModel()
{
//The below code has to be moved to thread for better user expericen since when UI is loaded it might not respond for some time since it is looping through all the drives and it;s directories
Directories=new ObservableCollection<FileSystem>();
Directories.Add(new FileSystem("C:\\", null);
Directories.Add(new FileSystem("D:\\", null);
Directories.Add(new FileSystem("E:\\", null);
}
}
Since each child knows it;s parent now you can traverse back, root node parent will be null
Xmal will have the following
<TreeView Grid.Row="1" Background="Transparent" ItemsSource="{Binding Directories}" Margin="0,10,0,0" Name="FolderListTreeView"
Height="Auto" HorizontalAlignment="Stretch" Width="300" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:FileSystem}" ItemsSource="{Binding SubDirectories}">
<Label Content="{Binding Path= Name}" Name="NodeLabel" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Hope this helps you
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'm using a WPF TreeView control, which I've bound to a simple tree structure based on ObservableCollections. Here's the XAML:
<TreeView Name="tree" Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Text}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And the tree structure:
public class Node : IEnumerable {
private string text;
private ObservableCollection<Node> children;
public string Text { get { return text; } }
public ObservableCollection<Node> Children { get { return children; } }
public Node(string text, params string[] items){
this.text = text;
children = new ObservableCollection<Node>();
foreach (string item in items)
children.Add(new Node(item));
}
public IEnumerator GetEnumerator() {
for (int i = 0; i < children.Count; i++)
yield return children[i];
}
}
I set the ItemsSource of this tree to be the root of my tree structure, and the children of that become root-level items in the tree (just as I want):
private Node root;
root = new Node("Animals");
for(int i=0;i<3;i++)
root.Children.Add(new Node("Mammals", "Dogs", "Bears"));
tree.ItemsSource = root;
I can add new children to the various non-root nodes of my tree structure, and they appear in the TreeView right where they should.
root.Children[0].Children.Add(new Node("Cats", "Lions", "Tigers"));
But, if I add a child to the root node:
root.Children.Add(new Node("Lizards", "Skinks", "Geckos"));
The item does not appear, and nothing I've tried (such as setting the ItemsSource to null and then back again) has caused it to appear.
If I add the lizards before setting the ItemsSource, they show up, but not if I add them afterwards.
Any ideas?
You are setting ItemsSource = root which happens to implement IEnumerable but is not in and of itself observable. Even though you have a Children property which is observable, that's not what you're binding the TreeView to so the TreeView doesn't have any way of listening to changes that occur through the Children property.
I would drop IEnumerable from the Node class altogether. Then set treeView.ItemsSource = root.Children;
if 'root' is an ObservableCollection your treeview will update. is 'root' an observable collection, or is root a node that is in an observable collection? seeing your binding for the items source would help to answer this question. as you are assigning it in code you might just be setting it to be a single element, not a collection
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.