it-swarm.com.de

Datenbindung an SelectedItem in einer WPF-Baumansicht

Wie kann ich das in einer WPF-Baumansicht ausgewählte Element abrufen? Ich möchte dies in XAML tun, weil ich es binden möchte.

Sie könnten denken, dass es SelectedItem ist, aber anscheinend das ist nicht vorhanden ist schreibgeschützt und daher unbrauchbar.

Das möchte ich tun:

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

Ich möchte die SelectedItem an eine Eigenschaft in meinem Modell binden.

Aber das gibt mir den Fehler:

Die Eigenschaft 'SelectedItem' ist schreibgeschützt und kann nicht über Markup festgelegt werden.

Edit: Ok, so habe ich das gelöst:

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

und in der codebehindfile meines xaml:

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

Mir ist klar, dass bereits eine Antwort akzeptiert wurde, aber ich habe dies zur Lösung des Problems zusammengestellt. Es verwendet eine ähnliche Idee wie Deltas Lösung, ohne jedoch die TreeView-Klasse untergliedern zu müssen:

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;
    }
}

Sie können dies in Ihrer XAML folgendermaßen verwenden:

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

Hoffentlich hilft es jemandem!

217
Steve Greatrex

Diese Eigenschaft ist vorhanden: TreeView.SelectedItem

Es ist jedoch schreibgeschützt, daher können Sie es nicht über eine Bindung zuweisen, sondern nur abrufen

43
Thomas Levesque

Nun, ich habe eine Lösung gefunden. Es verschiebt das Durcheinander, so dass MVVM funktioniert.

Fügen Sie zuerst diese Klasse hinzu:

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));
}

und füge dies zu deinem xaml hinzu:

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

Antworten Sie mit angefügten Eigenschaften und ohne externe Abhängigkeiten, falls dies jemals erforderlich sein sollte!

Sie können eine angefügte Eigenschaft erstellen, die bindbar ist und über einen Getter und Setter verfügt:

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;
        }
    }
}

Fügen Sie die Namespace-Deklaration, die diese Klasse enthält, zu Ihrer XAML hinzu und binden Sie sie wie folgt (lokal habe ich die Namespace-Deklaration genannt):

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

    </TreeView>

Jetzt können Sie das ausgewählte Element binden und es auch in Ihrem Ansichtsmodell festlegen, um es programmgesteuert zu ändern, falls diese Anforderung jemals auftritt. Dies setzt natürlich voraus, dass Sie INotifyPropertyChanged für diese bestimmte Eigenschaft implementieren.

34
Bas

Es antwortet etwas mehr, als das OP erwartet ... Aber ich hoffe, es könnte wenigstens jemandem helfen.

Wenn Sie eine ICommand ausführen möchten, wenn die SelectedItem geändert wurde, können Sie einen Befehl für ein Ereignis binden und die Verwendung einer Eigenschaft SelectedItem in der ViewModel ist nicht mehr erforderlich.

Um dies zu tun:

1- Verweis auf System.Windows.Interactivity hinzufügen

xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"

2- Binden Sie den Befehl an das Ereignis SelectedItemChanged

<TreeView x:Name="myTreeView" Margin="1"
            ItemsSource="{Binding Directories}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <i:InvokeCommandAction Command="{Binding SomeCommand}"
                                   CommandParameter="
                                            {Binding ElementName=myTreeView
                                             ,Path=SelectedItem}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <TreeView.ItemTemplate>
           <!-- ... -->
    </TreeView.ItemTemplate>
</TreeView>
20
JiBéDoublevé

Dies kann auf eine "schönere" Art und Weise erfolgen, indem nur die Bindung und der EventToCommand der GalaSoft MVVM Light-Bibliothek verwendet wird. Fügen Sie in Ihrem VM einen Befehl hinzu, der aufgerufen wird, wenn das ausgewählte Element geändert wird, und initialisieren Sie den Befehl, um die erforderliche Aktion auszuführen. In diesem Beispiel habe ich einen RelayCommand verwendet und werde einfach die SelectedCluster-Eigenschaft festlegen.

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; }
}

Fügen Sie dann das EventToCommand-Verhalten in Ihrem xaml hinzu. Dies ist sehr einfach bei der Mischung.

