WPF / MVVM – come gestire il doppio clic su TreeViewItems nel ViewModel?

(Nota: questo è un re-post in quanto la mia prima domanda è stata postata sotto il titolo sbagliato: Here Sorry!)

Ho una vista ad albero WPF standard e ho elementi associati per visualizzare le classi del modello.

Ora desidero gestire il comportamento quando si fa doppio clic sugli elementi (apertura di documenti in stile visual-studio).

Posso far triggersre al gestore eventi il ​​controllo che ospita la vista ad albero (mostrato in xaml), ma come faccio a bind a un comportamento specifico sulle classi del modello di visualizzazione, ad esempio ProjectViewModel?

Preferibile legato a ICommand-implementer, in quanto utilizzato altrove …

                                   

Aggiornare la mia risposta un po ‘.

Ho provato un sacco di approcci diversi per questo e mi sento ancora come i comportamenti attaccati è la soluzione migliore. Anche se potrebbe sembrare un sacco di spese generali all’inizio non lo è. Mantengo tutti i miei comportamenti per ICommands nello stesso posto e ogni volta che ho bisogno di supporto per un altro evento è solo questione di copia / incolla e modificare l’evento in PropertyChangedCallback .

Ho anche aggiunto il supporto opzionale per CommandParameter .

Nel designer si tratta solo di selezionare l’evento desiderato

inserisci la descrizione dell'immagine qui

Puoi impostarlo su TreeView , TreeViewItem o in qualsiasi altro posto che ti piace.

Esempio. Impostalo su TreeView

  

Esempio. Impostalo su TreeViewItem

      

Ed ecco il comportamento MouseDoubleClick

 public class MouseDoubleClick { public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(MouseDoubleClick), new UIPropertyMetadata(CommandChanged)); public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(MouseDoubleClick), new UIPropertyMetadata(null)); public static void SetCommand(DependencyObject target, ICommand value) { target.SetValue(CommandProperty, value); } public static void SetCommandParameter(DependencyObject target, object value) { target.SetValue(CommandParameterProperty, value); } public static object GetCommandParameter(DependencyObject target) { return target.GetValue(CommandParameterProperty); } private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { Control control = target as Control; if (control != null) { if ((e.NewValue != null) && (e.OldValue == null)) { control.MouseDoubleClick += OnMouseDoubleClick; } else if ((e.NewValue == null) && (e.OldValue != null)) { control.MouseDoubleClick -= OnMouseDoubleClick; } } } private static void OnMouseDoubleClick(object sender, RoutedEventArgs e) { Control control = sender as Control; ICommand command = (ICommand)control.GetValue(CommandProperty); object commandParameter = control.GetValue(CommandParameterProperty); command.Execute(commandParameter); } } 

Sono in ritardo per questo, ma ho appena usato una soluzione diversa. Ancora una volta, potrebbe non essere il migliore, ma ecco come l’ho fatto.

Prima di tutto, la precedente risposta di Meleak è interessante, ma credo che sia molto pesante essere costretti ad aggiungere AttachedBehaviors solo per qualcosa di semplice come MouseDoubleClick. Questo mi costringerebbe a usare un nuovo pattern nella mia app e complicherebbe ancora di più tutto.

Il mio objective è rimanere il più semplice ansible. Quindi ho fatto qualcosa di molto semplice (il mio esempio è per un DataGrid, ma puoi usarlo su molti controlli diversi):

    

