我为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>
即使ListBox
和ListBoxItem
失去焦点,此样式也会保留所选项目,这在我的情况下是绝对必须的。
问题是,当ListBoxItem
的一个孩子被集中注意力时,我也希望选择TextBox
。为实现此目的,我添加了一个触发器,在IsSelected
为真时将IsKeyboardFocusWithin
设置为true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
当我添加此触发器时,焦点位于子TextBox
上时会选择项目,但第一个行为消失。现在当我在ListBox
外面点击时,该项目被取消选中。
我如何保持这两种行为?
答案 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;
}