web-dev-qa-db-fra.com

WPF/MVVM - comment gérer un double-clic sur TreeViewItems dans le ViewModel?

(Remarque - ceci est un re-post car ma première question a été postée sous un faux titre: ici Désolé!)

J'ai un treeview WPF standard et des éléments liés pour afficher les classes de modèle.

Je souhaite maintenant gérer le comportement lorsque les éléments sont double-cliqués (ouverture de documents dans le style visual-studio).

Je peux obtenir que le gestionnaire d'événements se déclenche dans le contrôle contenant l'arborescence (xaml illustré), mais comment puis-je me lier à un comportement spécifique sur les classes de modèle de vue - par exemple. ProjectViewModel?

Préférable lié à ICommand-Implementer, comme cela est utilisé ailleurs ...

<TreeView ItemsSource="{Binding Projects}" MouseDoubleClick="TreeView_MouseDoubleClick">
    <TreeView.ItemContainerStyle>
        <!-- 
This Style binds a TreeViewItem to a TreeViewItemViewModel. 
-->
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight" Value="Normal" />
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="FontWeight" Value="Bold" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>

    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type Implementations:ProjectViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Margin="3,0" Source="Images\Region.png" />
                <TextBlock Text="{Binding DisplayName}" />
            </StackPanel>
        </HierarchicalDataTemplate>

        <HierarchicalDataTemplate DataType="{x:Type Implementations:PumpViewModel}" ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Margin="3,0" Source="Images\State.png" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type Implementations:PumpDesignViewModel}">
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Margin="3,0" Source="Images\City.png" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>
27
Anders Juul

Mise à jour un peu ma réponse.

J'ai essayé beaucoup d'approches différentes pour cela et je me sens toujours comme comportements attachés est la meilleure solution. Bien que cela puisse ressembler à beaucoup de frais généraux au début, ce n’est vraiment pas le cas. Je garde tous mes comportements pour ICommands au même endroit et chaque fois que j'ai besoin d'assistance pour un autre événement, il suffit simplement de copier/coller et de modifier l'événement dans PropertyChangedCallback

J'ai également ajouté le support optionnel pour CommandParameter. 

Dans le concepteur, il suffit de sélectionner l'événement souhaité

enter image description here

Vous pouvez définir ceci soit sur TreeView, TreeViewItem ou à n’importe quel autre endroit que vous aimez. 

Exemple. Réglez-le sur la TreeView

<TreeView commandBehaviors:MouseDoubleClick.Command="{Binding YourCommand}"
          commandBehaviors:MouseDoubleClick.CommandParameter="{Binding}"
          .../>

Exemple. Réglez-le sur TreeViewItem

<TreeView ItemsSource="{Binding Projects}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="commandBehaviors:MouseDoubleClick.Command"
                    Value="{Binding YourCommand}"/>
            <Setter Property="commandBehaviors:MouseDoubleClick.CommandParameter"
                    Value="{Binding}"/>
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

Et voici le Comportement attaché 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);
    }
}
51
Fredrik Hedblad

Je suis en retard pour cela, mais je viens d'utiliser une solution différente. Encore une fois, ce n'est peut-être pas le meilleur, mais voici comment j'ai fait cela.

Tout d’abord, la réponse précédente de Meleak est bien, mais j’ai le sentiment qu’il est très lourd d’être obligé d’ajouter AttachedBehaviors pour quelque chose d'aussi simple qu'un MouseDoubleClick. Cela me forcerait à utiliser un nouveau modèle dans mon application et compliquerait encore plus tout.

Mon objectif est de rester aussi simple que possible. J'ai donc fait quelque chose de très basique (mon exemple est pour un DataGrid, mais vous pouvez l'utiliser sur beaucoup de contrôles différents):

<DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick">
   <!-- ... -->
</DataGrid>

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