<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>
18
bstoney

Alles zu kompliziert ... Gehen Sie mit Caliburn Micro (http://caliburnmicro.codeplex.com/)

Aussicht:

<TreeView Micro:Message.Attach="[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]" />

ViewModel:

public void SetSelectedItem(YourNodeViewModel item) {}; 
13
Devgig

Ich bin auf diese Seite gestoßen, auf der Suche nach der gleichen Antwort wie der ursprüngliche Autor, und beweist, dass es immer mehr als einen Weg gibt, die Lösung für mich war sogar noch einfacher als die hier gegebenen Antworten, also dachte ich, ich könnte auch hinzufügen auf den Haufen.

Die Motivation für die Bindung ist es, Nice & MVVM zu behalten. Die wahrscheinliche Verwendung von ViewModel besteht darin, eine Eigenschaft mit einem Namen wie "CurrentThingy" zu haben, und an anderer Stelle ist der DataContext an eine andere Sache an "CurrentThingy" gebunden.

Anstatt zusätzliche Schritte durchzuführen (z. B. benutzerdefiniertes Verhalten, Drittanbieter-Kontrolle), um eine Nice-Bindung von der Baumansicht zu meinem Modell und dann von etwas anderem zu meinem Modell zu unterstützen, bestand meine Lösung darin, die einfache Bindung der anderen Elemente zu verwenden TreeView.SelectedItem, anstatt das andere an mein ViewModel zu binden, wodurch die zusätzliche Arbeit übersprungen wird.

XAML:

<TreeView x:Name="myTreeView" ItemsSource="{Binding MyThingyCollection}">
.... stuff
</TreeView>

<!-- then.. somewhere else where I want to see the currently selected TreeView item: -->

<local:MyThingyDetailsView 
       DataContext="{Binding ElementName=myTreeView, Path=SelectedItem}" />

Dies ist natürlich gut für das Lesen des aktuell ausgewählten Elements, aber nicht für die Einstellung. Das ist alles, was ich brauche. 

8
Wes

Möglicherweise können Sie auch die TreeViewItem.IsSelected-Eigenschaft verwenden

5
nabeelfarid

Ich habe alle Lösungen dieser Fragen ausprobiert. Niemand hat mein Problem vollständig gelöst. Ich denke, es ist besser, eine solche vererbte Klasse mit der neu definierten Eigenschaft SelectedItem zu verwenden. Es funktioniert perfekt, wenn Sie ein Baumelement aus der GUI auswählen und diesen Eigenschaftswert in Ihrem Code festlegen

public class TreeViewEx : TreeView
{
    public TreeViewEx()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeViewEx_SelectedItemChanged);
    }

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

    #region SelectedItem

    /// <summary>
    /// Gets or Sets the SelectedItem possible Value of the TreeViewItem object.
    /// </summary>
    public new object SelectedItem
    {
        get { return this.GetValue(TreeViewEx.SelectedItemProperty); }
        set { this.SetValue(TreeViewEx.SelectedItemProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public new static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewEx),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedItemProperty_Changed));

    static void SelectedItemProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        TreeViewEx targetObject = dependencyObject as TreeViewEx;
        if (targetObject != null)
        {
            TreeViewItem tvi = targetObject.FindItemNode(targetObject.SelectedItem) as TreeViewItem;
            if (tvi != null)
                tvi.IsSelected = true;
        }
    }                                               
    #endregion SelectedItem   

    public TreeViewItem FindItemNode(object item)
    {
        TreeViewItem node = null;
        foreach (object data in this.Items)
        {
            node = this.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
            if (node != null)
            {
                if (data == item)
                    break;
                node = FindItemNodeInChildren(node, item);
                if (node != null)
                    break;
            }
        }
        return node;
    }

    protected TreeViewItem FindItemNodeInChildren(TreeViewItem parent, object item)
    {
        TreeViewItem node = null;
        bool isExpanded = parent.IsExpanded;
        if (!isExpanded) //Can't find child container unless the parent node is Expanded once
        {
            parent.IsExpanded = true;
            parent.UpdateLayout();
        }
        foreach (object data in parent.Items)
        {
            node = parent.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
            if (data == item && node != null)
                break;
            node = FindItemNodeInChildren(node, item);
            if (node != null)
                break;
        }
        if (node == null && parent.IsExpanded != isExpanded)
            parent.IsExpanded = isExpanded;
        if (node != null)
            parent.IsExpanded = true;
        return node;
    }
} 
3
Evgeny Bechkalo

