webentwicklung-frage-antwort-db.com.de

WPF MVVM TreeView SelectedItem

Das kann nicht so schwierig sein. In der Strukturansicht in WPF können Sie das SelectedItem nicht festlegen, da die Eigenschaft ReadOnly lautet. Ich habe die TreeView bevölkern, sogar aktualisieren, wenn sich die datengebundene Sammlung ändert.

Ich muss nur wissen, welcher Artikel ausgewählt ist. Ich verwende MVVM, daher gibt es keinen Codebehind oder keine Variable, um auf die Strukturansicht zu verweisen. Dies ist die einzige Lösung Ich habe festgestellt, aber es ist ein offensichtlicher Hack, dass ein anderes Element in XAML erstellt wird, das die ElementName-Bindung verwendet, um sich selbst auf das ausgewählte Element der Strukturansicht festzulegen. Anschließend müssen Sie auch Ihr Viewmodel binden . Mehrere andere Fragen werden dazu gestellt, es werden jedoch keine anderen Arbeitslösungen angegeben.

Ich habe diese Frage gesehen, aber wenn ich die angegebene Antwort verwende, erhalte ich Kompilierungsfehler. Aus irgendeinem Grund kann ich meinem Projekt keinen Verweis auf die Mischung sdk System.Windows.Interactivity hinzufügen. Es heißt "unknown error system.windows is not preloaded" und ich habe noch nicht herausgefunden, wie ich daran vorbeikomme.

Für Bonuspunkte: Warum hat Microsoft die SelectedItem-Eigenschaft dieses Elements zu ReadOnly gemacht?

30
Kyeotic

Sie sollten sich nicht wirklich direkt mit der SelectedItem-Eigenschaft befassen müssen, sondern IsSelected an eine Eigenschaft in Ihrem Ansichtsmodell binden und dort den Überblick über das ausgewählte Element behalten.

Ein Sketch:

<TreeView ItemsSource="{Binding TreeData}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
    private static object _selectedItem = null;
    // This is public get-only here but you could implement a public setter which
    // also selects the item.
    // Also this should be moved to an instance property on a VM for the whole tree, 
    // otherwise there will be conflicts for more than one tree.
    public static object SelectedItem
    {
        get { return _selectedItem; }
        private set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                OnSelectedItemChanged();
            }
        }
    }

    static virtual void OnSelectedItemChanged()
    {
        // Raise event / do other things
    }

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_isSelected != value)
            {
                _isSelected = value;
                OnPropertyChanged("IsSelected");
                if (_isSelected)
                {
                    SelectedItem = this;
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
47
H.B.

Sie können eine angehängte Eigenschaft erstellen, die bindbar ist und einen Getter und Setter enthält:

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 (so habe ich die Namespace-Deklaration lokal benannt):

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

Jetzt können Sie das ausgewählte Element binden und es in Ihrem Ansichtsmodell so einstellen, dass es programmgesteuert geändert wird, falls dies jemals erforderlich wird. Dies setzt natürlich voraus, dass Sie INotifyPropertyChanged für diese bestimmte Eigenschaft implementieren.

12
Bas

Eine sehr ungewöhnliche, aber sehr effektive Möglichkeit, dies in einer für MVVM akzeptablen Weise zu lösen, ist die folgende:

  1. Erstellen Sie ein sichtbarkeitsreduziertes ContentControl in derselben Ansicht wie die TreeView. Nennen Sie es entsprechend und binden Sie seinen Inhalt an eine SelectedSomething -Eigenschaft in viewmodel. Dieses ContentControl "hält" das ausgewählte Objekt und verwaltet dessen Bindung OneWayToSource.
  2. Hören Sie das SelectedItemChanged in TreeView und fügen Sie einen Handler in CodeBehind hinzu, um Ihren ContentControl.Content auf das neu ausgewählte Element festzulegen.

XAML:

<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
    SelectedItemChanged="TreeView_SelectedItemChanged">

Code hinter:

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        SelectedItemHelper.Content = e.NewValue;
    }

ViewModel:

    public object SelectedObject  // Class is not actually "object"
    {
        get { return _selected_object; }
        set
        {
            _selected_object = value;
            RaisePropertyChanged(() => SelectedObject);
            Console.WriteLine(SelectedObject);
        }
    }
    object _selected_object;
6
heltonbiker

Verwenden Sie den Bindemodus OneWayToSource . Das geht nicht. Siehe Bearbeiten.

Bearbeiten : Laut dieser Frage ist dies anscheinend ein Fehler oder ein beabsichtigtes Verhalten von Microsoft. Es gibt jedoch einige Workarounds. Funktioniert eine dieser Funktionen für Ihr TreeView?

Das Microsoft Connect-Problem: https://connect.Microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings

Gepostet von Microsoft am 10.01.2010, um 14:46 Uhr

Wir können dies heute in WPF nicht tun, aus dem gleichen Grund können wir keine Bindungen für Eigenschaften unterstützen, die keine DependencyProperties sind. Der Laufzeitstatus einer Bindung pro Instanz wird in einer BindingExpression gespeichert, die wir in der EffectiveValueTable für das Ziel-DependencyObject speichern. Wenn die Zieleigenschaft kein DP ist oder der DP schreibgeschützt ist, kann der BindingExpression nicht gespeichert werden.

Es ist möglich, dass wir uns eines Tages dafür entscheiden, die Bindungsfunktionalität auf diese beiden Szenarien auszudehnen. Wir werden ziemlich oft danach gefragt. Mit anderen Worten, Ihre Anfrage ist bereits in unserer Liste der Funktionen enthalten, die in zukünftigen Versionen berücksichtigt werden müssen.

Vielen Dank für Ihr Feedback.

4
Aphex

Ich habe mich für eine Kombination aus Code Behind und ViewModel-Code entschieden. das xaml sieht so aus:

<TreeView 
                    Name="tvCountries"
                ItemsSource="{Binding Path=Countries}"
                ItemTemplate="{StaticResource ResourceKey=countryTemplate}"   
                    SelectedValuePath="Name"
                    SelectedItemChanged="tvCountries_SelectedItemChanged">

Code dahinter

private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
        if (vm != null)
        {
            var treeItem = sender as TreeView;
            vm.TreeItemSelected = treeItem.SelectedItem;
        }
    }

Und im Ansichtsmodell gibt es ein TreeItemSelected-Objekt, auf das Sie dann im Ansichtsmodell zugreifen können.

2
allan

Sie können jederzeit eine DependencyProperty erstellen, die ICommand verwendet, und das SelectedItemChanged-Ereignis in der TreeView abhören. Dies kann ein bisschen einfacher sein als das Binden von IsSelected, aber ich kann mir vorstellen, dass Sie IsSelected aus anderen Gründen trotzdem binden werden. Wenn Sie nur IsSelected binden möchten, können Sie jederzeit eine Nachricht an Ihren Artikel senden, wenn sich IsSelected ändert. Dann können Sie diese Nachrichten überall in Ihrem Programm abhören.

1
stricq