当子TextBox获得焦点时,设置ListBoxItem的IsSelected。

14
我有一个典型的MVVM场景:我有一个ListBox,其绑定到StepViewModels列表。 我定义了一个DataTemplate,以便将StepViewModels呈现为StepViews。 StepView UserControl具有一组标签和文本框。
我想要做的是在textBox聚焦时选择包装StepView的ListBoxItem。 我尝试使用以下触发器为我的TextBox创建样式:
<Trigger Property="IsFocused" Value="true">
    <Setter TargetName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Property="IsSelected" Value="True"/>
</Trigger>

但是我收到一个错误提示,告诉我TextBoxs没有IsSelected属性。我知道,但Target是一个ListBoxItem。 我该如何让它工作?


你能提供描述整个结构的XAML代码吗?(包括文本框和列表框) - Amsakanna
我刚刚发布了对我有效的解决方案:https://dev59.com/uW_Xa4cB1Zd3GeqP2Y1g#37942357 - Mr.B
5个回答

33

有一个只读属性IsKeyboardFocusWithin,如果任何子元素获得焦点,它将设置为true。您可以使用此属性在触发器中设置ListBoxItem.IsSelected:

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

13
这种方法有一个非常大的问题,就是当应用程序失去焦点时,IsSelected属性将被设置为false。也就是说,如果我点击列表框项内的文本框但切换到另一个应用程序(例如在浏览器中回答StackOverflow问题),然后再切换回你的应用程序... IsSelected属性将会被设置为true、false、true,而不是一直保持为true。如果您根据ListBox的SelectedItem属性来驱动行为,这可能会带来非常大的问题。 - Jordan0Day
@Jordan0Day 是的,这个问题也困扰了我。如果其他任何东西获得焦点(即使是 WPF 应用程序中的其他控件),ListBoxItem 也会被取消选择。这个答案解决了这个问题:https://dev59.com/uW_Xa4cB1Zd3GeqP2Y1g#15383435 - epalm

5

正如Jordan0Day所指出的那样,使用IsKeyboardFocusWithin解决方案可能会出现大问题。在我的情况下,工具栏中的一个按钮也不再起作用了,它与ListBox有关。聚焦也存在同样的问题。单击按钮时,ListBoxItem会失去焦点,并且该按钮会更新其CanExecute方法,这导致在按钮点击命令执行之前瞬间禁用该按钮。

对我来说,一个更好的解决方案是使用ItemContainerStyle EventSetter,如本帖所述:当内部控件被使用时选择ListboxItem

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>

视图中的代码后台中的事件处理程序:

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

这是正确的做法。还应该查看Dr.WPF在社交.MSDN上发布的链接帖子。 - Indy9000

2

通过使用附加属性来实现自定义行为,是实现这一目标的一种方法。基本上,应用于 ListBoxItem 的附加属性将使用样式进行应用,并连接到其 GotFocus 事件。如果控件的任何后代获得焦点,则会触发该事件,因此它非常适合此任务。在事件处理程序中,将 IsSelected 设置为 true

我为您编写了一个小例子:

行为类:

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:

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

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

谢谢你的回答,但 Bowen 的回答用了更少的代码就完成了任务。还是非常感谢你的帮助! - jpsstavares
确实,我不知道那个属性,有这么多 :) 对他的回答加一 - Aviad P.

1

编辑:有人在不同的问题上已经给出了相同的答案:https://dev59.com/OEvSa4cB1Zd3GeqPg70K#7555852

继续Maexs的回答,使用EventTrigger而不是EventSetter可以省去代码后台:

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

1
如果您创建了一个用户控件并将其用作数据模板,似乎会更加清晰。这样,您就不必使用那些不总是有效的样式触发器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接