I have a TreeView that is bound to a collection class that I have no control over. Inside this class is a collection of objects, which each have their own collection of items. This hierarchy is fixed at 3 deep. The children of the TreeView are contained in an ObservableCollection and are updated in the TreeView accordingly. The collections inside each of these objects are not observable, and thus I have to manually re-bind the data to the TreeView each time I add an object to one of the children. This causes all of the expanded children to be reset to an unexpanded state. I am trying to cache the objects that were expanded so they can be re-expanded after re-binding. The children work as I would expect, however when I try to expand a grandchild of the TreeView I get a null object.
To get a TreeViewItem I use the ItemContainerGenerator property of the ItemsControl:
TreeViewItem cfItem = treeView.ItemContainerGenerator
.ContainerFromItem(obj) as TreeViewItem;
cfItem.IsExpanded = true;
The second level collections all have a reference to their parent object. So since I have many of these object, they are looped over and each uses it's parent object to find the TreeViewItem of it's parent. The order in which they are added to the collection guarantees (I think) that the children will always be processed after the parent. Thus I get this ugly line of code:
qualItem = (
(TreeViewItem)treeView.ItemContainerGenerator
.ContainerFromItem(
((Child)obj).ParentObject
)
)
.ItemContainerGenerator.ContainerFromItem(obj) as TreeViewItem;
This line always fails when it attempts to get the container from item obj. It successfully gets the parent TreeViewItem, but when attempting to get the Child's TreeViewItem container, I always receive null. The documentation states that ContainerFromItem() returns
"A System.Windows.UIElement that
corresponds to the given item. Returns
null if the item does not belong to
the item collection, or if a
System.Windows.UIElement has not been
generated for it."
I know that the second level child exists in the parent's item collection. I checked in the debugger in VS 2010, and all of the items are there. I spent a good bit of time on Google searching for an answer and came across someone who said that the container items are created on a background worker thread and may not be generated by the time an attempt is made to get the child item container. I tried waiting for the Status property of the ItemContainerGenerator to be equal to GeneratorStatus.ContainersGenerated, but I still got the same result. I need to somehow obtain the TreeViewItem container for my second level children so they can be re-expanded just like the first level children.
"A System.Windows.UIElement that corresponds to the given item. Returns null if the item does not belong to the item collection, or if a System.Windows.UIElement has not been generated for it."
Looks like because of Virtualization, the UIElement you look for doesn't exist when you are looking for it. If the collection is not too large, try turning the Virtualization off.
The solution is to add another layer of abstraction between the data and the TreeView. The top level collection is an ObservableCollection, and that contains several ViewModels, which implement INotifyPropertyChanged, and also have ObservableCollections of elements that are in the tree beneath it. This system allows WPF to more easily bind and keep track of the elements that are being added and deleted.
My main source of information was this article:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Related
I have a virtualized TreeView and am successfully using the mechanism described here to select a given domain object in the view (expanding the path to the object accordingly), but the tree is always realized fully, which is prohibitively slow for my case (several thousand items).
Is there a better way of getting from the object in a domain hierarchy to the tree
item instead of this brute force method? I know the path to the item in the domain hierarchy, so I would like to realize only those items that lie on the path to the item to be selected, but I have not found out how to do so.
A method I've used in the past is to also include an IsExpanded property binding to a ViewModel object (which represents your visual TreeItem).
You can then walk the path from your leaf node back to the root of the tree and setting IsExpanded to true as you go which means no brute force nasty non-MVVM compliant code in the code behind of your XAML to do so.
I currently have a simple tree view that contains one parent node with multiple child nodes. I was wondering if there is a way to find the location of the selected node in the parent tree.
I currently have an action event on the treeview and when the user clicks on the child it prints out the string value of the selected child. I have tried using:
int val = TreeView.SelectedItemProperty.GlobalIndex;
but it always returns 0. I have seen some examples in VB but I cant seem to get the same idea to work in C#.
You have to use the ItemContainerGenerator property of the Treeview.
http://msdn.microsoft.com/en-us/library/system.windows.controls.itemcontainergenerator.aspx
See: ContainerFromIndex and IndexFromContainer
Note that each TreeViewItem also has an ItemContainerGenerator (its an ItemsControl), so you'd have to recursively search down the tree if you have multiple levels.
I think the answer to all your treeview problems (and most ui ones) in wpf is to build a ViewModel. Anytime you start crawling the visual tree to look for elements that you are already binding to, you are doing things the hard way. Once you start using ItemsContainerGenerator you have to start worrying about a whole lot of issues you should not have to.
You are binding to a hierarchical structure. If that structure has a selected item property on each item and it is bound to the TreeViewItem selected item then you can just get the selected item in code and do everything else from there. Have a look at a similiar question here.
So i didn't find the answer i was looking for (I may of confused others with what my question was. by saying location). Anyways how I solved it was I got the string value of the child selected and compared it to my list. Thanks to those who answered!
I have a render-heavy item template in an ItemsControl and want to minimize the recreation of child item templates when ItemsSource signals a change. I am wondering if, because ObservableCollection can tell WPF precisely what has changed (as opposed to just the whole list), if it is more efficient in rendering changes to the collection, or if WPF is smart enough to reuse previous item views if it detects the same item is still in the changed list.
Your suspicion is correct. WPF will not reuse previous views. If you replace the ItemsSource of an ItemsControl with a new List, it will create completely new views for each item in the list, even if the same items were in the old list.
You can test this yourself by putting a custom control in the ItemTemplate and adding a breakpoint or debug logging to its constructor. If you replace the ItemsSource with an identical list, you will see your control constructed once for each item in the list. On the other hand, when an item is added to an ObservableCollection you will only see it called once.
Note that the ItemsControl can reuse the container (such as ListBoxItem) if you are using a virtualizing panel and have container recycling enabled. See Link. It still can't reuse the contents of the container, however.
ObservableCollection only informs of addition and removal of objects - so perhaps not as precise as what you were expecting (if an object within the list changes, ObservableCollection will not fire off any notifications).
I have a WPF application that includes a TreeView. The user adds content to the tree using right click and context menu.
This creates a new TreeViewItem that is added to the tree's ItemCollection.
The problem is that sometimes the new items are not shown, although they were added. If you minimize/maximize the window they suddenly appear.
I tried to call TreeView.UpdateLayout() after the addition of the new item, but the result is the same.
Any suggestions?
dont add to the trees item collection. bind the treeview to an observable collection. keep the model and the view seperate. the treeview will reflect changes to the model. you should never have to call treeview.updateLayout. add your items to a collection. (your model) which is bound to the treeview. its a lot less work
I am not shure about this but try calling Refresh or call on the node witch is parent to the new node ExpandAll. Hope this works for you
Best Regards,
iordan
I've got an ObservableCollection assigned to ItemsSource of a listbox. Listbox is using a DataTemplate which has a usercontrol in it which has items bound to each listboxitem's properties.
I have an up and down button on the usercontrol which moves an item up or down the list. The list is sorted by the property that I'm changing. Click up or down, the DisplayOrder property is changed, I'm using INotifyProperty to tell the ObservableCollection it needs to re-sort.
What is the best way for the usercontrol to get the item count so that I can disable the down button when an item reaches the bottom of the list. (The top is easy, I compare to 0)
I see two ways of handling this.
The first is to pass a handle of your collection to each of your items (when they get added to the collection) so that they can calculate if they are the first or last item themselves.
The other is to expose writable properties on your items, such as CanGoUp and CanGoDown, and your parent control becomes responsible for setting these properties properly. I prefer this solution because it decouples the behavior of your parent list, from the child items. Even though the up/down buttons are placed on your child items, it's really a functionality of the parent list.
listBox1.Items.Count ?
this.Parent.Controls.Count?