2010-06-11 8 views
5

Tengo un estilo definido para mi ListBoxItems con un gatillo para establecer un color de fondo cuando IsSelected es verdadera:Cómo usar IsKeyboardFocusWithin y IsSelected together?

<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}"> 
     <Setter Property="SnapsToDevicePixels" Value="true"/> 
     <Setter Property="OverridesDefaultStyle" Value="true"/> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="ListBoxItem"> 
        <Border Name="Border" Padding="0" SnapsToDevicePixels="true"> 
         <ContentPresenter /> 
        </Border> 
        <ControlTemplate.Triggers> 
         <Trigger Property="IsSelected" Value="True"> 
          <Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/> 
         </Trigger> 
        </ControlTemplate.Triggers> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 

Este estilo mantiene el elemento seleccionado, incluso cuando el ListBox y ListBoxItem pierde el foco, que en mi caso es una necesidad absoluta. El problema es que también quiero que se seleccione ListBoxItem cuando uno de los hijos de TextBox se enfoca. Para lograr esto añado un desencadenante que IsSelected a cierto cuando IsKeyboardFocusWithin es cierto:

<Trigger Property="IsKeyboardFocusWithin" Value="True"> 
    <Setter Property="IsSelected" Value="True" /> 
</Trigger> 

Cuando agrego este disparador se selecciona el elemento cuando la atención se centra en un niño TextBox, pero el primer comportamiento desaparece. Ahora cuando hago clic fuera del ListBox, el elemento se deselecciona.

¿Cómo puedo mantener ambos comportamientos?

+0

Hermosa solución solo XAML: https://stackoverflow.com/a/15383435/419761 – l33t

Respuesta

6

Cuando su cuadro de lista pierde el foco, establecerá el elemento seleccionado en nulo debido a su desencadenador. Puede seleccionar el enfoque utilizando algún código detrás que no deseleccionará cuando pierde el enfoque.

XAML:

<Window x:Class="SelectedTest.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Height="300" Width="300"> 

    <StackPanel> 
     <TextBox Text="Loose focus here" /> 
     <ListBox Name="_listBox" ItemsSource="{Binding Path=Items}"> 
      <ListBox.ItemTemplate> 
       <DataTemplate> 
        <StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus"> 
         <TextBox Text="{Binding .}" Margin="10" /> 
         <TextBox Text="{Binding .}" Margin="10" /> 
        </StackPanel> 
       </DataTemplate> 
      </ListBox.ItemTemplate> 
      <ListBox.ItemContainerStyle> 
       <Style TargetType="{x:Type ListBoxItem}"> 
        <Setter Property="SnapsToDevicePixels" Value="true"/> 
        <Setter Property="OverridesDefaultStyle" Value="true"/> 
        <Setter Property="Template"> 
         <Setter.Value> 
          <ControlTemplate TargetType="ListBoxItem"> 
           <Border Name="Border" SnapsToDevicePixels="true" Background="Transparent"> 
            <ContentPresenter /> 
           </Border> 
           <ControlTemplate.Triggers> 
            <Trigger Property="IsSelected" Value="True"> 
             <Setter TargetName="Border" Property="Background" Value="Red"/> 
            </Trigger>         
           </ControlTemplate.Triggers> 
          </ControlTemplate> 
         </Setter.Value> 
        </Setter> 
       </Style> 
      </ListBox.ItemContainerStyle> 
     </ListBox> 
    </StackPanel> 
</Window> 

Código atrás:

private void OnChildGotFocus(object sender, RoutedEventArgs e) 
{ 
    _listBox.SelectedItem = (sender as StackPanel).DataContext; 
} 
+0

¡Muchas gracias! Eso es exactamente lo que estaba buscando. – jpsstavares

4

me di cuenta de que IsKeyboardFocusWithin no es la mejor solución.

Lo que hice en este caso fue establecer el estilo en todos los controles utilizados como DataTemplate para enviar el GotFocus -event que se manejará en el código subyacente. Luego, en el código subyacente, busqué en el árbol visual (usando VisualTreeHelper) para encontrar el ListViewItem y configuré IsSelected en true. De esta forma, no "toca" el DataContext y funciona solo con los elementos de Vista.

