7

我有一个为我定义的样式,当它为 TrueListBoxItems时,触发器可以设置背景颜色:IsSelected

    <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失去焦点时,这种风格也会保持选定的项目,这在我的情况下是绝对必须的。问题是我还希望在ListBoxItemTextBox的一个孩子集中注意力时选择 。为了实现这一点,我添加了一个触发器,当为IsSelected真时设置IsKeyboardFocusWithin为真:

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

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

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

4

3 回答 3

6

当您的列表框失去焦点时,由于您的触发器,它会将所选项目设置为空。您可以使用后面的一些代码选择焦点,当您失去焦点时不会取消选择。

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; 
}
于 2010-06-11T13:19:45.790 回答
5

“当我添加此触发器时,当焦点位于子 TextBox 上时,该项目被选中,但第一个行为消失了。现在,当我在 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);
    }

}

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

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

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

于 2012-10-05T19:04:07.217 回答
4

我发现这IsKeyboardFocusWithin不是最好的解决方案。

在这种情况下,我所做的是在所有用作 DataTemplate 的控件上设置样式,以发送GotFocus要在后面的代码中处理的事件。然后,在后面的代码中,我搜索了可视化树(使用VisualTreeHelper)找到ListViewItem并设置IsSelectedtrue. 这样它就不会“接触” 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;
}
于 2012-09-24T10:00:39.143 回答