Установить ListBoxItem.IsSelected, когда дочерний TextBox сфокусирован
У меня есть типичный сценарий MVVM: у меня есть ListBox, который привязан к списку StepsViewModels. Я определяю DataTemplate так, чтобы StepViewModels отображались как StepViews. StepView UserControl имеет набор меток и текстовых полей.
То, что я хочу сделать, это выбрать ListBoxItem, который оборачивает StepView, когда textBox сфокусирован. Я попытался создать стиль для моих TextBox с помощью следующего триггера:
<Trigger Property="IsFocused" Value="true">
<Setter TargetName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Property="IsSelected" Value="True"/>
</Trigger>
Но я получаю сообщение об ошибке, сообщающее, что TextBoxs не имеет свойства IsSelected. Я теперь что, но Цель является ListBoxItem. Как я могу заставить это работать?
5 ответов
Существует свойство только для чтения 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>
Как правильно указал 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>
EventHandler в коде позади представления:
private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e)
{
(sender as ListBoxItem).IsSelected = true;
}
Одним из способов достижения этого является реализация пользовательского поведения с использованием присоединенного свойства. По сути, прикрепленное свойство будет применяться к 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>
Изменить: кто-то уже имел тот же ответ на другой вопрос: /questions/47927083/vyiberite-listboxitem-esli-textbox-v-itemtemplate-poluchaet-fokus/47927124#47927124
Продолжая ответ 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>
Если вы создаете пользовательский элемент управления, а затем используете его как шаблон данных, похоже, он работает чище. Тогда вам не нужно использовать грязные триггеры стиля, которые не работают 100% времени.