2010-06-11 14 views
5

Ho uno stile definito per il mio ListBoxItems con un trigger per impostare un colore di sfondo quando IsSelected è vera:Come utilizzare IsKeyboardFocusWithin e IsSelected insieme?

<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> 

Questo stile mantiene l'elemento selezionato anche quando il ListBox e ListBoxItem perde messa a fuoco, che nel mio caso è un assoluto deve. Il problema è che voglio anche che lo ListBoxItem sia selezionato quando uno dei figli del suo si focalizza. Per raggiungere questo aggiungo un trigger che imposta IsSelected true quando IsKeyboardFocusWithin è vera:

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

Quando aggiungo questo innesco l'elemento è selezionato quando il focus è su un bambino TextBox, ma il primo comportamento scompare. Ora quando faccio clic all'esterno dello ListBox, l'elemento è deselezionato.

Come posso mantenere entrambi i comportamenti?

+0

Bella soluzione XAML-only: https://stackoverflow.com/a/15383435/419761 – l33t

risposta

6

Quando la casella di riepilogo perde lo stato attivo, imposta l'elemento selezionato su null a causa del trigger. Puoi selezionare la messa a fuoco usando un codice che non deselezionerà quando perdi la messa a fuoco.

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> 

codice dietro:

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

Grazie mille! Questo è esattamente quello che stavo cercando. – jpsstavares

4

ho capito che IsKeyboardFocusWithin non è la soluzione migliore.

Quello che ho fatto in questo caso è stato quello di impostare lo stile su tutti i controlli usati come DataTemplate per inviare il GotFocus - evento da gestire nel codice sottostante. Quindi, nel codice sottostante, ho cercato l'albero visivo (utilizzando VisualTreeHelper) per trovare lo ListViewItem e impostare IsSelected su true. In questo modo non "tocca" DataContext e funziona solo con gli elementi View.

<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

"Quando aggiungo questo innesco l'elemento è selezionato quando il focus è su un TextBox bambino, ma il primo comportamento scompare. Ora, quando si fa clic al di fuori della ListBox, l'elemento viene de-selezionata."

In realtà, non penso che abbia perso quel comportamento originale. Quello che sospetto stia accadendo è che stai facendo clic direttamente nella casella di testo da qualche altra parte, quindi il ListBoxItem sottostante non è mai stato effettivamente selezionato. Se lo facesse, vedresti che la selezione rimarrà comunque dopo che te ne sei andato come vuoi.

È possibile testare questo forzando il ListBoxItem da selezionare facendo clic direttamente su di esso (nota a margine: si dovrebbe sempre dare uno sfondo, anche se solo 'trasparente' in modo che possa ricevere clic del mouse, che ha vinto ' t se è null) o anche solo premendo 'Maiusc-Tab' per impostare lo stato attivo lì, indietro dalla casella di testo.

Tuttavia, ciò non risolve il problema, ovvero che il controllo TextBox viene attivato ma non consente al ListBoxItem sottostante di conoscerlo.

I due approcci che è possibile utilizzare per questo sono un trigger di evento o un comportamento collegato.

Il primo è un evento innescato nell'evento IsKeyboardFocusWithinChanged in cui si imposta "IsSelected" su true se lo stato attivo della tastiera è impostato su true. (Nota: la risposta di Sheridan fa una notifica di falsi cambiamenti ma non dovrebbe essere usata nei casi in cui è possibile selezionare multipla nell'elenco perché tutto viene selezionato.) Ma anche un evento trigger causa problemi perché si perdono i comportamenti di selezione multipla come toggling o range-click, ecc.

L'altro (e il mio approccio preferito) è scrivere un comportamento collegato che si imposta su ListBoxItem, direttamente o tramite uno stile se si preferisce.

Ecco il comportamento allegato. Nota: dovrai ancora gestire le cose a selezione multipla se vuoi implementarle. Si noti inoltre che sebbene io stia collegando il comportamento a un ListBoxItem, all'interno del cast di UIElement. In questo modo è anche possibile utilizzarlo in ComboBoxItem, TreeViewItem, ecc. Fondamentalmente qualsiasi ContainerItem in un controllo basato su selettore.

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); 
    } 

} 

... ed è sufficiente aggiungere questo come un setter struttura in stile del vostro ListBoxItem

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

Questo ovviamente presuppone che si sia importato un namespace XML chiamato 'comportamenti' che punta allo spazio dei nomi dove la classe è contenuta. Puoi mettere la classe stessa in una libreria 'Helper' condivisa, che è ciò che facciamo. In questo modo, ovunque lo vogliamo, è una semplice proprietà impostata in XAML e il comportamento si prende cura di tutto il resto.

Problemi correlati