Sie können auch eine XAML-bindbare SelectedItem-Eigenschaft erstellen, ohne Interaction.Behaviors zu verwenden.

public static class BindableSelectedItemHelper
{
    #region Properties

    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(BindableSelectedItemHelper),
        new FrameworkPropertyMetadata(null, OnSelectedItemPropertyChanged));

    public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(BindableSelectedItemHelper), new PropertyMetadata(false, Attach));

    private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(BindableSelectedItemHelper));

    #endregion

    #region Implementation

    public static void SetAttach(DependencyObject dp, bool value)
    {
        dp.SetValue(AttachProperty, value);
    }

    public static bool GetAttach(DependencyObject dp)
    {
        return (bool)dp.GetValue(AttachProperty);
    }

    public static string GetSelectedItem(DependencyObject dp)
    {
        return (string)dp.GetValue(SelectedItemProperty);
    }

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

    private static bool GetIsUpdating(DependencyObject dp)
    {
        return (bool)dp.GetValue(IsUpdatingProperty);
    }

    private static void SetIsUpdating(DependencyObject dp, bool value)
    {
        dp.SetValue(IsUpdatingProperty, value);
    }

    private static void Attach(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        TreeListView treeListView = sender as TreeListView;
        if (treeListView != null)
        {
            if ((bool)e.OldValue)
                treeListView.SelectedItemChanged -= SelectedItemChanged;

            if ((bool)e.NewValue)
                treeListView.SelectedItemChanged += SelectedItemChanged;
        }
    }

    private static void OnSelectedItemPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        TreeListView treeListView = sender as TreeListView;
        if (treeListView != null)
        {
            treeListView.SelectedItemChanged -= SelectedItemChanged;

            if (!(bool)GetIsUpdating(treeListView))
            {
                foreach (TreeViewItem item in treeListView.Items)
                {
                    if (item == e.NewValue)
                    {
                        item.IsSelected = true;
                        break;
                    }
                    else
                       item.IsSelected = false;                        
                }
            }

            treeListView.SelectedItemChanged += SelectedItemChanged;
        }
    }

    private static void SelectedItemChanged(object sender, RoutedEventArgs e)
    {
        TreeListView treeListView = sender as TreeListView;
        if (treeListView != null)
        {
            SetIsUpdating(treeListView, true);
            SetSelectedItem(treeListView, treeListView.SelectedItem);
            SetIsUpdating(treeListView, false);
        }
    }
    #endregion
}

Sie können dies in Ihrer XAML folgendermaßen verwenden:

<TreeView  helper:BindableSelectedItemHelper.Attach="True" 
           helper:BindableSelectedItemHelper.SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
3

Meine Anforderung war eine PRISM-MVVM-basierte Lösung, bei der eine TreeView erforderlich war und das gebundene Objekt vom Typ Collection <> ist und daher HierarchicalDataTemplate benötigt. Das standardmäßige BindableSelectedItemBehavior kann das untergeordnete TreeViewItem nicht identifizieren. Damit es in diesem Szenario funktioniert.

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 behavior = sender as BindableSelectedItemBehavior;
        if (behavior == null) return;
        var tree = behavior.AssociatedObject;
        if (tree == null) return;
        if (e.NewValue == null)
            foreach (var item in tree.Items.OfType<TreeViewItem>())
                item.SetValue(TreeViewItem.IsSelectedProperty, false);
        var treeViewItem = e.NewValue as TreeViewItem;
        if (treeViewItem != null)
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
        else
        {
            var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            if (itemsHostProperty == null) return;
            var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;
            if (itemsHost == null) return;
            foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
            {
                if (WalkTreeViewItem(item, e.NewValue)) 
                    break;
            }
        }
    }

    public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue)
    {
        if (treeViewItem.DataContext == selectedValue)
        {
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
            treeViewItem.Focus();
            return true;
        }
        var itemsHostProperty = treeViewItem.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        if (itemsHostProperty == null) return false;
        var itemsHost = itemsHostProperty.GetValue(treeViewItem, null) as Panel;
        if (itemsHost == null) return false;
        foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
        {
            if (WalkTreeViewItem(item, selectedValue))
                break;
        }
        return false;
    }
    #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;
    }
}

