Der Link gab Estifanos Kidane gebrochen ist. Er meinte wahrscheinlich the "Changing selection in a virtualized TreeView" MSDN sample. jedoch zeigt diese Probe, wie ein Knoten in einem Baum wählen, aber unter Verwendung von Code-Behind und nicht mvvm und verbindlich, so dass es auch nicht die fehlenden SelectedItemChanged event behandeln, wenn das gebundene SelectedItem geändert wird. Die einzige Lösung, die ich mir vorstellen kann, ist, das MVVM-Muster aufzubrechen, und wenn die ViewModel-Eigenschaft, die an die SelectedItem-Eigenschaft gebunden ist, geändert wird, ruft die View auf und ruft eine Code-Behind-Methode (ähnlich dem MSDN-Beispiel) auf sicher, dass der neue Wert tatsächlich in der Baumstruktur ausgewählt ist.
Hier ist der Code, den ich geschrieben habe, um damit umzugehen. Angenommen, Ihre Datenelemente vom Typ sind Node
, die eine Parent
Eigenschaft hat:
public class Node
{
public Node Parent { get; set; }
}
ich das folgende Verhalten Klasse schrieb:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class NodeTreeSelectionBehavior : Behavior<TreeView>
{
public Node SelectedItem
{
get { return (Node)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(Node), typeof(NodeTreeSelectionBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newNode = e.NewValue as Node;
if (newNode == null) return;
var behavior = (NodeTreeSelectionBehavior)d;
var tree = behavior.AssociatedObject;
var nodeDynasty = new List<Node> { newNode };
var parent = newNode.Parent;
while (parent != null)
{
nodeDynasty.Insert(0, parent);
parent = parent.Parent;
}
var currentParent = tree as ItemsControl;
foreach (var node in nodeDynasty)
{
// first try the easy way
var newParent = currentParent.ItemContainerGenerator.ContainerFromItem(node) as TreeViewItem;
if (newParent == null)
{
// if this failed, it's probably because of virtualization, and we will have to do it the hard way.
// this code is influenced by TreeViewItem.ExpandRecursive decompiled code, and the MSDN sample at http://code.msdn.microsoft.com/Changing-selection-in-a-6a6242c8/sourcecode?fileId=18862&pathId=753647475
// see also the question at http://stackoverflow.com/q/183636/46635
currentParent.ApplyTemplate();
var itemsPresenter = (ItemsPresenter)currentParent.Template.FindName("ItemsHost", currentParent);
if (itemsPresenter != null)
{
itemsPresenter.ApplyTemplate();
}
else
{
currentParent.UpdateLayout();
}
var virtualizingPanel = GetItemsHost(currentParent) as VirtualizingPanel;
CallEnsureGenerator(virtualizingPanel);
var index = currentParent.Items.IndexOf(node);
if (index < 0)
{
throw new InvalidOperationException("Node '" + node + "' cannot be fount in container");
}
CallBringIndexIntoView(virtualizingPanel, index);
newParent = currentParent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
}
if (newParent == null)
{
throw new InvalidOperationException("Tree view item cannot be found or created for node '" + node + "'");
}
if (node == newNode)
{
newParent.IsSelected = true;
newParent.BringIntoView();
break;
}
newParent.IsExpanded = true;
currentParent = newParent;
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue as Node;
}
#region Functions to get internal members using reflection
// Some functionality we need is hidden in internal members, so we use reflection to get them
#region ItemsControl.ItemsHost
static readonly PropertyInfo ItemsHostPropertyInfo = typeof(ItemsControl).GetProperty("ItemsHost", BindingFlags.Instance | BindingFlags.NonPublic);
private static Panel GetItemsHost(ItemsControl itemsControl)
{
Debug.Assert(itemsControl != null);
return ItemsHostPropertyInfo.GetValue(itemsControl, null) as Panel;
}
#endregion ItemsControl.ItemsHost
#region Panel.EnsureGenerator
private static readonly MethodInfo EnsureGeneratorMethodInfo = typeof(Panel).GetMethod("EnsureGenerator", BindingFlags.Instance | BindingFlags.NonPublic);
private static void CallEnsureGenerator(Panel panel)
{
Debug.Assert(panel != null);
EnsureGeneratorMethodInfo.Invoke(panel, null);
}
#endregion Panel.EnsureGenerator
#region VirtualizingPanel.BringIndexIntoView
private static readonly MethodInfo BringIndexIntoViewMethodInfo = typeof(VirtualizingPanel).GetMethod("BringIndexIntoView", BindingFlags.Instance | BindingFlags.NonPublic);
private static void CallBringIndexIntoView(VirtualizingPanel virtualizingPanel, int index)
{
Debug.Assert(virtualizingPanel != null);
BringIndexIntoViewMethodInfo.Invoke(virtualizingPanel, new object[] { index });
}
#endregion VirtualizingPanel.BringIndexIntoView
#endregion Functions to get internal members using reflection
}
Mit dieser Klasse können Sie schreiben XAML wie folgt aus:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyProject">
<Grid>
<TreeView ItemsSource="{Binding MyItems}"
ScrollViewer.CanContentScroll="True"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<i:Interaction.Behaviors>
<local:NodeTreeSelectionBehavior SelectedItem="{Binding MySelectedItem}" />
</i:Interaction.Behaviors>
</TreeView>
<Grid>
<UserControl>
Hallo, ich auch mit dem gleichen Problem steckte. Sie haben eine Lösung für dieses Problem? – akjoshi