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();
}
}
}
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. –
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
@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