Dadurch können alle Elemente unabhängig vom Level durchlaufen werden.

2

Ich schlage eine Ergänzung des Verhaltens von Steve Greatrex vor. Sein Verhalten spiegelt keine Änderungen der Quelle wider, da es sich möglicherweise nicht um eine Auflistung von TreeViewItems handelt. Es ist also eine Frage des TreeViewItem in der Baumstruktur, welcher Datenkontext der ausgewählte Wert aus der Quelle ist .. Die Baumansicht hat eine geschützte Eigenschaft namens "ItemsHost", die die TreeViewItem-Auflistung enthält. Wir können es durch Nachdenken erhalten und durch den Baum gehen, um nach dem ausgewählten Element zu suchen. 

private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var behavior = sender as BindableSelectedItemBehaviour;

        if (behavior == null) return;

        var tree = behavior.AssociatedObject;

        if (tree == null) return;

        if (e.NewValue == null) 
            foreach (var item in tree.Items.OfType<TreeViewItem>())
                item.SetValue(TreeViewItem.IsSelectedProperty, false);

        var treeViewItem = e.NewValue as TreeViewItem; 
        if (treeViewItem != null)
        {
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
        }
        else
        {
            var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            if (itemsHostProperty == null) return;

            var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;

            if (itemsHost == null) return;

            foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
                if (WalkTreeViewItem(item, e.NewValue)) break;
        }
    }

    public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue) {
        if (treeViewItem.DataContext == selectedValue)
        {
            treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
            treeViewItem.Focus();
            return true;
        }

        foreach (var item in treeViewItem.Items.OfType<TreeViewItem>())
            if (WalkTreeViewItem(item, selectedValue)) return true;

        return false;
    }

Auf diese Weise funktioniert das Verhalten für wechselseitige Bindungen. Alternativ ist es möglich, die ItemsHost-Erfassung in die OnAttached-Methode des Verhaltens zu verschieben, wodurch der Aufwand für die Verwendung der Reflektion bei jeder Aktualisierung der Bindung gespart wird. 

2
Arthur Nunes

WPF MVVM TreeView SelectedItem

... ist eine bessere Antwort, erwähnt jedoch keine Möglichkeit, das SelectedItem im ViewModel abzurufen/festzulegen. 

  1. Fügen Sie Ihrem ItemViewModel eine boolesche IsSelected-Eigenschaft hinzu, und binden Sie sie in einem Style Setter für das TreeViewItem. 
  2. Fügen Sie Ihrem ViewModel eine SelectedItem-Eigenschaft hinzu, die als DataContext für die TreeView verwendet wird. Dies ist das fehlende Teil in der obigen Lösung.
 'ItemVM ...
 Öffentliche Eigenschaft IsSelected As Boolean 
 Erhalten
 Return _func.SelectedNode ist ich 
 End Get 
 Setze (Wert As Boolean) 
 Wenn IsSelected-Wert Then 
 _func.SelectedNode = If (Wert, Ich, Nichts) 
 End If 
 RaisePropertyChange () 
 End Set 
 End-Eigenschaft 
 'TreeVM ...
 Öffentliche Eigenschaft SelectedItem As ItemVM 
 Erhalten
 Gibt _selectedItem .__ zurück. End Get 
 Set (Wert als ItemVM) 
 Wenn _selectedItem Wert ist, dann 
 Rückkehr
 End If 
 Dim prev = _selectedItem 
 _selectedItem = Wert 
 Wenn prev IsNot Nothing 
 prev.IsSelected = False 
 End If 
 Wenn _selectedItemNicht nichts dann ist 
 _selectedItem.IsSelected = True 
 End If 
 End Set 
 End-Eigenschaft 
<TreeView ItemsSource="{Binding Path=TreeVM}" 
          BorderBrush="Transparent">
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>
2
JustinMichel

