摘要:WPF TreeView – SelectedItem Two Way Binding
Because the standard WPF TreeView implementation supports Virtualization it is unable to support two way bindings on its SelectedItem property as standard. This makes sense, because with Virtualization you may not have a container (TreeViewItem) available for any particular bound data item at the time you need it (to set its IsSelected property of the TreeViewItem to ‘True’).
The solution is to use attached properties to force the ItemContainerGenerator to create the necessary containers for each data item. This will break the virtualization support, but that is a price you need to pay for a solution to this issue, so you should only use it on TreeView controls where the lack of Virtualization wont be a significant drawback.
Here is the attached property implementation:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
public class DemoAttachedProps
{
public static DependencyProperty SelectedItemProperty = DependencyProperty
.RegisterAttached(
"SelectedItem", typeof(object), typeof(DemoAttachedProps),
new PropertyMetadata(new object(), OnSelectedItemChanged));
public static object GetSelectedItem(TreeView treeView)
{
return treeView.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(TreeView treeView, object value)
{
treeView.SetValue(SelectedItemProperty, value);
}
private static void OnSelectedItemChanged(DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
var treeView = d as TreeView;
if (treeView == null)
{
return;
}
treeView.SelectedItemChanged -= TreeViewItemChanged;
var treeViewItem = SelectTreeViewItemForBinding(args.NewValue,
treeView);
if (treeViewItem != null)
{
treeViewItem.IsSelected = true;
}
treeView.SelectedItemChanged += TreeViewItemChanged;
}
private static void TreeViewItemChanged(object sender,
RoutedPropertyChangedEventArgs e)
{
((TreeView)sender).SetValue(SelectedItemProperty, e.NewValue);
}
private static TreeViewItem SelectTreeViewItemForBinding(
object dataItem, ItemsControl ic)
{
if (ic == null || dataItem == null)
{
return null;
}
IItemContainerGenerator generator = ic.ItemContainerGenerator;
using (generator.StartAt(generator.GeneratorPositionFromIndex(-1),
GeneratorDirection.Forward))
{
foreach (var t in ic.Items)
{
bool isNewlyRealized;
var tvi = generator.GenerateNext(out isNewlyRealized);
if (isNewlyRealized)
{
generator.PrepareItemContainer(tvi);
}
if (t == dataItem)
{
return tvi as TreeViewItem;
}
var tmp = SelectTreeViewItemForBinding(dataItem,
tvi as ItemsControl);
if (tmp != null)
{
return tmp;
}
}
}
return null;
}
}
And here is the attached property in action.
public class TestDataObjViewModel
{
public List TestList { get; private set; }
public List TestHierarchy { get; private set; }
public TestDataObjViewModel()
{
TestList = new List();
var top = new TestDataObj { TestText = "top" };
TestList.Add(top);
var second1 = new TestDataObj { TestText = "second 1" };
TestList.Add(second1);
top.Children.Add(second1);
var second2 = new TestDataObj { TestText = "second 2" };
TestList.Add(second2);
top.Children.Add(second2);
var third = new TestDataObj { TestText = "third" };
TestList.Add(third);
second1.Children.Add(third);
TestHierarchy = new List { top };
}
}
public class TestDataObj
{
private readonly List children = new List();
public string TestText { get; set; }
public List Children
{
get
{
return children;
}
}
}
Here’s the code-behind for the demo XAML
public MainWindow()
{
InitializeComponent();
DataContext = new TestDataObjViewModel();
}
And here’s the XAML