2012-06-17 21 views
8

Tengo la aplicación MVVM con WPF TreeView en el lado izquierdo de una ventana. Un panel de detalles a la derecha cambia el contenido según el nodo de árbol seleccionado.¿Cómo saber si se selecciona con el mouse o la tecla?

Si el usuario selecciona un nodo, el contenido del panel de detalles cambia inmediatamente. Se desea si el usuario hizo clic en el nodo, pero quiero retrasar el cambio de contenido si el usuario navega por el árbol con la tecla hacia abajo/arriba. (El mismo comportamiento que el Explorador de Windows, al menos en Win XP) Supongo que tengo que saber en mi ViewModel si el nodo se ha seleccionado mediante el mouse o el teclado.

¿Cómo puedo lograrlo?

Actualización:

Este es mi primer post, por tanto, no estoy seguro de si este es el lugar correcto, pero quiero que la comunidad sepa lo que hice en el ínterin. Aquí está mi propia solución. No soy un experto, por lo tanto, no sé si es una buena solución. Pero funciona para mí y sería feliz si ayuda a los demás. Correcciones de errores, mejoras o mejores soluciones son muy apreciadas.

creé propiedad adjunta a continuación HasMouseFocus ...
(Primero he utilizado la MouseEnterEvent pero esto no funciona bien si el usuario navega por el árbol con la tecla arriba/abajo y el puntero del ratón es al azar en cualquier árbol navegada artículo, porque en ese caso los datos se actualiza inmediatamente.)

public static bool GetHasMouseFocus(TreeViewItem treeViewItem) 
{ 
    return (bool)treeViewItem.GetValue(HasMouseFocusProperty); 
} 

public static void SetHasMouseFocus(TreeViewItem treeViewItem, bool value) 
{ 
    treeViewItem.SetValue(HasMouseFocusProperty, value); 
} 

public static readonly DependencyProperty HasMouseFocusProperty = 
    DependencyProperty.RegisterAttached(
    "HasMouseFocus", 
    typeof(bool), 
    typeof(TreeViewItemProperties), 
    new UIPropertyMetadata(false, OnHasMouseFocusChanged) 
); 

static void OnHasMouseFocusChanged(
    DependencyObject depObj, DependencyPropertyChangedEventArgs e) 
{ 
    TreeViewItem item = depObj as TreeViewItem; 
    if (item == null) 
    return; 

    if (e.NewValue is bool == false) 
    return; 

    if ((bool)e.NewValue) 
    { 
    item.MouseDown += OnMouseDown; 
    item.MouseLeave += OnMouseLeave; 
    } 
    else 
    { 
    item.MouseDown -= OnMouseDown; 
    item.MouseLeave -= OnMouseLeave; 
    } 
} 

/// <summary> 
/// Set HasMouseFocusProperty on model of associated element. 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
static void OnMouseDown(object sender, MouseEventArgs e) 
{ 
    if (sender != e.OriginalSource) 
    return; 

    TreeViewItem item = sender as TreeViewItem; 
    if ((item != null) & (item.HasHeader)) 
    { 
    // get the underlying model of current tree item 
    TreeItemViewModel header = item.Header as TreeItemViewModel; 
    if (header != null) 
    { 
     header.HasMouseFocus = true; 
    } 
    } 
} 

/// <summary> 
/// Clear HasMouseFocusProperty on model of associated element. 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
static void OnMouseLeave(object sender, MouseEventArgs e) 
{ 
    if (sender != e.OriginalSource) 
    return; 

    TreeViewItem item = sender as TreeViewItem; 
    if ((item != null) & (item.HasHeader)) 
    { 
    // get the underlying model of current tree item 
    TreeItemViewModel header = item.Header as TreeItemViewModel; 
    if (header != null) 
    { 
     header.HasMouseFocus = false; 
    } 
    } 
} 

... y lo aplicó a la TreeView.ItemContainerStyle

<TreeView.ItemContainerStyle> 
     <Style TargetType="{x:Type TreeViewItem}" > 
     <!-- These Setters binds some properties of a TreeViewItem to the TreeViewItemViewModel. --> 
     <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
     <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
     <Setter Property="ToolTip" Value="{Binding Path=CognosBaseClass.ToolTip}"/> 
     <!-- These Setters applies attached behaviors to all TreeViewItems. --> 
     <Setter Property="properties:TreeViewItemProperties.PreviewMouseRightButtonDown" Value="True" /> 
     <Setter Property="properties:TreeViewItemProperties.BringIntoViewWhenSelected" Value="True" /> 
     <Setter Property="properties:TreeViewItemProperties.HasMouseFocus" Value="True" /> 
     </Style> 
    </TreeView.ItemContainerStyle> 

