2010-06-02 3 views
13

J'ai un scénario MVVM typique: J'ai un ListBox qui est lié à une liste de StepsViewModels. Je définis un DataTemplate afin que StepViewModels soit rendu en tant que StepViews. Le UserControl StepView possède un ensemble d'étiquettes et de zones de texte. Ce que je veux faire est de sélectionner le ListBoxItem qui enveloppe le StepView quand un textBox est focalisé. J'ai essayé de créer un style pour mes textboxs avec le déclencheur suivant:Définir ListBoxItem.IsSelected lors de la mise au point de TextBox enfant

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

Mais je reçois une erreur me disant que textboxs ne sont pas une propriété IsSelected. Je maintenant que mais la cible est un ListBoxItem. Comment puis-je le faire fonctionner?

+0

Pouvez-vous donner le code XAML qui décrit la structure entière (zone de texte, listbox) – Amsakanna

+0

I? Je viens de poster une solution qui a fonctionné pour moi: http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-without-losing-selec/37942357#37942357 –

Répondre

27

Il existe une propriété en lecture seule IsKeyboardFocusWithin qui sera définie sur true si un enfant est mis au point. Vous pouvez utiliser cette option pour régler ListBoxItem.IsSelected dans un déclencheur:

<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

Merci beaucoup! c'était exactement ce que je cherchais. – jpsstavares

+12

Il y a un très gros "gotcha" avec cette approche - lorsque votre application elle-même perd le focus, IsSelected sera mis à false. C'est-à-dire que je clique sur la zone de texte d'un élément de liste, mais que je passe à une autre application (pour répondre à une question StackOverflow dans mon navigateur), puis revenez à votre application ... la propriété IsSelected sera définie sur true, false, true, par opposition à rester juste tout le temps. Cela peut poser un très gros problème si vous conduisez des comportements hors de la propriété SelectedItem ListBox. – Jordan0Day

+0

@ Jordan0Day Yep, cela m'a cloué aussi. Si quelque chose d'autre obtient le focus (même un autre contrôle dans l'application WPF), le ListBoxItem n'est pas sélectionné. Cette réponse résout le problème: http://stackoverflow.com/a/15383435/466011 – epalm

2

Une façon d'y parvenir consiste à implémenter un comportement personnalisé à l'aide d'une propriété jointe. Fondamentalement, la propriété attachée serait appliquée au ListBoxItem en utilisant un style, et se connecterait à leur événement GotFocus. Cela se déclenche même si un descendant du contrôle obtient le focus, il est donc approprié pour cette tâche. Dans le gestionnaire d'événements, IsSelected est défini sur true.

j'ai écrit un petit exemple pour vous:

La classe Comportement:

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

Le XAML de fenêtre:

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

Le XAML de contrôle de l'utilisateur:

<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

Merci pour votre réponse, mais la réponse de Bowen fait le travail avec beaucoup moins de code. Merci beaucoup pour l'aide si! – jpsstavares

+0

En effet, je n'étais pas au courant de cette propriété, il y en a tellement :) +1 à sa réponse aussi –

1

Si vous créez un contrôle utilisateur, puis l'utiliser comme DataTemplate Il semble fonctionner plus propre. Ensuite, vous ne devez pas utiliser les déclencheurs de style sale qui ne fonctionnent pas 100% du temps.

5

Comme Jordan0Day a correctement souligné qu'il peut y avoir en effet de gros problèmes en utilisant la solution IsKeyboardFocusWithin. Dans mon cas, un bouton dans une barre d'outils concernant la ListBox ne fonctionnait plus. Le même problème avec focus. Lorsque vous cliquez sur le bouton, le ListBoxItem perd la Focus et le bouton met à jour sa méthode CanExecute, ce qui entraîne la désactivation du bouton juste avant que la commande de clic sur le bouton ne soit exécutée.

Pour moi, une solution beaucoup mieux est d'utiliser un EventSetter ItemContainerStyle comme décrit dans ce 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 dans le code derrière de la vue:

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

C'est la bonne façon de le faire. Vous devriez également regarder le message social.MSDN lié à Dr.WPF. – Indy9000

1

Edit: Quelqu'un d'autre a déjà eu la même réponse sur une autre question: https://stackoverflow.com/a/7555852/2484737

Continuant sur Maexs de la réponse, en utilisant un EventTrigger au lieu d'un EventSetter supprime le besoin de 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> 
Questions connexes