Pourquoi ai-je l'impression que cela ne casse pas le modèle MVVM? Parce qu'à mon avis, les seules choses que vous devriez mettre dans le code-behind sont des passerelles vers votre viewModel, des choses très spécifiques à votre interface utilisateur. Dans ce cas, il est simplement indiqué que si vous double-cliquez, lancez la commande correspondante. C'est presque la même chose qu'un Command = "{Binding EditAppCommand}", je viens de simuler ce comportement.

N'hésitez pas à me donner votre avis là-dessus, je serais heureux d'entendre certains critiques de cette façon de penser, mais pour le moment, je pense que c'est la manière la plus simple de la mettre en œuvre sans détruire le MVVM.

9
Damascus

Les recommandations de Meleak et d'Igor sont bonnes, mais lorsque le gestionnaire d'événements double-clic est lié à TreeViewItem, le gestionnaire d'événements this est appelé pour tous les éléments parents de l'élément (pas uniquement l'élément cliqué). Si cela n'est pas souhaité, voici un autre ajout:

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

C'est vraiment simple et c'est comme ça que j'ai géré le double-clic sur le TreeView:

<Window x:Class="TreeViewWpfApplication.MainWindow"
    ...
    xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
    xmlns:ei="http://schemas.Microsoft.com/expression/2010/interactions"
    ...>

      <TreeView ItemsSource="{Binding Departments}" >
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseDoubleClick">
                <ei:CallMethodAction MethodName="SomeMethod" TargetObject="{Binding}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
      </TreeView>
</Window>

System.Windows.Interactivity.dll provient de C:\Program Files (x86)\Microsoft SDKs\Expression\Blend.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll ou par NuGet

Mon modèle de vue:

public class TreeViewModel : INotifyPropertyChanged
{   
    private List<Department> departments;
    public TreeViewModel()
    {
        Departments = new List<Department>()
        {
            new Department("Department1"),
            new Department("Department2"),
            new Department("Department3")
        };
    }

    public List<Department> Departments
    {
        get
        {
            return departments;
        }
        set
        {
            departments = value;
            OnPropertyChanged("Departments");
        }
    }

    public void SomeMethod()
    {
        MessageBox.Show("*****");
    }
}   
3
StepUp

La solution Meleak est géniale !, mais j'ai ajouté 

    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);
    }
2
ígor

La meilleure approche que j'ai atteinte consiste simplement à associer la propriété IsSelected de TreeViewItem au ViewModel en mode bidirectionnel et à implémenter la logique dans le configurateur de propriétés. Ensuite, vous pouvez définir quoi faire si la valeur est true ou false, car cette propriété sera modifiée chaque fois que l'utilisateur clique sur un élément.

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

Cela évite beaucoup de code.

En outre, cette technique vous permet d'implémenter le comportement "onclick" uniquement dans les modèles de vue qui en ont réellement besoin.

0
JoanComasFdz

Reliure de la souris sur le bloc de texte

Dans le TreeView.Resources de la vue:

   <HierarchicalDataTemplate 
      DataType="{x:Type treeview:DiscoveryUrlViewModel}" 
      ItemsSource="{Binding Children}">

      <StackPanel Orientation="Horizontal">
           <Image Width="16" Height="16" Margin="3,0" Source="../Images/ic_search.png" />

           <TextBlock Text="{Binding DisplayText}" >
               <TextBlock.InputBindings>
                     <MouseBinding Gesture="LeftDoubleClick"
                                   Command="{Binding DoubleClickCopyCommand}"
                                   CommandParameter="{Binding }" />
               </TextBlock.InputBindings>
            </TextBlock>
       </StackPanel>
 </HierarchicalDataTemplate>

Dans le ViewModel de cette vue (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;
        }
0
Pinfi

Juste par curiosité: si je prends Frederiks, mais que je le mets en œuvre directement en tant que comportement?

public class MouseDoubleClickBehavior : Behavior<Control>
{
    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);
    }
}
0
Slesa