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

(Note - Ceci est un re-post comme ma première question a été posté sous le titre erroné: ici Désolé!)

j'ai une vue d'arbre standard de WPF et j'ai des articles reliés pour voir les classes de modèles.

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

je peux faire démarrer le gestionnaire d'événements dans le boîtier de contrôle de treeview (XAML), mais comment puis-je me lier à comportement spécifique sur les classes de modèles de vue-par exemple ProjectViewModel?

préférable lié à ICommand-implementer, car il 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="ImagesRegion.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="ImagesState.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="ImagesCity.png" />
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>
27
demandé sur Community 2010-12-21 12:30:40

8 réponses

mise à Jour de ma réponse un peu.

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 de support pour un autre événement c'est juste une question de copier/coller et changer l'événement dans le PropertyChangedCallback .

j'ai aussi ajouté le support optionnel pour CommandParameter .

dans le concepteur il s'agit juste de choisir l'événement désiré

enter image description here

vous pouvez régler cela soit sur TreeView , TreeViewItem ou tout autre endroit que vous aimez.

exemple. Mettez-le sur le TreeView

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

exemple. Mettez-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 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);
    }
}
51
répondu Fredrik Hedblad 2011-09-22 20:56:36

je suis en retard pour ça, mais j'ai juste utilisé une solution différente. Encore une fois, ce n'est peut-être pas la meilleure, mais voici comment j'ai fait ça.

tout d'abord, la réponse précédente de Meleak est cool, mais j'ai l'impression qu'il est très lourd d'être forcé d'ajouter des comportements attachés juste pour quelque chose d'aussi basique qu'un MouseDoubleClick. Cela me forcerait à utiliser un nouveau modèle dans mon application et compliquerait encore plus tout.

mon but est de rester aussi simple que possible. Par conséquent, j'ai fait quelque chose de très basique (mon exemple est pour un DataGrid, mais vous pouvez l'utiliser sur beaucoup de commandes différentes):

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

dans le code-derrière:

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 j'ai l'impression que ça ne casse pas le modèle MVVM? Parce que, à mon avis, les seules choses que vous devriez mettre dans le code-derrière sont des ponts vers votre modèle de vue, des choses très spécifiques à votre UI. Dans ce cas, il est juste dit que si vous double-cliquez, le feu commande. C'est presque la même chose qu'une commande="{Binding EditAppCommand}", j'ai simulé ce comportement.

N'hésitez pas à me donner votre avis à ce sujet, je serais heureux d'entendre certaines critiques à cette façon de penser, mais pour l'instant je crois que c'est la façon la plus facile de mettre en œuvre sans briser MVVM.

9
répondu Damascus 2011-03-23 10:04:53

les recommandations de Meleak et ígor sont excellentes, mais lorsque le double clic event handler est lié à TreeViewItem puis ce event handler est appelé pour tous les éléments parent de l'élément (pas seulement l'élément cliqué). Si elle n'est pas désirée, 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
répondu hightower70 2012-09-17 14:40:31

Meleak solution est génial!, mais j'ai ajouté check

    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
répondu ígor 2012-07-26 09:28:46

c'est vraiment simple et c'est comme ça que j'ai géré le double clic au 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>

du Système.Windows.Interactivité.dll est tiré de C:\Program fichiers (x86)\Microsoft SDKs\Expression\Blend.Netfram framework\v4.0 \ Libraries\System.Windows.Interactivité.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("*****");
    }
}   
2
répondu StepUp 2015-10-16 12:09:58

juste par curiosité: et si je prends part à Frederik, mais que je l'implémente 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
répondu Slesa 2014-04-17 21:19:05

la meilleure approche que j'ai atteinte est simplement de lier la propriété IsSelected du TreeViewItem au ViewModel dans un mode bidirectionnel et d'implémenter la logique dans la propriété setter. Ensuite, vous pouvez définir ce qu'il faut faire si la valeur est true ou false, parce que cette propriété changera chaque fois que l'utilisateur cliquera 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 de mettre en œuvre le comportement "onclick" uniquement dans le Viewmodel qui en ont vraiment besoin.

0
répondu JoanComasFdz 2014-11-10 05:03:52

Mouse Binding on the Textlock

dans le TreeView.Ressources 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 Modèlevue 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
répondu Pinfi 2018-02-07 09:28:22