2010-06-02 5 views
13

Ich habe ein typisches MVVM-Szenario: Ich habe eine ListBox, die an eine Liste von StepsViewModels gebunden ist. Ich definiere ein DataTemplate, so dass StepViewModels als StepViews gerendert werden. Das StepView UserControl hat eine Reihe von Labels und TextBoxen.Set ListBoxItem.IsSelected, wenn untergeordnete TextBox fokussiert ist

Ich möchte das ListBoxItem auswählen, das die StepView umschließt, wenn eine TextBox fokussiert ist. Ich habe versucht, einen Stil für meine TextBoxs mit dem folgenden Trigger zu erstellen:

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

Aber ich erhalte eine Fehlermeldung mir zu sagen, dass TextBoxs keine IsSelected Eigenschaft. Ich jetzt das aber das Target ist ein ListBoxItem. Wie kann ich es funktionieren lassen?

+0

Können Sie den XAML-Code geben, der die gesamte Struktur beschreibt (Textbox, listbox) – Amsakanna

+0

ich?‘ habe gerade eine Lösung gepostet, die für mich funktioniert: http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-witout-losing-selec/37942357#37942357 –

Antwort

27

Es gibt eine schreibgeschützte Eigenschaft IsKeyboardFocusWithin, die auf True gesetzt wird, wenn ein untergeordnetes Objekt fokussiert ist. Sie können diese ListBoxItem.IsSelected in einem Trigger zu setzen verwenden:

<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

Vielen Dank! das war genau das was ich suchte. – jpsstavares

+12

Es gibt ein wirklich großes "Gotcha" mit diesem Ansatz - wenn Ihre Anwendung selbst den Fokus verliert, wird IsSelected auf false gesetzt. Das heißt, ich klicke auf das Textfeld in einem Listenfeldelement, wechsle jedoch zu einer anderen App (um beispielsweise eine StackOverflow-Frage in meinem Browser zu beantworten), wechsle dann zurück zu Ihrer Anwendung ... die IsSelected-Eigenschaft wird auf true, false, true festgelegt. im Gegensatz dazu, nur die ganze Zeit treu zu bleiben. Dies kann ein sehr großes Problem darstellen, wenn Sie Verhaltensweisen von der SelectedItem-Eigenschaft der ListBox entfernen. – Jordan0Day

+0

@ Jordan0Day Ja, das hat mich auch genagelt. Wenn etwas anderes den Fokus erhält (sogar ein anderes Steuerelement in der WPF-App), wird das ListBoxItem nicht ausgewählt. Diese Antwort löst es: http://stackoverflow.com/a/15383435/466011 – epalm

2

Eine Möglichkeit, dies zu erreichen, besteht darin, ein benutzerdefiniertes Verhalten mithilfe einer angefügten Eigenschaft zu implementieren. Grundsätzlich würde die angehängte Eigenschaft auf die ListBoxItem unter Verwendung eines Stils angewendet werden und würde sich an ihr GotFocus-Ereignis anschließen. Das wird sogar ausgelöst, wenn ein Nachfolger des Steuerelements den Fokus erhält. Daher eignet es sich für diese Aufgabe. Im Ereignishandler ist IsSelected auf true eingestellt.

Ich schrieb ein kleines Beispiel für Sie up:

Das Verhalten Klasse:

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

Das Fenster XAML:

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

Der User Control XAML:

<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

Danke für Ihre Antwort, aber Bowens Antwort macht den Job mit viel weniger Code. Vielen Dank für die Hilfe! – jpsstavares

+0

In der Tat war mir dieses Eigentum nicht bekannt, es gibt so viele :) +1 zu seiner Antwort sowie –

1

Wenn Sie einen User Control erstellen und dann verwenden, wie das Datatemplate Es scheint sauberer zu arbeiten. Dann müssen Sie nicht die schmutzigen Style Trigger verwenden, die nicht 100% der Zeit funktionieren.

5

Wie Jordan0Day richtig gezeigt hat, kann es große Probleme mit IsKeyboardFocusWithin Lösung geben. In meinem Fall funktionierte auch ein Button in einer Toolbar, der sich auf die ListBox bezieht, nicht mehr. Das gleiche Problem mit Fokus. Wenn Sie auf die Schaltfläche klicken, verliert ListBoxItem den Fokus, und die Schaltfläche aktualisiert ihre CanExecute-Methode, was dazu führte, dass die Schaltfläche nur einen Moment vor dem Ausführen des Befehls zum Klicken deaktiviert wurde.

Für mich eine viel bessere Lösung war eine ItemContainerStyle Eventsetter zu verwenden, wie in diesem Beitrag beschrieben: 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 im Code hinter der Ansicht:

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

Dies ist der richtige Weg, es zu tun. Schaut euch den verlinkten social.MSDN-Beitrag von Dr.WPF an. – Indy9000

1

Bearbeiten: Jemand anderes hatte bereits die gleiche Antwort auf eine andere Frage: https://stackoverflow.com/a/7555852/2484737

auf Maexs Fortsetzung Antwort, entfernt für die Code-behind die Notwendigkeit, einen Eventtrigger anstelle eines Eventsetter mit:

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