WPF MVVM TreeView SelectedItem

Questo non può essere così difficile. TreeView in WPF non ti consente di impostare l’object SelectedItem, dicendo che la proprietà è ReadOnly. Ho il popolamento TreeView, anche l’aggiornamento quando si tratta di modifiche alla raccolta dei dati del database.

Ho solo bisogno di sapere quale elemento è selezionato. Sto usando MVVM, quindi non ci sono codebehind o variabili per fare riferimento alla vista ad albero di. Questa è l’unica soluzione che ho trovato, ma è un trucco ovvio, crea un altro elemento in XAML che usa il collegamento ElementName per impostarsi sull’elemento selezionato treeview, che deve poi associare anche il tuo Viewmodel. A questo vengono poste molte altre domande , ma non vengono fornite altre soluzioni operative.

Ho visto questa domanda , ma l’utilizzo della risposta fornita mi dà errori di compilazione, per qualche motivo non riesco ad aggiungere un riferimento al sdk di fusione System.Windows.Interactivity al mio progetto. Dice “error error system.windows non è stato precaricato” e non ho ancora capito come superarlo.

Per i punti bonus: perché diavolo Microsoft ha reso la proprietà SelectedItem di questo elemento ReadOnly?

Non dovresti davvero avere a che fare direttamente con la proprietà SelectedItem, associare IsSelected a una proprietà sul tuo viewmodel e tenere traccia dell’elemento selezionato lì.

Uno schizzo:

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

È ansible creare una proprietà associata che è associabile e ha un getter e setter:

 public class TreeViewHelper { private static Dictionary behaviors = new Dictionary(); 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; } } } 

Aggiungi la dichiarazione dello spazio dei nomi che contiene quella class al tuo XAML e associa come segue (local è come ho chiamato la dichiarazione dello spazio dei nomi):

  

Ora è ansible associare l’elemento selezionato e anche impostarlo nel modello di visualizzazione per modificarlo a livello di codice, qualora tale requisito dovesse mai presentarsi. Questo è, naturalmente, assumendo che si implementi INotifyPropertyChanged su quella particolare proprietà.

Un modo molto insolito ma abbastanza efficace per risolvere questo in un modo accettabile MVVM è il seguente:

  1. Crea un ContentControl collassato per la visibilità nella stessa vista di TreeView. Assegnare un nome appropriato e associare il suo contenuto ad alcune proprietà SelectedSomething in viewmodel. Questo ContentControl “manterrà” l’object selezionato e gestirà il suo binding, OneWayToSource;
  2. Ascolta SelectedItemChanged in TreeView e aggiungi un gestore in code-behind per impostare ContentControl.Content sull’elemento appena selezionato.

XAML:

   

Codice dietro:

  private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs 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; 

Utilizzare la modalità di associazione OneWayToSource . Questo non funziona. Vedi modifica.

Modifica : Sembra che questo sia un bug o un comportamento “di progettazione” di Microsoft, in base a questa domanda ; ci sono alcuni workaround pubblicati, però. Qualcuna di queste funzioni per TreeView?

Il problema di Microsoft Connect: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings

Postato da Microsoft il 1/10/2010 alle 14:46

Non possiamo farlo in WPF oggi, per lo stesso motivo per cui non possiamo supportare i binding su proprietà che non sono DependencyProperties. Lo stato di runtime peristanza di un’associazione viene mantenuto in un’espressione Binding, che viene memorizzata in EffectiveValueTable per il DependencyObject di destinazione. Quando la proprietà target non è un DP o il DP è di sola lettura, non c’è spazio per memorizzare BindingExpression.

È ansible che un giorno potremo scegliere di estendere le funzionalità di associazione a questi due scenari. Ci viene chiesto di loro abbastanza spesso. In altre parole, la tua richiesta è già presente nell’elenco delle funzioni da prendere in considerazione nelle versioni future.

Grazie per il vostro feedback

Ho deciso di utilizzare una combinazione di codice dietro e codice viewmodel. lo xaml è così:

  

Codice dietro

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

E nel viewmodel c’è un object TreeItemSelected che puoi quindi accedere a viewmodel.

È sempre ansible creare una proprietà Dependency che utilizza ICommand e ascoltare l’evento SelectedItemChanged in TreeView. Questo può essere un po ‘più facile del collegamento IsSelected, ma immagino che finirai per bind IsSelected comunque per altri motivi. Se desideri eseguire il binding su IsSelected, puoi sempre fare in modo che il tuo articolo invii un messaggio ogni volta che viene selezionato IsSelected. Quindi puoi ascoltare quei messaggi ovunque nel tuo programma.