如何将IsKeyboardFocusWithin和IsSelected一起使用?

jps*_*res 6 wpf listbox listboxitem

我有一个为我定义的样式ListBoxItems,触发器设置IsSelected为True 时的背景颜色:

    <Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Border Name="Border" Padding="0" SnapsToDevicePixels="true">
                        <ContentPresenter />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
Run Code Online (Sandbox Code Playgroud)

这种风格即使在ListBoxListBoxItem失去焦点时也能保持所选项目,在我的情况下绝对是必须的.问题是我还希望ListBoxItem在其中一个TextBox孩子得到关注时被选中.为了达到这个目的,我添加了一个触发器,IsSelectedIsKeyboardFocusWithin为true 时设置为true:

<Trigger Property="IsKeyboardFocusWithin" Value="True">
    <Setter Property="IsSelected" Value="True" />
</Trigger>
Run Code Online (Sandbox Code Playgroud)

当我添加此触发器时,焦点在子节点上时会选择Item TextBox,但第一个行为会消失.现在当我点击外面时ListBox,该项目被取消选中.

我怎样才能保持这两种行为?

Wal*_*mer 6

当您的列表框失去焦点时,它会因您的触发器将所选项设置为null.您可以使用一些代码来选择焦点,这些代码在您失去焦点时不会取消选择.

XAML:

<Window x:Class="SelectedTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">

    <StackPanel>
        <TextBox Text="Loose focus here" />
        <ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
                        <TextBox Text="{Binding .}" Margin="10" />
                        <TextBox Text="{Binding .}" Margin="10" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="SnapsToDevicePixels" Value="true"/>
                    <Setter Property="OverridesDefaultStyle" Value="true"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="ListBoxItem">
                                <Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
                                    <ContentPresenter />
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsSelected" Value="True">
                                        <Setter TargetName="Border" Property="Background" Value="Red"/>
                                    </Trigger>                                   
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

代码背后:

private void OnChildGotFocus(object sender, RoutedEventArgs e) 
{   
   _listBox.SelectedItem = (sender as StackPanel).DataContext; 
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*eIV 5

"当我添加此触发器时,焦点位于子TextBox上时会选择Item,但第一个行为消失.现在,当我在ListBox外部单击时,该项目将被取消选中."

实际上,我认为它没有失去原有的行为.我怀疑发生的是你从其他地方直接点击文本框,这样底层的ListBoxItem实际上从未被选中.但是,如果确实如此,您会看到在您离开后仍然保留选择.

您可以通过直接单击它来强制选择ListBoxItem来测试它(侧注:你应该总是给它一个背景,即使只是'透明'所以它可以接收鼠标点击,如果它是null,它就不会或者甚至只是点击"Shift-Tab"将焦点设置在那里,从文本框返回.

但是,这并不能解决您的问题,即TextBox获得焦点但不会让底层ListBoxItem知道它.

您可以使用的两种方法是事件触发器或附加行为.

第一个是IsKeyboardFocusWithinChanged事件的事件触发器,如果​​键盘焦点更改为true,则将"IsSelected"设置为true.(注意:Sheridan的答案会进行虚假更改通知,但不应该用于您可以在列表中进行多选的情况,因为所有内容都会被选中.)但即使是事件触发器也会导致问题,因为您丢失了多选行为例如切换或范围点击等

另一个(以及我的首选方法)是编写一个您在ListBoxItem上直接设置的附加行为,或者如果您愿意,可以通过样式编写.

这是附加的行为.注意:如果要实现,则需要再次处理多选项.另请注意,虽然我将行为附加到ListBoxItem,但我在内部转换为UIElement.这样你也可以在ComboBoxItem,TreeViewItem等中使用它.基本上是基于Selector的控件中的任何ContainerItem.

public class AutoSelectWhenAnyChildGetsFocus
{
    public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
        "Enabled",
        typeof(bool),
        typeof(AutoSelectWhenAnyChildGetsFocus),
        new UIPropertyMetadata(false, Enabled_Changed));

    public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
    public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }

    private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var attachEvents = (bool)e.NewValue;
        var targetUiElement = (UIElement)sender;

        if(attachEvents)
            targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
        else
            targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
    }

    static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var targetUiElement = (UIElement)sender;

        if(targetUiElement.IsKeyboardFocusWithin)
            Selector.SetIsSelected(targetUiElement, true);
    }

}
Run Code Online (Sandbox Code Playgroud)

...而你只需在ListBoxItem的样式中将其添加为属性设置器

<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
Run Code Online (Sandbox Code Playgroud)

当然,这假设您已导入名为"behavior"的XML命名空间,该命名空间指向包含该类的命名空间.您可以将类本身放在共享的"Helper"库中,这就是我们所做的.这样,在我们想要它的任何地方,它都是在XAML中设置的简单属性,并且行为会处理其他所有事情.