<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle"> 
... 
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/> 
... 

private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e) 
{ 
var control = (Control)sender; 
FocusParentListViewItem(control); 
} 

private void FocusParentListViewItem(Control control) 
{ 
var listViewItem = FindVisualParent<ListViewItem>(control); 
if (listViewItem != null) 
    listViewItem.IsSelected = true; 
} 

public static T FindVisualParent<T>(UIElement element) where T : UIElement 
{ 
UIElement parent = element; 

while (parent != null) 
{ 
    var correctlyTyped = parent as T; 

    if (correctlyTyped != null) 
    { 
     return correctlyTyped; 
    } 

    parent = VisualTreeHelper.GetParent(parent) as UIElement; 
} 

return null; 
} 
4

"Cuando agrego este disparador se selecciona el elemento cuando el foco está en un cuadro de texto niño, pero el primer comportamiento desaparece. Ahora al hacer clic fuera del cuadro de lista, el artículo es seleccionada de".

En realidad, no creo que haya perdido ese comportamiento original. Lo que sospecho que está sucediendo es que está haciendo clic directamente en el cuadro de texto desde otro lugar, por lo que el ListBoxItem subyacente nunca se seleccionó realmente. Sin embargo, si lo hiciera, verías que la selección permanecerá después de que te hayas ido como quieras.

Puede probar esto forzando que ListBoxItem se seleccione haciendo clic directamente en él (nota al margen: siempre debe darle un fondo, incluso si es 'transparente' para que pueda recibir clics del mouse, que ganó ' t si es nulo) o simplemente presionando 'Shift-Tab' para establecer el foco allí, de regreso del cuadro de texto.

Sin embargo, eso no resuelve su problema, que es que TextBox obtiene el foco pero no permite que el elemento ListBoxItem subyacente lo sepa.

Los dos enfoques que puede usar para eso son un activador de evento o un comportamiento adjunto.

El primero es un desencadenante de evento en el evento IsKeyboardFocusWithinChanged donde establece 'IsSelected' en verdadero si el foco del teclado cambió a verdadero. (Nota: la respuesta de Sheridan hace una notificación falsa de cambio pero no debe usarse en los casos en que puede seleccionar varias veces en la lista porque todo se selecciona). Pero incluso un desencadenante de evento causa problemas porque pierde los comportamientos de selección múltiple como alternar o hacer clic en rango, etc.

El otro (y mi enfoque preferido) es escribir un comportamiento adjunto que establezca en ListBoxItem, ya sea directamente o mediante un estilo si lo prefiere.

Aquí está el comportamiento adjunto. Nota: De nuevo tendrá que manejar las cosas de selección múltiple si desea implementar eso. También tenga en cuenta que, aunque estoy adjuntando el comportamiento a un ListBoxItem, dentro de Ihe echo a UIElement. De esta forma también puede usarlo en ComboBoxItem, TreeViewItem, etc. Básicamente cualquier ContainerItem en un control basado en Selector.

public class AutoSelectWhenAnyChildGetsFocus 
{ 
    public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
     "Enabled", 
     typeof(bool), 
     typeof(AutoSelectWhenAnyChildGetsFocus), 
     new UIPropertyMetadata(false, Enabled_Changed)); 

    public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); } 
    public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); } 

    private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
    { 
     var attachEvents = (bool)e.NewValue; 
     var targetUiElement = (UIElement)sender; 

     if(attachEvents) 
      targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged; 
     else 
      targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged; 
    } 

    static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     var targetUiElement = (UIElement)sender; 

     if(targetUiElement.IsKeyboardFocusWithin) 
      Selector.SetIsSelected(targetUiElement, true); 
    } 

} 

... y sólo tiene que añadir esto como un regulador de la propiedad en el estilo de su ListBoxItem

<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" /> 

supuesto, esto supone que haya importado un espacio de nombres XML llamado 'comportamientos' que apunta al espacio de nombres donde está contenida la clase Puedes poner la clase en una biblioteca compartida 'Helper', que es lo que hacemos. De esta forma, en cualquier lugar que lo deseemos, es una propiedad simple establecida en el XAML y el comportamiento se ocupa de todo lo demás.

Cuestiones relacionadas