WPF / MVVM – como lidar com um clique duplo em TreeViewItems no ViewModel?

(Nota – este é um re-post como a minha primeira pergunta foi postada sob o título errado: Aqui Desculpe!)

Eu tenho um treeview de WPF padrão e tenho itens vinculados para exibir classs de modelo.

Agora eu quero lidar com o comportamento quando os itens são clicados duas vezes (abrindo documentos estilo visual-studio).

Eu posso obter manipulador de events para triggersr no controle habitação a treeview (xaml mostrado), mas como faço para vincular a um comportamento específico nas classs de modelo de exibição – por exemplo, ProjectViewModel?

Preferencial ligado ao ICommand-implementer, pois isso é usado em outro lugar …

                                   

Atualizando minha resposta um pouco.

Eu tentei muitas abordagens diferentes para isso e ainda sinto que o Attached Behaviors é a melhor solução. Embora possa parecer um monte de sobrecarga no início, realmente não é. Eu mantenho todos os meus comportamentos para ICommands no mesmo lugar e sempre que eu preciso de suporte para outro evento é apenas uma questão de copiar / colar e alterar o evento no PropertyChangedCallback .

Eu também adicionei o suporte opcional para CommandParameter .

No designer, é apenas uma questão de selecionar o evento desejado

insira a descrição da imagem aqui

Você pode definir isso em TreeView , TreeViewItem ou qualquer outro lugar que você gosta.

Exemplo. Defina-o no TreeView

  

Exemplo. Defina-o no TreeViewItem

      

E aqui está o Attached Behavior 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); } } 

Estou atrasado para isso, mas acabei de usar uma solução diferente. Mais uma vez, pode não ser o melhor, mas aqui está como eu fiz isso.

Primeiro de tudo, a resposta anterior do Meleak é legal, mas eu sinto que é muito pesado para ser forçado a adicionar AttachedBehaviors apenas para algo tão básico como um MouseDoubleClick. Isso me forçaria a usar um novo padrão no meu aplicativo e complicaria ainda mais tudo.

Meu objective é ficar o mais simples possível. Por isso fiz algo muito básico (meu exemplo é para um DataGrid, mas você pode usar isso em vários controles diferentes):

    

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

Por que eu sinto que não quebra o padrão MVVM? Porque na minha opinião, as únicas coisas que você deve colocar no code-behind são pontes para o seu viewModel, coisas muito específicas para a sua interface do usuário. Neste caso, apenas diz que se você clicar duas vezes, ative o comando relacionado. É quase o mesmo que um Command = “{Binding EditAppCommand}”, apenas simulei esse comportamento.

Sinta-se à vontade para me dar sua opinião sobre isso, eu ficaria feliz em ouvir alguns críticos a este modo de pensar, mas por enquanto eu acredito que é a maneira mais fácil de implementá-lo sem quebrar o MVVM.

As recomendações de Meleak e ígor são ótimas, mas quando o manipulador de events de clique duplo está vinculado a TreeViewItem , esse manipulador de events é chamado para todos os elementos pai do item (não apenas o elemento clicado). Se não for desejado, aqui está outra adição:

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

Meleak solução é ótima !, mas eu adicionei cheque

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

É realmente simples e é assim que eu lidei com duplo clique no TreeView:

          

O System.Windows.Interactivity.dll é obtido de C: \ Arquivos de Programas (x86) \ Microsoft SDKs \ Expression \ Blend.NETFramework \ v4.0 \ Libraries \ System.Windows.Interactivity.dll ou pelo NuGet

Meu modelo de visualização:

 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("*****"); } } 

Só por curiosidade: e se eu tomar parte Frederiks, mas implementá-lo diretamente como 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); } } 

A melhor abordagem que alcancei é apenas vincular a propriedade IsSelected do TreeViewItem ao ViewModel em um modo Bidirecional e implementar a lógica no definidor de propriedades. Em seguida, você pode definir o que fazer se o valor for verdadeiro ou falso, porque essa propriedade será alterada sempre que o usuário clicar em um item.

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

Isso evita muito código.

Além disso, essa técnica permite implementar o comportamento “onclick” apenas nos ViewModels que realmente precisam dele.

Ligação de mouse no TextBlock

No TreeView.Resources da Vista:

            

No ViewModel dessa visão (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; }