Donde propiedades es la ruta de acceso de mi propiedad adjunta.

xmlns:properties="clr-namespace:WPF.MVVM.AttachedProperties;assembly=WPF.MVVM" 

Luego, en mi modelo de vista, si HasMousefocusProperty es cierto, actualizar el panel de detalles (GridView) inmediatamente. Si es falso, simplemente empiezo un DispatcherTimer y aplico el elemento seleccionado actualmente como Tag. Después de un intervalo de 500 ms, el evento Tick aplica los detalles, pero solo si el elemento seleccionado sigue siendo el mismo que el de Tag.

/// <summary> 
/// This property is beeing set when the selected item of the tree has changed. 
/// </summary> 
public TreeItemViewModel SelectedTreeItem 
{ 
    get { return Property(() => SelectedTreeItem); } 
    set 
    { 
    Property(() => SelectedTreeItem, value); 
    if (this.SelectedTreeItem.HasMouseFocus) 
    { 
     // show details for selected node immediately 
     ShowGridItems(value); 
    } 
    else 
    { 
     // delay showing details 
     this._selctedNodeChangedTimer.Stop(); 
     this._selctedNodeChangedTimer.Tag = value; 
     this._selctedNodeChangedTimer.Start(); 
    } 
    } 
} 
+0

Las dos respuestas principales en la siguiente pregunta SO tratan con el problema de vinculación de claves: http://stackoverflow.com/a/7086853/1416258 y http://stackoverflow.com/a/918062/1416258 En cuanto a su situación personal, jueguen o esperen que alguien les dé pescado. –

+0

Muchas gracias @Geoist. Utilizando un enlace de Cammand podría establecer un indicador en mi ViewModel tan pronto como el usuario presiona las teclas especificadas. Pero tengo que borrar esta bandera nuevamente cuando (o antes) luego el usuario selecciona otro nodo de árbol. ¿Algunas ideas? – AelanY

+0

@AelanY Sí, tiene razón, tendrá que encargarse de restablecer esta bandera; el mejor lugar sería la lógica en la que está iniciando la actualización de su panel de detalles. – akjoshi

Respuesta

2

usted puede manejar el OnPreviewKeyDown para su TreeView (o control de usuario tenerlo) y programáticamente establecer un indicador en su modelo de vista y considerar que mientras que los detalles refrescantes del panel -

protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e) 
{ 
    switch(e.Key) 
    { 
     case Key.Up: 
     case Key.Down: 
      MyViewModel.IsUserNavigating = true; 
      break; 
    } 
} 

Un approch similar y otras soluciones se mencionan en esta cuestión de forma -

How can I programmatically navigate (not select, but navigate) a WPF TreeView?

Actualización: [En respuesta al comentario de AalanY]

No creo que haya ningún problema en tener algún código subyacente en Views, eso no rompe MVVM.

En el artículo, WPF Apps With The Model-View-ViewModel Design Pattern, el autor que es Josh Smith dice:

En una arquitectura MVVM bien diseñado, el código subyacente para la mayoría Vistas debe estar vacío, o, a lo sumo, sólo contienen código que manipula los controles y los recursos contenidos en esa vista. A veces es también necesario escribir código en el código subyacente de una vista que interactúa con un objeto modelo de vista, tales como conectar un evento o llamar a un método que de otro modo sería muy difícil invocar desde el modelo de vista sí.

En mi experiencia que es imposible construir una empresa (de un tamaño considerable) la aplicación sin tener ningún código subyacente, especialmente cuando se tiene que utilizar los controles complejos como TreeView, DataGrid o parte controla 3'rd.

+0

Gracias por su respuesta @akjoshi, pero estoy usando el patrón MVVM y no quiero tener ningún código detrás. – AelanY

+0

Gracias de nuevo @akjoshi, ya leí este gran artículo de Josh Smith y por supuesto tienes razón. ¿Qué piensas de mi solución? Consulte mi actualización anterior ... – AelanY

+0

@AelanY Su solución se ve bien, ya que se puede adjuntar a una vista de árbol en XAML y tiene elementos de reutilización; excepto esto, es casi lo mismo que usar manejadores de eventos y actualizar el indicador de ViewModel. – akjoshi

Cuestiones relacionadas