2010-06-02 16 views
13

Ho un tipico scenario MVVM: Ho un ListBox che è associato a un elenco di StepsViewModels. Definisco un DataTemplate in modo che StepViewModels sia reso come StepViews. StepView UserControl ha un set di etichette e TextBox.Imposta ListBoxItem.IsSelezionata quando FocusBox figlio è attivo

Quello che voglio fare è selezionare ListBoxItem che avvolge lo StepView quando un textBox è focalizzato. Ho cercato di creare uno stile per i miei TextBoxs con il seguente trigger:

<Trigger Property="IsFocused" Value="true"> 
    <Setter TargetName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Property="IsSelected" Value="True"/> 
</Trigger> 

Ma ottengo un errore che mi diceva che TextBoxs non hanno una proprietà IsSelected. Ora quello ma il bersaglio è un ListBoxItem. Come posso farlo funzionare?

+0

Puoi dare il codice XAML che descrive l'intera struttura (textbox, listbox) – Amsakanna

+0

io?' Ho appena pubblicato una soluzione che ha funzionato per me: http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-without-losing-selec/37942357#37942357 –

risposta

27

C'è una proprietà di sola lettura IsKeyboardFocusWithin che verrà impostata su true se qualsiasi figlio è focalizzato. È possibile utilizzare questo per impostare ListBoxItem.IsSelected in un trigger:

<ListBox ItemsSource="{Binding SomeCollection}" HorizontalAlignment="Left"> 
    <ListBox.ItemContainerStyle> 
     <Style TargetType="{x:Type ListBoxItem}"> 
      <Style.Triggers> 
       <Trigger Property="IsKeyboardFocusWithin" Value="True"> 
        <Setter Property="IsSelected" Value="True" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </ListBox.ItemContainerStyle> 
    <ListBox.ItemTemplate> 
     <DataTemplate> 
      <TextBox Width="100" Margin="5" Text="{Binding Name}"/> 
     </DataTemplate> 
    </ListBox.ItemTemplate> 
</ListBox> 
+0

Grazie mille! quello era esattamente quello che stavo cercando. – jpsstavares

+12

C'è un grosso "raggiro" con questo approccio: quando la tua applicazione perde lo stato attivo, IsSelected sarà impostato su false. Cioè, faccio clic sulla casella di testo all'interno di una voce di elenco, ma passa a un'altra app (ad esempio per rispondere a una domanda StackOverflow nel mio browser), quindi ritorna alla tua applicazione ... la proprietà IsSelected sarà impostata su true, false, true, al contrario di rimanere sempre vero per tutto il tempo. Questo può rappresentare un grosso problema se stai guidando i comportamenti fuori dalla proprietà SelectedItem di ListBox. – Jordan0Day

+0

@ Jordan0Day Sì, questo mi ha inchiodato anche io. Se qualcos'altro ottiene il focus (anche qualche altro controllo all'interno dell'app WPF), il ListBoxItem viene deselezionato. Questa risposta lo risolve: http://stackoverflow.com/a/15383435/466011 – epalm

2

Un modo per ottenere ciò è implementare un comportamento personalizzato utilizzando una proprietà associata. Fondamentalmente, la proprietà associata verrebbe applicata allo ListBoxItem utilizzando uno stile e si collegherebbe al proprio evento GotFocus. Che si attiva anche se qualsiasi discendente del controllo ottiene l'attenzione, quindi è adatto per questo compito. Nel gestore eventi, IsSelected è impostato su true.

ho scritto un piccolo esempio per voi:

Il comportamento Classe:

public class MyBehavior 
{ 
    public static bool GetSelectOnDescendantFocus(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(SelectOnDescendantFocusProperty); 
    } 

    public static void SetSelectOnDescendantFocus(
     DependencyObject obj, bool value) 
    { 
     obj.SetValue(SelectOnDescendantFocusProperty, value); 
    } 

    public static readonly DependencyProperty SelectOnDescendantFocusProperty = 
     DependencyProperty.RegisterAttached(
      "SelectOnDescendantFocus", 
      typeof(bool), 
      typeof(MyBehavior), 
      new UIPropertyMetadata(false, OnSelectOnDescendantFocusChanged)); 

    static void OnSelectOnDescendantFocusChanged(
     DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ListBoxItem lbi = d as ListBoxItem; 
     if (lbi == null) return; 
     bool ov = (bool)e.OldValue; 
     bool nv = (bool)e.NewValue; 
     if (ov == nv) return; 
     if (nv) 
     { 
      lbi.GotFocus += lbi_GotFocus; 
     } 
     else 
     { 
      lbi.GotFocus -= lbi_GotFocus; 
     } 
    } 

