c# - WPF/MVVM: ¿cómo hacer doble clic en TreeViewItems en ViewModel?
mouseevent commandbinding (8)
¡La solución de Meleak es genial !, pero agregué un 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);
}
(Nota: esta es una nueva publicación ya que mi primera pregunta fue publicada bajo un titular incorrecto: ¡ Here siento!)
Tengo una vista de árbol estándar de WPF y he vinculado elementos para ver las clases de modelos.
Ahora deseo manejar el comportamiento cuando se hace doble clic en los elementos (abriendo documentos visual-studio-style).
Puedo hacer que el manejador de eventos dispare en la carcasa de control de la vista de árbol (se muestra xaml), pero ¿cómo puedo unirme a un comportamiento específico en las clases de modelos de vista, por ejemplo, ProjectViewModel?
Preferible ligado a ICommand-implementor, ya que esto se usa en otra parte ...
<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>
El mejor enfoque al que he llegado es simplemente vincular la propiedad IsSelected
del TreeViewItem al ViewModel en modo bidireccional e implementar la lógica en el setter de propiedades. Luego puede definir qué hacer si el valor es verdadero o falso, porque esta propiedad cambiará cada vez que el usuario haga clic en 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.
}
}
}
Esto evita mucho código.
Además, esta técnica le permite implementar el comportamiento "onclick" solo en los ViewModels que realmente lo necesitan.
Enlazar con el mouse en TextBlock
En TreeView.Resources de la vista:
<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>
En el ViewModel de esa 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;
}
Es realmente simple y así es como me las arreglé para hacer doble clic en 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 está tomado de C: / Archivos de programa (x86) / Microsoft SDKs / Expression / Blend.NETFramework / v4.0 / Libraries / System.Windows.Interactivity.dll o por NuGet
Mi modelo de vista:
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("*****");
}
}
Las recomendaciones de Meleak y ígor son geniales, pero cuando el controlador de eventos de doble clic está vinculado a TreeViewItem
, se llama a este controlador de eventos para todos los elementos primarios del elemento (no solo para el elemento cliqueado). Si no es deseado, aquí hay otra adición:
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);
}
}
Llego tarde para esto, pero acabo de usar una solución diferente. Una vez más, podría no ser el mejor, pero aquí es cómo lo hice.
En primer lugar, la respuesta anterior de Meleak es genial, pero siento que es muy pesado estar obligado a agregar comportamientos adjuntos solo por algo tan básico como un MouseDoubleClick. Esto me obligaría a usar un nuevo patrón en mi aplicación y lo complicaría aún más todo.
Mi objetivo es ser lo más simple posible. Por lo tanto, hice algo muy básico (mi ejemplo es para un DataGrid, pero puede usarlo en muchos controles diferentes):
<DataGrid MouseDoubleClick="DataGrid_MouseDoubleClick">
<!-- ... -->
</DataGrid>
En el código subyacente:
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 qué siento que no rompe el patrón MVVM? Porque, en mi opinión, las únicas cosas que debes poner en el código subyacente son los puentes a tu viewModel, cosas muy específicas para tu UI. En este caso, solo dice que si haces doble clic, disparas el comando relacionado. Es casi lo mismo que Command = "{Binding EditAppCommand}", simulé este comportamiento.
Siéntete libre de darme tu opinión sobre esto, me alegraría escuchar algunas críticas a esta forma de pensar, pero por ahora creo que es la forma más fácil de implementarlo sin romper MVVM.
Solo por curiosidad: ¿y si tomo parte en Frederiks, pero lo implemento directamente como comportamiento?
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);
}
}
Actualizando mi respuesta un poco.
He intentado muchos enfoques diferentes para esto y todavía siento que los comportamientos adjuntos son la mejor solución. Aunque podría parecer mucho sobrecargado al principio, realmente no lo es. Mantengo todos mis comportamientos para ICommands
en el mismo lugar y cada vez que necesito ayuda para otro evento, solo es cuestión de copiar / pegar y cambiar el evento en PropertyChangedCallback
.
También agregué el soporte opcional para CommandParameter
.
En el diseñador, solo se trata de seleccionar el evento deseado
Puede establecer esto en TreeView
, TreeViewItem
o en cualquier otro lugar que desee.
Ejemplo. Establecerlo en TreeView
<TreeView commandBehaviors:MouseDoubleClick.Command="{Binding YourCommand}"
commandBehaviors:MouseDoubleClick.CommandParameter="{Binding}"
.../>
Ejemplo. Establecerlo en 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>
Y aquí está el comportamiento 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);
}
}