Comment sélectionner L'élément TreeView à partir du code
j'ai un treeview à trois niveaux. Comment puis-je sélectionner un élément de troisième niveau à partir de code? J'ai essayé une méthode mentionnée dans de nombreux blogs et sur stackoverflow mais elle semble fonctionner seulement pour le premier niveau (dbObject est null pour les éléments au-dessous du premier niveau).
voici le code que j'utilise pour sélectionner TreeViewItem. Dois-je raté quelque chose?
public static void SetSelectedItem(this TreeView control, object item)
{
try
{
var dObject = control.ItemContainerGenerator.ContainerFromItem(item);
//uncomment the following line if UI updates are unnecessary
((TreeViewItem)dObject).IsSelected = true;
MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select",
BindingFlags.NonPublic | BindingFlags.Instance);
selectMethod.Invoke(dObject, new object[] { true });
}
catch { }
}
6 réponses
une Autre option serait d'utiliser la liaison. Si vous avez un objet que vous utilisez la liaison avec le texte de chaque TreeViewItem
(par exemple), vous pouvez créer un style qui lie aussi le IsSelected
propriété:
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.Resources>
</TreeView>
Cela suppose que l'objet lié a un IsSelected
propriété de type bool
. Vous pouvez ensuite sélectionner un TreeViewItem
paramètre IsSelected
true
pour son objet correspondant.
La même approche peut être utilisée avec l' IsExpanded
bien à le contrôle quand un TreeViewItem
est élargi ou effondré.
après avoir essayé différentes solutions je suis venu à site. Zhou Yong montre comment programmer l'extension de tous les noeuds de TreeView. Il y a deux idées principales de sa méthode:
- ContainerFromItem retournera le conteneur seulement si l'article est l'enfant direct de l'élément. Dans TreeView cela signifie que seul le premier niveau de conteneur enfant sera retourné et vous devez appeler ContainerFromItem sur TreeViewItem enfant pour obtenir conteneur à partir du niveau suivant
- pour ContainerFromItem to work TreeViewItem visual children doit être créé et cela ne se produit que lorsque TreeViewItem est étendu. Cela signifie que pour sélectionner TreeViewItem tous les éléments précédant l'élément requis doivent être élargis. En pratique, cela signifie que nous devrons fournir le chemin vers l'élément que nous voulons sélectionner au lieu de simplement l'élément.
voici le code avec lequel j'ai fini
public static void SelectItem(this ItemsControl parentContainer, List<object> path)
{
var head = path.First();
var tail = path.GetRange(1, path.Count - 1);
var itemContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(head) as TreeViewItem;
if (itemContainer != null && itemContainer.Items.Count == 0)
{
itemContainer.IsSelected = true;
var selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance);
selectMethod.Invoke(itemContainer, new object[] { true });
}
else if (itemContainer != null)
{
itemContainer.IsExpanded = true;
if (itemContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
itemContainer.ItemContainerGenerator.StatusChanged += delegate
{
SelectItem(itemContainer, tail);
};
}
else
{
SelectItem(itemContainer, tail);
}
}
}
vous pouvez utiliser TreeView
extension, ce qui je trouve est une solution plus simple:
public static class TreeViewExtension
{
public static bool SetSelectedItem(this TreeView treeView, object item)
{
return SetSelected(treeView, item);
}
private static bool SetSelected(ItemsControl parent, object child)
{
if (parent == null || child == null)
return false;
TreeViewItem childNode = parent.ItemContainerGenerator
.ContainerFromItem(child) as TreeViewItem;
if (childNode != null)
{
childNode.Focus();
return childNode.IsSelected = true;
}
if (parent.Items.Count > 0)
{
foreach (object childItem in parent.Items)
{
ItemsControl childControl = parent
.ItemContainerGenerator
.ContainerFromItem(childItem)
as ItemsControl;
if (SetSelected(childControl, child))
return true;
}
}
return false;
}
}
pour plus d'informations, lire cet article de blog; http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/
Oui, la méthode ContainerFromItem ne donne rien, même si vous l'appelez du parent direct TreeViewItem.
vous pourriez avoir besoin de faire un peu de refonte. Si vous créez tout comme un TreeViewItem explicite, vous devriez être en mesure de garder une référence à elle et placer IsSelected sur elle.
Dans mon cas (j'ai eu le même problème), mais il a été unappropriate à l'utilisation de la liaison à IsSelected propriété de l'objet de Données et aussi je ne pourrais facilement obtenir le chemin d'accès à l'élément de l'arbre, donc le code suivant fait le travail parfaitement:
private void SelectTreeViewItem(object item)
{
try
{
var tvi = GetContainerFromItem(this.MainRegion, item);
tvi.Focus();
tvi.IsSelected = true;
var selectMethod =
typeof(TreeViewItem).GetMethod("Select",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
selectMethod.Invoke(tvi, new object[] { true });
}
catch { }
}
private TreeViewItem GetContainerFromItem(ItemsControl parent, object item)
{
var found = parent.ItemContainerGenerator.ContainerFromItem(item);
if (found == null)
{
for (int i = 0; i < parent.Items.Count; i++)
{
var childContainer = parent.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
TreeViewItem childFound = null;
if (childContainer != null && childContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
childContainer.ItemContainerGenerator.StatusChanged += (o, e) =>
{
childFound = GetContainerFromItem(childContainer, item);
};
}
else
{
childFound = GetContainerFromItem(childContainer, item);
}
if (childFound != null)
return childFound;
}
}
return found as TreeViewItem;
}
très tard à la partie avec ma réponse mais pour ceux qui veulent une solution pure MVVM cela peut être fait avec un déclencheur D'événement (pour mettre à jour la liaison lorsque l'utilisateur sélectionne un nouvel élément) et un déclencheur de données (pour mettre à jour l'élément sélectionné lorsque la valeur de la liaison change).
Pour que cela fonctionne, le principal ViewModel besoins items, une propriété de l'élément actuellement sélectionné et une commande de la propriété qui sera appelée lorsque l'élément actuellement sélectionné modifications:
public class MainViewModel : ViewModelBase
{
// the currently selected node, can be changed programmatically
private Node _CurrentNode;
public Node CurrentNode
{
get { return this._CurrentNode; }
set { this._CurrentNode = value; RaisePropertyChanged(() => this.CurrentNode); }
}
// called when the user selects a new node in the tree view
public ICommand SelectedNodeChangedCommand { get { return new RelayCommand<Node>(OnSelectedNodeChanged); } }
private void OnSelectedNodeChanged(Node node)
{
this.CurrentNode = node;
}
// list of items to display in the tree view
private ObservableCollection<Node> _Items;
public ObservableCollection<Node> Items
{
get { return this._Items; }
set { this._Items = value; RaisePropertyChanged(() => this.Items); }
}
}
Le TreeView besoins d'un événement de déclenchement d'appel SelectedNodeChangedCommand lors de la sélection, et un DataTrigger dans le TreeViewItem de style, de sorte que les éléments de contrôle d'être sélectionné lorsque la valeur de CurrentNode est modifié par programmation dans le code:
<TreeView x:Name="treeView" ItemsSource="{Binding Items}"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<TreeView.Resources>
<conv:EqualityConverter x:Key="EqualityConverter" />
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="IsSelected" Value="False" />
<Style.Triggers>
<!-- DataTrigger updates TreeViewItem selection when vm code changes CurrentNode -->
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type TreeView}}" Path="DataContext.CurrentNode" />
<Binding />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
<!-- *** HierarchicalDataTemplates go here *** -->
</TreeView.Resources>
<!-- EventTrigger invokes SelectedNodeChangedCommand when selection is changed by user interaction -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<cmd:EventToCommand Command="{Binding SelectedNodeChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
le DataTrigger fonctionne en détectant quand la valeur de CurrentNode correspond au noeud pour l'élément de la liste courante. Malheureusement DataTriggers ne peut pas lier leur valeur, il doit donc tester avec un EqualityConverter à la place qui ne fait qu'une simple comparaison:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}