I have a requirement to change the selected node in a treeview which is hosted in a separate tab. Further more, if the parent node is not expanded, I wish to expand the node.
After about an hour of fruitless searching through SO, Google, et al, I have decided to post a question.
I can find and expand the required node when it is all visible, but when the treeveiw is obscured by another tab item, it doesn't update. I'm also not entirely sure that the item is being 'selected' - in the debugger it says ISelected is true, and the IsExpanded property of the parent is also true.
I have simplified down my actual problem in the following lines of code:
XAML (Tab control which has two items, one is a button to reproduce the problem, and a treeview which should be updated):
<Window x:Class="TreeviewTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl>
<TabItem Header="Select">
<Button Content="Select Delta!" Click="ButtonBase_OnClick" />
</TabItem>
<TabItem Header="Tree">
<TreeView ItemsSource="{Binding NodesDisplay}" Name ="treTest">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildrenDisplay}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</TabItem>
</TabControl>
</Grid>
MainWindow code:
namespace TreeviewTest
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public ObservableCollection<TreeNode> Nodes { get; set; }
public ICollectionView NodesDisplay { get; set; }
public MainWindow()
{
InitializeComponent();
Nodes = new ObservableCollection<TreeNode>
{
new TreeNode(new List<TreeLeaf>
{
new TreeLeaf{Name = "Alpha"},
new TreeLeaf{Name = "Beta"}
}){ Name = "One" },
new TreeNode(new List<TreeLeaf>
{
new TreeLeaf{Name = "Delta"},
new TreeLeaf{Name = "Gamma"}
}){ Name = "Two" }
};
NodesDisplay = CollectionViewSource.GetDefaultView(Nodes);
DataContext = this;
}
public class TreeNode
{
public string Name { get; set; }
public ObservableCollection<TreeLeaf> Children { get; private set; }
public ICollectionView ChildrenDisplay { get; private set; }
public TreeNode(IEnumerable<TreeLeaf> leaves)
{
Children = new ObservableCollection<TreeLeaf>(leaves);
ChildrenDisplay = CollectionViewSource.GetDefaultView(Children);
}
}
public class TreeLeaf
{
public string Name { get; set; }
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
EnsureCanIterateThroughCollection(treTest);
var rootLevelToSelect = Nodes.First(x => x.Name == "Two");
TreeViewItem root = treTest.ItemContainerGenerator.ContainerFromItem(rootLevelToSelect) as TreeViewItem;
EnsureCanIterateThroughCollection(root);
var leafLevelToSelect = rootLevelToSelect.Children.First(x => x.Name == "Delta");
TreeViewItem leaf = root.ItemContainerGenerator.ContainerFromItem(leafLevelToSelect) as TreeViewItem;
if (!root.IsExpanded)
root.IsExpanded = true;
leaf.IsSelected = true;
ReflectivelySelectTreeviewItem(leaf);
}
//Got this from another SO post - not sure is setting IsSelected on the node is actually doing what I think it is...
private static void ReflectivelySelectTreeviewItem(TreeViewItem node)
{
MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance);
selectMethod.Invoke(node, new object[] { true });
}
private static void EnsureCanIterateThroughCollection(ItemsControl itemsControl)
{
if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.NotStarted)
ForceGenerateChildContent(itemsControl);
}
private static void ForceGenerateChildContent(ItemsControl itemsControl)
{
itemsControl.ApplyTemplate();
IItemContainerGenerator generator = itemsControl.ItemContainerGenerator;
GeneratorPosition position = generator.GeneratorPositionFromIndex(0);
using (generator.StartAt(position, GeneratorDirection.Forward, true))
{
for (int i = 0; i < itemsControl.Items.Count; i++)
{
DependencyObject dp = generator.GenerateNext();
generator.PrepareItemContainer(dp);
}
}
}
}
}
Also - another XAML snippet which does the same thing, but where the treeview is visible - you should be able to see the treeview expand and the item selected
<Window x:Class="TreeviewTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Select Delta!" Click="ButtonBase_OnClick" />
<TreeView ItemsSource="{Binding NodesDisplay}" Name ="treTest" Grid.Row="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildrenDisplay}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
I would appreciate any help - My understanding of treeview's and WPF and databinding is that it doesn't work as nicely as something that gives you IsSynchronisedWithCurrentItem, which is why I am trying to handle updates to the treeview manually and am also trying to select items in the tree programmatically. If I am wrong on that front I would love a pointer to show me how to do it in a more 'WPF' way!