    static void lbi_GotFocus(object sender, RoutedEventArgs e) 
    { 
     ListBoxItem lbi = sender as ListBoxItem; 
     lbi.IsSelected = true; 
    } 
} 

Il XAML finestra:

<Window x:Class="q2960098.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:sys="clr-namespace:System;assembly=mscorlib" 
     Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:q2960098"> 
    <Window.Resources> 
     <DataTemplate x:Key="UserControlItemTemplate"> 
      <Border BorderBrush="Black" BorderThickness="5" Margin="10"> 
       <my:UserControl1/> 
      </Border> 
     </DataTemplate> 
     <XmlDataProvider x:Key="data"> 
      <x:XData> 
       <test xmlns=""> 
        <item a1="1" a2="2" a3="3" a4="4">a</item> 
        <item a1="a" a2="b" a3="c" a4="d">b</item> 
        <item a1="A" a2="B" a3="C" a4="D">c</item> 
       </test> 
      </x:XData> 
     </XmlDataProvider> 
     <Style x:Key="MyBehaviorStyle" TargetType="ListBoxItem"> 
      <Setter Property="my:MyBehavior.SelectOnDescendantFocus" Value="True"/> 
     </Style> 
    </Window.Resources> 
    <Grid> 
     <ListBox ItemTemplate="{StaticResource UserControlItemTemplate}" 
       ItemsSource="{Binding Source={StaticResource data}, XPath=//item}" 
       HorizontalContentAlignment="Stretch" 
       ItemContainerStyle="{StaticResource MyBehaviorStyle}"> 

     </ListBox> 
    </Grid> 
</Window> 

Il XAML di controllo utente:

<UserControl x:Class="q2960098.UserControl1" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <UniformGrid> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
    </UniformGrid> 
</UserControl> 
+0

Grazie per la tua risposta, ma la risposta di Bowen fa il lavoro con molto meno codice. Grazie mille per l'aiuto! – jpsstavares

+0

In effetti, non ero a conoscenza di quella proprietà, ce ne sono così tanti :) +1 alla sua risposta pure –

1

Se si crea un controllo utente e quindi utilizzarlo come il DataTemplate Sembra funzionare più pulito. Quindi non è necessario utilizzare i trigger stile sporchi che non funzionano il 100% del tempo.

5

Come giustamente giustamente sottolineato da Jordan0Day, possono esserci dei grossi problemi utilizzando la soluzione IsKeyboardFocusWithin. Nel mio caso un pulsante in una barra degli strumenti che riguarda la ListBox non funzionava più. Lo stesso problema con lo stato attivo. Quando si fa clic sul pulsante, ListBoxItem perde la messa a fuoco e il pulsante ha aggiornato il suo metodo CanExecute, che ha provocato la disattivazione del pulsante un attimo prima dell'esecuzione del comando del clic del pulsante.

Per me una soluzione molto migliore è stato quello di utilizzare un ItemContainerStyle EventSetter come descritto in questo post: ListboxItem selection when the controls inside are used

XAML:

<Style x:Key="MyItemContainer.Style" TargetType="{x:Type ListBoxItem}"> 
    <Setter Property="Background" Value="LightGray"/> 
    <EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" /> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type ListBoxItem}"> 
       <Border x:Name="backgroundBorder" Background="White"> 
        <ContentPresenter Content="{TemplateBinding Content}"/> 
       </Border> 
      <ControlTemplate.Triggers> 
       <Trigger Property="IsSelected" Value="True"> 
        <Setter TargetName="backgroundBorder" Property="Background" Value="#FFD7E6FC"/> 
       </Trigger> 
      </ControlTemplate.Triggers> 
     </ControlTemplate> 
    </Setter.Value> 
</Setter> 
</Style> 

EventHandler nel codice dietro della vista:

private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e) 
{ 
    (sender as ListBoxItem).IsSelected = true; 
} 
+0

Questo è il modo corretto per farlo. Dovrebbe guardare anche il post social.MSDN collegato da Dr.WPF. – Indy9000

1

Modifica: qualcun altro aveva già la stessa risposta a un'altra domanda: https://stackoverflow.com/a/7555852/2484737

Proseguendo Maexs' risposta, utilizzando un EventTrigger invece di un EventSetter elimina la necessità di code-behind:

<Style.Triggers> 
    <EventTrigger RoutedEvent="GotKeyboardFocus"> 
     <BeginStoryboard> 
      <Storyboard > 
       <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected" > 
        <DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/> 
       </BooleanAnimationUsingKeyFrames> 
      </Storyboard> 
     </BeginStoryboard> 
    </EventTrigger> 
</Style.Triggers> 
Problemi correlati