263 Stimmen

Datenbindung an SelectedItem in einem WPF Treeview

Wie kann ich das Element abrufen, das in einer WPF-Treeview ausgewählt ist? Ich möchte dies in XAML zu tun, weil ich es zu binden möchten.

Man könnte meinen, es sei SelectedItem aber offenbar ist das gibt es nicht ist schreibgeschützt und daher unbrauchbar.

Das ist es, was ich tun möchte:

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

Ich möchte die SelectedItem zu einer Eigenschaft in meinem Modell.

Dies führt jedoch zu einer Fehlermeldung:

Die Eigenschaft "SelectedItem" ist schreibgeschützt und kann nicht über das Markup gesetzt werden.

Editar: Ok, so habe ich das Problem gelöst:

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

und in der Code-Hintergrunddatei meiner Xaml:

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

1voto

Nils Punkte 59

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

Die ursprüngliche Frage lautete "Abrufen" des ausgewählten Elements. Ich musste auch das ausgewählte Element in meinem Viewmodel "abrufen" (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 einer Abhängigkeitseigenschaft im Viewmodel (in meinem Fall vom Typ 'MyObject', da meine Baumansicht an ein Objekt vom Typ 'MyObject' gebunden ist)
  • Binden Sie vom Treeview.SelectedItem an diese Eigenschaft im Konstruktor der Ansicht (ja, das ist Code dahinter, aber es ist wahrscheinlich, dass Sie Ihren Datentext dort auch initiieren werden)

Viewmodel:

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

Ansicht-Konstruktor:

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

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

1voto

Kino101 Punkte 545

Ich schlage diese Lösung vor (die ich für die einfachste und speicherleckfreie halte), die perfekt für die Aktualisierung des ausgewählten Elements des ViewModels aus dem ausgewählten Element der Ansicht funktioniert.

Bitte beachten Sie, dass eine Änderung des ausgewählten Elements im ViewModel das ausgewählte Element in der View nicht aktualisiert.

public class TreeViewEx : TreeView
{
    public static readonly DependencyProperty SelectedItemExProperty = DependencyProperty.Register("SelectedItemEx", typeof(object), typeof(TreeViewEx), new FrameworkPropertyMetadata(default(object))
    {
        BindsTwoWayByDefault = true // Required in order to avoid setting the "BindingMode" from the XAML
    });

    public object SelectedItemEx
    {
        get => GetValue(SelectedItemExProperty);
        set => SetValue(SelectedItemExProperty, value);
    }

    protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
    {
        SelectedItemEx = e.NewValue;
    }
}

XAML-Verwendung

<l:TreeViewEx ItemsSource="{Binding Path=Items}" SelectedItemEx="{Binding Path=SelectedItem}" >

0voto

Eric Jorgensen Punkte 1172

(Einigen wir uns einfach darauf, dass TreeView in Bezug auf dieses Problem offensichtlich kaputt ist. Bindung an SelectedItem wäre offensichtlich gewesen. Seufzer )

Ich brauchte die Lösung, um richtig mit der IsSelected-Eigenschaft von TreeViewItem interagieren, so ist, wie ich es tat:

// 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>

0voto

Kino101 Punkte 545

Ich stelle Ihnen meine Lösung vor, die die folgenden Funktionen bietet:

  • Unterstützt 2 Wege Bindung

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

  • Keine TreeView-Unterklassenbildung

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

1/ Fügen Sie den folgenden Code in Ihr 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/ Beispiel für die Verwendung in Ihrer XAML-Datei

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

-1voto

Dave Punkte 2887

Ich weiß, es ist schon eine Weile her, seit dies gepostet wurde, aber FWIW Ich benutze Teleriks RadTreeView, und SelectedItem scheint gut zu funktionieren - entweder das Problem wurde in der Zwischenzeit behoben, oder Telerik haben um es für uns gearbeitet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X