250 votes

Liaison de données avec SelectedItem dans un Treeview WPF

Comment puis-je récupérer l'élément qui est sélectionné dans un WPF-treeview ? Je veux le faire en XAML, car je veux le lier.

Vous pourriez penser que c'est SelectedItem mais apparemment cela n'existe pas est en lecture seule et donc inutilisable.

C'est ce que je veux faire :

<TreeView ItemsSource="{Binding Path=Model.Clusters}" 
            ItemTemplate="{StaticResource ClusterTemplate}"
            SelectedItem="{Binding Path=Model.SelectedCluster}" />

Je veux lier le SelectedItem à une propriété de mon modèle.

Mais cela me donne l'erreur :

La propriété "SelectedItem" est en lecture seule et ne peut pas être définie à partir de la balise.

Editar: Ok, voilà comment j'ai résolu le problème :

<TreeView
          ItemsSource="{Binding Path=Model.Clusters}" 
          ItemTemplate="{StaticResource HoofdCLusterTemplate}"
          SelectedItemChanged="TreeView_OnSelectedItemChanged" />

et dans le codebehindfile de mon xaml :

private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    Model.SelectedCluster = (Cluster)e.NewValue;
}

57 votes

Mec, ça craint. Ça vient de me frapper aussi. Je suis venu ici en espérant trouver un moyen décent et je suis juste un idiot. C'est la première fois que je suis triste de ne pas être un idiot

9 votes

C'est vraiment nul et cela gâche le concept de liaison

0 votes

J'espère que cela pourra aider quelqu'un à se lier à un élément de l'arborescence sélectionné et à le rappeler sur une commande I. jacobaloysious.wordpress.com/2012/02/19/

259voto

Steve Greatrex Points 8466

Je réalise que cette question a déjà eu une réponse acceptée, mais j'ai fait ceci pour résoudre le problème. Il utilise une idée similaire à la solution de Delta, mais sans avoir besoin de sous-classer le TreeView :

public class BindableSelectedItemBehavior : Behavior<TreeView>
{
    #region SelectedItem Property

    public object SelectedItem
    {
        get { return (object)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));

    private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var item = e.NewValue as TreeViewItem;
        if (item != null)
        {
            item.SetValue(TreeViewItem.IsSelectedProperty, true);
        }
    }

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();

        this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (this.AssociatedObject != null)
        {
            this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        }
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        this.SelectedItem = e.NewValue;
    }
}

Vous pouvez ensuite l'utiliser dans votre XAML comme :

<TreeView>
    <e:Interaction.Behaviors>
        <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
    </e:Interaction.Behaviors>
</TreeView>

J'espère que cela aidera quelqu'un !

6 votes

Comme l'a souligné Brent, j'ai également dû ajouter Mode=TwoWay à la liaison. Je ne suis pas un "Blender" et je ne connaissais pas la classe Behavior<> de System.Windows.Interactivity. L'assemblage fait partie d'Expression Blend. Pour ceux qui ne veulent pas acheter/installer la version d'essai pour obtenir cet assemblage, vous pouvez télécharger le BlendSDK qui inclut System.Windows.Interactivity. BlendSDK 3 pour 3.5... Je pense que c'est BlendSDK 4 pour 4.0. Note : Ceci vous permet seulement d'obtenir l'élément sélectionné, mais ne vous permet pas de définir l'élément sélectionné.

0 votes

Voir ma réponse ci-dessous pour une solution sans dépendance à des assemblages externes. Et qui permet également de définir l'élément sélectionné.

5 votes

Vous pouvez également remplacer UIPropertyMetadata par FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged)) ;

46voto

Thomas Levesque Points 141081

Cette propriété existe : TreeView.SelectedItem

Mais il est en lecture seule, donc vous ne pouvez pas l'assigner par le biais d'une liaison, mais seulement le récupérer.

1 votes

J'accepte cette réponse, car j'y ai trouvé ce lien, qui m'a conduit à ma propre réponse : msdn.microsoft.com/fr/us/library/ms788714.aspx

3 votes

Je peux donc avoir ceci TreeView.SelectedItem affecter une propriété du modèle lorsque l'utilisateur sélectionne un élément (alias OneWayToSource ) ?

45voto

Bas Brekelmans Points 13799

Répondez avec des propriétés attachées et sans dépendances externes, si jamais le besoin s'en fait sentir !

Vous pouvez créer une propriété attachée qui peut être liée et qui possède un getter et un setter :

public class TreeViewHelper
{
    private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();

    public static object GetSelectedItem(DependencyObject obj)
    {
        return (object)obj.GetValue(SelectedItemProperty);
    }

    public static void SetSelectedItem(DependencyObject obj, object value)
    {
        obj.SetValue(SelectedItemProperty, value);
    }

    // Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));

    private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is TreeView))
            return;

        if (!behaviors.ContainsKey(obj))
            behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));

        TreeViewSelectedItemBehavior view = behaviors[obj];
        view.ChangeSelectedItem(e.NewValue);
    }

    private class TreeViewSelectedItemBehavior
    {
        TreeView view;
        public TreeViewSelectedItemBehavior(TreeView view)
        {
            this.view = view;
            view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
        }

        internal void ChangeSelectedItem(object p)
        {
            TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
            item.IsSelected = true;
        }
    }
}

Ajoutez la déclaration d'espace de nom contenant cette classe à votre XAML et liez-la comme suit (local est le nom que j'ai donné à la déclaration d'espace de nom) :

        <TreeView ItemsSource="{Binding Path=Root.Children}" local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}">

    </TreeView>

Vous pouvez maintenant lier l'élément sélectionné et le définir dans votre modèle de vue afin de le modifier de manière programmatique, si cela s'avère nécessaire. Cela suppose, bien sûr, que vous implémentez INotifyPropertyChanged sur cette propriété particulière.

0 votes

La liaison bidirectionnelle ne semble pas fonctionner comme la distribution. TreeViewItem à sélectionner dans le TreeViewSelectedItemBehavior est (d'après mon expérience) toujours null . Cela peut être dû au fait que ItemContainerGenerator ne renvoie que les éléments qui ont été générés visuellement, mais je ne suis pas vraiment sûr. +1 pour le reste en tout cas.

5 votes

+1, meilleure réponse de ce fil de discussion, je pense. Pas de dépendance à System.Windows.Interactivity, et permet une liaison bidirectionnelle (réglage programmatique dans un environnement MVVM). Parfait.

0 votes

+1, bien que trop tard, un changement mineur est nécessaire. Hériter de la classe Helper avec DependencyObject et avoir une propriété pour SelectedItem.

39voto

Delta Points 794

Eh bien, j'ai trouvé une solution. Elle déplace le désordre, de sorte que MVVM fonctionne.

Ajoutez d'abord cette classe :

public class ExtendedTreeView : TreeView
{
    public ExtendedTreeView()
        : base()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(___ICH);
    }

    void ___ICH(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        if (SelectedItem != null)
        {
            SetValue(SelectedItem_Property, SelectedItem);
        }
    }

    public object SelectedItem_
    {
        get { return (object)GetValue(SelectedItem_Property); }
        set { SetValue(SelectedItem_Property, value); }
    }
    public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null));
}

et ajoutez ceci à votre xaml :

 <local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
 .....
 </local:ExtendedTreeView>

3 votes

C'est la SEULE chose qui s'est approchée de l'efficacité pour moi jusqu'à présent. J'aime vraiment cette solution.

1 votes

Je ne sais pas pourquoi mais cela n'a pas fonctionné pour moi :( J'ai réussi à obtenir l'élément sélectionné à partir de l'arbre mais pas l'inverse - pour changer l'élément sélectionné à partir de l'extérieur de l'arbre.

0 votes

Il serait un peu plus judicieux de définir la propriété de dépendance comme BindsTwoWayByDefault, ce qui éviterait de devoir spécifier TwoWay dans le XAML.

19voto

bstoney Points 3010

Ceci peut être réalisé d'une manière plus "sympa" en utilisant uniquement la liaison et l'EventToCommand de la bibliothèque MVVM Light de GalaSoft. Dans votre VM, ajoutez une commande qui sera appelée lorsque l'élément sélectionné est modifié, et initialisez la commande pour qu'elle effectue l'action nécessaire. Dans cet exemple, j'ai utilisé une RelayCommand et je vais simplement définir la propriété SelectedCluster.

public class ViewModel
{
    public ViewModel()
    {
        SelectedClusterChanged = new RelayCommand<Cluster>( c => SelectedCluster = c );
    }

    public RelayCommand<Cluster> SelectedClusterChanged { get; private set; } 

    public Cluster SelectedCluster { get; private set; }
}

Ajoutez ensuite le comportement EventToCommand dans votre xaml. C'est très facile avec blend.

<TreeView
      x:Name="lstClusters"
      ItemsSource="{Binding Path=Model.Clusters}" 
      ItemTemplate="{StaticResource HoofdCLusterTemplate}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SelectedClusterChanged}" CommandParameter="{Binding ElementName=lstClusters,Path=SelectedValue}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TreeView>

0 votes

C'est une solution intéressante, surtout si vous utilisez déjà la boîte à outils MvvmLight. Elle ne résout cependant pas le problème de la mise en place du nœud sélectionné et de la mise à jour de la sélection par le treeview.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X