Dies kann auch mit der IsSelected-Eigenschaft des TreeView-Elements erfolgen. So habe ich es geschafft,

public delegate void TreeviewItemSelectedHandler(TreeViewItem item);
public class TreeViewItem
{      
  public static event TreeviewItemSelectedHandler OnItemSelected = delegate { };
  public bool IsSelected 
  {
    get { return isSelected; }
    set 
    { 
      isSelected = value;
      if (value)
        OnItemSelected(this);
    }
  }
}

Abonnieren Sie dann im ViewModel, das die Daten enthält, an die TreeView gebunden ist, das Ereignis in der TreeViewItem-Klasse.

TreeViewItem.OnItemSelected += TreeViewItemSelected;

Und schließlich implementieren Sie diesen Handler in demselben ViewModel,

private void TreeViewItemSelected(TreeViewItem item)
{
  //Do something
}

Und die Bindung natürlich

<Setter Property="IsSelected" Value="{Binding IsSelected}" />    
1
Fahad Owais

Ich weiß, dieser Thread ist 10 Jahre alt, aber das Problem besteht immer noch ....

Die ursprüngliche Frage lautete, das ausgewählte Objekt abzurufen. Ich musste das ausgewählte Element auch in mein Ansichtsmodell "übernehmen" (nicht festlegen). Von allen Antworten in diesem Thread ist die von "Wes" die einzige, die das Problem anders angeht: Wenn Sie das "Ausgewählte Element" als Ziel für die Datenbindung verwenden können, verwenden Sie es als Quelle für die Datenbindung. Wes hat es mit einer anderen view-Eigenschaft gemacht, ich werde es mit einer viewmodel-Eigenschaft machen:

Wir brauchen zwei Dinge:

  • Erstellen Sie eine Abhängigkeitseigenschaft im Ansichtsmodell (in meinem Fall vom Typ 'MyObject', da meine Strukturansicht an ein Objekt vom Typ 'MyObject' gebunden ist).
  • Binden Sie von Treeview.SelectedItem an diese Eigenschaft im Konstruktor der Ansicht (ja, das ist Code dahinter, aber wahrscheinlich werden Sie dort auch Ihren Datenkontext initiieren).

Ansichtsmodell:

public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register("SelectedTreeViewItem", typeof(MyObject), typeof(MyViewModel), new PropertyMetadata(OnSelectedTreeViewItemChanged));

    private static void OnSelectedTreeViewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as MyViewModel).OnSelectedTreeViewItemChanged(e);
    }

    private void OnSelectedTreeViewItemChanged(DependencyPropertyChangedEventArgs e)
    {
        //do your stuff here
    }

    public MyObject SelectedWorkOrderTreeViewItem
    {
        get { return (MyObject)GetValue(SelectedTreeViewItemProperty); }
        set { SetValue(SelectedTreeViewItemProperty, value); }
    }

Konstruktor anzeigen:

Binding binding = new Binding("SelectedItem")
        {
            Source = treeView, //name of tree view in xaml
            Mode = BindingMode.OneWay
        };

        BindingOperations.SetBinding(DataContext, MyViewModel.SelectedTreeViewItemProperty, binding);
1
Nils

Nachdem ich einen Tag im Internet studiert hatte, fand ich meine eigene Lösung für die Auswahl eines Elements, nachdem eine normale Baumansicht in einer normalen WPF/C # - Umgebung erstellt wurde

private void BuildSortTree(int sel)
        {
            MergeSort.Items.Clear();
            TreeViewItem itTemp = new TreeViewItem();
            itTemp.Header = SortList[0];
            MergeSort.Items.Add(itTemp);
            TreeViewItem prev;
            itTemp.IsExpanded = true;
            if (0 == sel) itTemp.IsSelected= true;
            prev = itTemp;
            for(int i = 1; i<SortList.Count; i++)
            {

                TreeViewItem itTempNEW = new TreeViewItem();
                itTempNEW.Header = SortList[i];
                prev.Items.Add(itTempNEW);
                itTempNEW.IsExpanded = true;
                if (i == sel) itTempNEW.IsSelected = true;
                prev = itTempNEW ;
            }
        }