Nel code-behind:

 private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) { //Execute the command related to the doubleclick, in my case Edit (this.DataContext as VmHome).EditAppCommand.Execute(null); } 

Perché mi sembra che non renda il pattern MVVM? Perché a mio parere, le uniche cose che dovresti inserire nel code-behind sono i ponti per viewModel, cose molto specifiche per la tua interfaccia utente. In questo caso dice semplicemente che se fai doppio clic, triggers il comando relativo. È quasi la stessa di Command = “{Binding EditAppCommand}”, ho appena simulato questo comportamento.

Sentiti libero di darmi la tua opinione su questo, sarei lieto di ascoltare alcuni critici a questo modo di pensare, ma per ora credo che sia il modo più semplice per implementarlo senza rompere MVVM.

Entrambe le raccomandazioni di Meleak e di ígor sono ottime, ma quando il gestore di eventi con doppio clic è associato a TreeViewItem questo gestore di eventi viene chiamato per tutti gli elementi padre dell’object (non solo l’elemento cliccato). Se non è desiderato, ecco un’altra aggiunta:

 private static void OnMouseDoubleClick(object sender, RoutedEventArgs e) { Control control = sender as Control; ICommand command = (ICommand)control.GetValue(CommandProperty); object commandParameter = control.GetValue(CommandParameterProperty); if (sender is TreeViewItem) { if (!((TreeViewItem)sender).IsSelected) return; } if (command.CanExecute(commandParameter)) { command.Execute(commandParameter); } } 

La soluzione di Meleak è fantastica !, ma ho aggiunto il controllo

  private static void OnMouseDoubleClick(object sender, RoutedEventArgs e) { Control control = sender as Control; ICommand command = (ICommand)control.GetValue(CommandProperty); object commandParameter = control.GetValue(CommandParameterProperty); //Check command can execute!! if(command.CanExecute(commandParameter )) command.Execute(commandParameter); } 

È davvero semplice ed è così che ho gestito il doppio click su TreeView:

          

System.Windows.Interactivity.dll è preso da C: \ Programmi (x86) \ Microsoft SDK \ Expression \ Blend.NETFramework \ v4.0 \ Libraries \ System.Windows.Interactivity.dll o da NuGet

Il mio modello di vista:

 public class TreeViewModel : INotifyPropertyChanged { private List departments; public TreeViewModel() { Departments = new List() { new Department("Department1"), new Department("Department2"), new Department("Department3") }; } public List Departments { get { return departments; } set { departments = value; OnPropertyChanged("Departments"); } } public void SomeMethod() { MessageBox.Show("*****"); } } 

Solo per curiosità: cosa succede se prendo parte a Frederiks, ma la implemento direttamente come comportamento?

 public class MouseDoubleClickBehavior : Behavior { public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof (ICommand), typeof (MouseDoubleClickBehavior), new PropertyMetadata(default(ICommand))); public ICommand Command { get { return (ICommand) GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } } public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof (object), typeof (MouseDoubleClickBehavior), new PropertyMetadata(default(object))); public object CommandParameter { get { return GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } protected override void OnAttached() { base.OnAttached(); AssociatedObject.MouseDoubleClick += OnMouseDoubleClick; } protected override void OnDetaching() { AssociatedObject.MouseDoubleClick -= OnMouseDoubleClick; base.OnDetaching(); } void OnMouseDoubleClick(object sender, RoutedEventArgs e) { if (Command == null) return; Command.Execute(/*commandParameter*/null); } } 

L’approccio migliore che ho raggiunto è bind la proprietà IsSelected da TreeViewItem a ViewModel in modalità bidirezionale e implementare la logica nel setter della proprietà. Quindi puoi definire cosa fare se il valore è vero o falso, perché questa proprietà cambierà ogni volta che l’utente fa clic su un elemento.

 class MyVM { private bool _isSelected; public bool IsSelected { get { return _isSelected; } set { if (_isSelected == null) return; _isSelected = vale; if (_isSelected) { // Your logic goes here. } else { // Your other logic goes here. } } } 

Questo evita un sacco di codice.

Inoltre, questa tecnica consente di implementare il comportamento “onclick” solo nei ViewModels che ne hanno veramente bisogno.

Binding del mouse su TextBlock

In TreeView.Resources of the View:

            

Nel ViewModel di quella vista (DiscoveryUrlViewModel.cs):

 private RelayCommand _doubleClickCommand; public ICommand DoubleClickCopyCommand { get { if (_doubleClickCommand == null) _doubleClickCommand = new RelayCommand(OnDoubleClick); return _doubleClickCommand; } } private void OnDoubleClick(object obj) { var clickedViewModel = (DiscoveryUrlViewModel)obj; }