2010-06-02 14 views
13

Mam typowy scenariusz MVVM: Mam ListBox, który jest powiązany z listą StepsViewModels. Definiuje DataTemplate, tak aby StepViewModels były renderowane jako StepViews. StepView UserControl ma zestaw etykiet i pól tekstowych.Zestaw ListBoxItem.IsSelected, gdy podrzędny TextBox jest skoncentrowany

Co chcę zrobić, to wybrać ListBoxItem, który owija StepView, gdy pole tekstowe jest skupione. Próbowałem utworzyć styl dla moich TextBoxs z następującym spustu:

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

Ale pojawia się błąd mówiąc mi, że TextBoxs nie mają właściwość IsSelected. Teraz, ale celem jest ListBoxItem. Jak mogę to sprawić?

+0

można dać kod XAML, który opisuje całą strukturę (tekstowe, listbox) – Amsakanna

+0

ja? Właśnie napisane rozwiązanie, które działało dla mnie: http://stackoverflow.com/questions/15366806/wpf-setting-selected-for-listbox-when-textbox-has-focus-without-losing-selec/37942357#37942357 –

Odpowiedz

27

Istnieje własność tylko do odczytu IsKeyboardFocusWithin, która zostanie ustawiona na wartość true, jeśli dowolne dziecko jest aktywne. Można to wykorzystać, aby ustawić ListBoxItem.IsSelected w wyzwalacz:

<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

Wielkie dzięki! właśnie tego szukałem. – jpsstavares

+12

Jest jedna naprawdę duża "gotcha" z tym podejściem - kiedy twoja aplikacja traci skupienie, IsSelected będzie ustawione na false. Oznacza to, że klikam pole tekstowe wewnątrz elementu listy, ale przełączam się do innej aplikacji (np. W celu odpowiedzi na pytanie StackOverflow w mojej przeglądarce), a następnie przełącz się z powrotem do aplikacji ... właściwość IsSelected zostanie ustawiona na wartość true, false, true, w przeciwieństwie do pozostawania wiernym przez cały czas. Może to stanowić bardzo duży problem, jeśli motywujesz zachowania z właściwości SelectedItem ListBox. – Jordan0Day

+0

@ Jordan0Day Tak, to też mnie przybrało. Jeśli cokolwiek innego skupi się (nawet jakaś inna kontrola w aplikacji WPF), element ListBoxItem zostanie odznaczony. Ta odpowiedź rozwiązuje: http://stackoverflow.com/a/15383435/466011 – epalm

2

Jednym ze sposobów osiągnięcia tego jest zaimplementowanie niestandardowego zachowania przy użyciu dołączonej właściwości. Zasadniczo załączona właściwość zostanie zastosowana do stylu ListBoxItem i będzie podłączana do zdarzenia GotFocus. To nawet wystrzeliwuje, jeśli jakikolwiek potomek kontroli dostanie ostrość, więc nadaje się do tego zadania. W module obsługi zdarzenia IsSelected jest ustawiony na true.

napisałem małą przykład dla Ciebie:

zachowanie Klasa:

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

XAML Okno:

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

XAML kontrolny użytkownika:

<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

Dzięki za odpowiedź, ale odpowiedź Bowena wykonuje tę pracę przy znacznie mniejszym kodzie. Bardzo dziękuję za pomoc! – jpsstavares

+0

Rzeczywiście, nie wiedziałem o tej nieruchomości, jest ich tak wiele :) +1 do jego odpowiedzi –

1

Jeśli utworzysz kontroli użytkownika, a następnie użyć go jako DataTemplate Wydaje pracować czystsze. Wtedy nie musisz używać brudnych wyzwalaczy stylu, które nie działają w 100% przypadków.

5

Jak słusznie zauważył Jordan0Day, mogą być rzeczywiście duże problemy przy użyciu rozwiązania IsKeyboardFocusWithin. W moim przypadku przycisk w pasku narzędzi, który dotyczy ListBox, również nie działał. Ten sam problem z fokusem. Po kliknięciu przycisku element ListBoxItem traci ostrość, a przycisk zaktualizował swoją metodę CanExecute, co spowodowało wyłączenie przycisku tuż przed wykonaniem polecenia kliknięcia przycisku.

Dla mnie o wiele lepszym rozwiązaniem było użycie ItemContainerStyle EventSetter jak opisane w tym poście: 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> 

Podprogram w kodzie za zdania:

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

To jest właściwy sposób, aby to zrobić. Powinieneś również spojrzeć na połączony wpis social.MSDN z Dr.WPF. – Indy9000

1

Edycja: Ktoś jeszcze miał tę samą odpowiedź na inne pytanie: https://stackoverflow.com/a/7555852/2484737

Kontynuując Maexs' odpowiedź, stosując EventTrigger zamiast EventSetter eliminuje potrzebę kodu źródłowego:

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