如何将IsKeyboardFocusWithin和IsSelected一起使用?

时间:2010-06-11 11:29:37

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

即使ListBoxListBoxItem失去焦点,此样式也会保留所选项目,这在我的情况下是绝对必须的。 问题是,当ListBoxItem的一个孩子被集中注意力时,我也希望选择TextBox。为实现此目的,我添加了一个触发器,在IsSelected为真时将IsKeyboardFocusWithin设置为true:

<Trigger Property="IsKeyboardFocusWithin" Value="True">
    <Setter Property="IsSelected" Value="True" />
</Trigger>

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

我如何保持这两种行为?

3 个答案:

答案 0 :(得分: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>

代码背后:

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

答案 1 :(得分:5)

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

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

你可以通过直接点击它来强制选择ListBoxItem来测试这个(侧面注意:你应该总是给它一个背景,即使只是'透明'所以它可以接收鼠标点击,它不会如果它为空)或甚至只是按“Shift-Tab”将焦点设置在那里,从文本框返回。

然而,这并没有解决您的问题,即TextBox获得焦点但不让底层ListBoxItem知道它。

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

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

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

这是附加的行为。注意:如果要实现,则需要再次处理多选项。另请注意,虽然我将行为附加到ListBoxItem,但我在内部转换为UIElement。这样你也可以在ComboBoxItem,TreeViewItem等中使用它。基本上是基于选择器的控件中的任何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);
    }

}

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

<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />

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

答案 2 :(得分:4)

我发现IsKeyboardFocusWithin不是最佳解决方案。

在这种情况下我做的是在用作DataTemplate的所有控件上设置样式,以发送GotFocus - 事件在后面的代码中处理。然后,在后面的代码中,我搜索了可视树(使用VisualTreeHelper)以查找ListViewItem并将IsSelected设置为true。这样它就不会“触摸”DataContext,只能与View元素一起使用。

<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...

private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}

private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
    listViewItem.IsSelected = true;
}

public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element; 

while (parent != null)
{
    var correctlyTyped = parent as T; 

    if (correctlyTyped != null)
    {
        return correctlyTyped;
    }

    parent = VisualTreeHelper.GetParent(parent) as UIElement;
} 

return null;
}
相关问题