J'ai un treeview à trois niveaux. Comment sélectionner un élément du troisième niveau du code? J'ai essayé une méthode mentionnée dans de nombreux blogs et sur stackoverflow mais elle semble fonctionner uniquement pour le premier niveau (dbObject est null pour les éléments situés au-dessous du premier niveau).
Voici le code que j'utilise pour sélectionner TreeViewItem. Est-ce que quelque chose me manque?
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 { }
}
Une autre option serait d'utiliser la liaison. Si vous utilisez une liaison avec un objet pour obtenir le texte de chaque TreeViewItem
(par exemple), vous pouvez créer un style qui lie également la propriété IsSelected
:
<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é possède une propriété IsSelected
de type bool
. Vous pouvez ensuite sélectionner TreeViewItem
en définissant IsSelected
sur true
pour son objet correspondant.
La même approche peut être utilisée avec la propriété IsExpanded
pour contrôler le moment où une TreeViewItem
est développée ou réduite.
Vous pouvez utiliser l'extension TreeView
suivante, qui 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, lisez cet article de blog; http://decompile.fr/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/
Après avoir essayé différentes solutions, je suis venu sur this site. Zhou Yong montre comment développer par programmation tous les nœuds de TreeView. Il y a deux idées principales dans sa méthode:
Voici le code que j'ai fini avec
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);
}
}
}
Dans mon cas (j'avais le même problème) mais il était inapproprié d'utiliser la liaison à la propriété IsSelected de l'objet Data et je ne pouvais pas non plus obtenir facilement le chemin d'accès à l'élément de l'arbre.
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 dans la soirée avec ma réponse, mais pour ceux qui souhaitent une solution MVVM pure, ceci 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 des modifications de liaison).
Pour que cela fonctionne, ViewModel principal a besoin des éléments, d'une propriété pour l'élément actuellement sélectionné et d'une propriété de commande qui sera appelée lorsque l'élément sélectionné sera modifié:
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); }
}
}
TreeView a besoin d'un déclencheur d'événement pour appeler SelectedNodeChangedCommand lorsque la sélection est modifiée et d'un DataTrigger dans le style TreeViewItem afin que les éléments de contrôle soient sélectionnés lorsque la valeur de CurrentNode est modifiée de manière programmable 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 détecte quand la valeur de CurrentNode correspond au nœud pour l'élément de la liste en cours. Malheureusement, DataTriggers ne peut pas lier sa valeur. Il doit donc effectuer un test avec un convertisseur EqualityConverter qui effectue simplement une 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();
}
}
Oui, la méthode ContainerFromItem ne restitue rien, même si vous l'appelez à partir de TreeViewItem parent direct.
Vous devrez peut-être un peu de refonte. Si vous créez tout en tant que TreeViewItem explicite, vous devriez pouvoir y garder une référence et y définir IsSelected.