Set ListBoxItem.IsSelected when child TextBox is Focused

懵懂的女人 提交于 2019-11-28 20:44:25

There is a read-only property IsKeyboardFocusWithin that will be set to true if any child is focused. You can use this to set ListBoxItem.IsSelected in a 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>

As Jordan0Day correctly pointed out there can be indeed big problems using IsKeyboardFocusWithin solution. In my case a Button in a Toolbar which regards to the ListBox was also not working anymore. The same problem with focus. When clicking the button the ListBoxItem does loose the Focus and the Button updated its CanExecute method, which resulted in disabling the button just a moment before the button click command should be executed.

For me a much better solution was to use a ItemContainerStyle EventSetter as described in this 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 in the code behind of the view:

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

One way to achieve that is by implementing a custom behavior using an attached property. Basically, the attached property would be applied to the ListBoxItem using a style, and would hook up to their GotFocus event. That even fires if any descendant of the control gets the focus, so it is suitable for this task. In the event handler, IsSelected is set to true.

I wrote up a small example for you:

The Behavior Class:

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

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

The 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 XPath=@a1}"/>
        <TextBox Margin="10" Text="{Binding XPath=@a2}"/>
        <TextBox Margin="10" Text="{Binding XPath=@a3}"/>
        <TextBox Margin="10" Text="{Binding XPath=@a4}"/>
    </UniformGrid>
</UserControl>

If you create a User Control and then use it as the DataTemplate It seems to work cleaner. Then you don't have to use the dirty Style Triggers that Don't work 100% of the time.

Roel van Westerop

Edit: Someone else already had the same answer on a different question: https://stackoverflow.com/a/7555852/2484737

Continuing on Maexs' answer, using an EventTrigger instead of an EventSetter removes the need for 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>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!