1
karma

Ich bringe Ihnen meine Lösung mit den folgenden Funktionen:

  • Unterstützt 2 Wege zum Binden

  • Automatische Aktualisierung der TreeViewItem.IsSelected-Eigenschaften (entsprechend dem SelectedItem)

  • Keine TreeView-Unterklasse

  • An ViewModel gebundene Elemente können von beliebigem Typ sein (auch null)

1/Fügen Sie den folgenden Code in Ihre CS ein:

public class BindableSelectedItem
{
    public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached(
        "SelectedItem", typeof(object), typeof(BindableSelectedItem), new PropertyMetadata(default(object), OnSelectedItemPropertyChangedCallback));

    private static void OnSelectedItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as TreeView;
        if (treeView != null)
        {
            BrowseTreeViewItems(treeView, tvi =>
            {
                tvi.IsSelected = tvi.DataContext == e.NewValue;
            });
        }
        else
        {
            throw new Exception("Attached property supports only TreeView");
        }
    }

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

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

    public static void BrowseTreeViewItems(TreeView treeView, Action<TreeViewItem> onBrowsedTreeViewItem)
    {
        var collectionsToVisit = new System.Collections.Generic.List<Tuple<ItemContainerGenerator, ItemCollection>> { new Tuple<ItemContainerGenerator, ItemCollection>(treeView.ItemContainerGenerator, treeView.Items) };
        var collectionIndex = 0;
        while (collectionIndex < collectionsToVisit.Count)
        {
            var itemContainerGenerator = collectionsToVisit[collectionIndex].Item1;
            var itemCollection = collectionsToVisit[collectionIndex].Item2;
            for (var i = 0; i < itemCollection.Count; i++)
            {
                var tvi = itemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
                if (tvi == null)
                {
                    continue;
                }

                if (tvi.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
                {
                    collectionsToVisit.Add(new Tuple<ItemContainerGenerator, ItemCollection>(tvi.ItemContainerGenerator, tvi.Items));
                }

                onBrowsedTreeViewItem(tvi);
            }

            collectionIndex++;
        }
    }

}

2/Anwendungsbeispiel in Ihrer XAML-Datei

<TreeView myNS:BindableSelectedItem.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />  
1
Kino101

Wenn die XAML hat 

<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />

Was ist daran falsch, diesen Artikel in der Liste zu finden? Ich benutze auch 

<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />

Um sicherzustellen, dass bei der Einstellung von IsSelected = true in VM die Eltern erweitert werden.

0
Jym

(Wir sind uns alle einig, dass TreeView in Bezug auf dieses Problem offensichtlich aufgegeben wurde. Die Bindung an SelectedItem wäre offensichtlich gewesen. Seufz)

Ich brauchte die Lösung, um richtig mit der IsSelected-Eigenschaft von TreeViewItem zu interagieren. So habe ich es gemacht:

// the Type CustomThing needs to implement IsSelected with notification
// for this to work.
public class CustomTreeView : TreeView
{
    public CustomThing SelectedCustomThing
    {
        get
        {
            return (CustomThing)GetValue(SelectedNode_Property);
        }
        set
        {
            SetValue(SelectedNode_Property, value);
            if(value != null) value.IsSelected = true;
        }
    }

    public static DependencyProperty SelectedNode_Property =
        DependencyProperty.Register(
            "SelectedCustomThing",
            typeof(CustomThing),
            typeof(CustomTreeView),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.None,
                SelectedNodeChanged));

    public CustomTreeView(): base()
    {
        this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(SelectedItemChanged_CustomHandler);
    }

    void SelectedItemChanged_CustomHandler(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        SetValue(SelectedNode_Property, SelectedItem);
    }

    private static void SelectedNodeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as CustomTreeView;
        var newNode = e.NewValue as CustomThing;

        treeView.SelectedCustomThing = (CustomThing)e.NewValue;
    }
}

Mit dieser XAML:

<local:CustonTreeView ItemsSource="{Binding TreeRoot}" 
    SelectedCustomThing="{Binding SelectedNode,Mode=TwoWay}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
        </Style>
    </TreeView.ItemContainerStyle>
</local:CustonTreeView>
0
Eric Jorgensen