Мы хотим программно установить SelectedItem
из ListBox
и хотим, чтобы этот элемент имел фокус, поэтому клавиши со стрелками работают относительно выбранного элемента. Кажется, достаточно просто.
Однако проблема в том, что если ListBox
уже имеет фокус клавиатуры при настройке SelectedItem
программно, в то время как он правильно обновляет свойство IsSelected
на ListBoxItem
, он не устанавливает на него фокус клавиатуры и таким образом, клавиши со стрелками перемещаются относительно предварительно сфокусированного элемента в списке, а не вновь выбранного элемента, как можно было бы ожидать.
Это очень запутанно для пользователя, так как он делает выбор, по-видимому, прыгающим при использовании клавиатуры, когда он возвращается назад туда, где он был до того, как был выбран программный выбор.
Примечание. Как я уже сказал, это происходит только в том случае, если вы программно установите свойство SelectedItem
на ListBox
, у которого уже есть клавиатура. Если это не так (или если это произойдет, но вы уйдете, а затем вернитесь назад), когда фокус клавиатуры вернется к ListBox
, правильный элемент теперь будет иметь фокус клавиатуры, как ожидалось.
Вот пример кода, показывающий эту проблему. Для демонстрации этого, запустите код, используйте мышь, чтобы выбрать "Семь" в списке (таким образом, сосредоточив внимание на ListBox
), затем нажмите кнопку "Тест". Наконец, нажмите клавишу "Alt" на клавиатуре, чтобы открыть фокус rect. Вы увидите это на самом деле на "Семь", и если вы используете стрелки вверх и вниз, они относятся к этой строке, а не "четыре", как ожидал пользователь.
Обратите внимание, что у меня есть Focusable
, установленный на false
на кнопке, чтобы не грабить список фокуса при нажатии. Если бы у меня не было этого, ListBox
потерял бы фокус, когда вы нажмете кнопку, и, таким образом, когда фокус вернется в ListBox, он будет на правильном элементе.
Файл XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="525" Height="350" WindowStartupLocation="CenterScreen"
Title="MainWindow" x:Name="Root">
<DockPanel>
<Button Content="Test"
DockPanel.Dock="Bottom"
HorizontalAlignment="Left"
Focusable="False"
Click="Button_Click" />
<ListBox x:Name="MainListBox" />
</DockPanel>
</Window>
Code-за:
using System.Collections.ObjectModel;
using System.Windows;
namespace Test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainListBox.ItemsSource = new string[]{
"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight"
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MainListBox.SelectedItem = MainListBox.Items[3];
}
}
}
Примечание. Некоторые предложили использовать IsSynchronizedWithCurrentItem
, но это свойство синхронизирует SelectedItem
объекта ListBox
с свойством Current
соответствующего представления. Это не связано с фокусом, поскольку эта проблема все еще существует.
Наша рабочая задача - временно установить фокус в другом месте, затем установить выбранный элемент, а затем установить фокус на ListBox
, но это имеет нежелательный эффект от того, что мы должны сделать наш ViewModel
осведомленным о ListBox
, затем выполните логику в зависимости от того, имеет ли она фокус и т.д. (т.е. вы не захотите просто сказать "Фокус в другом месте", а затем вернуться сюда, если "здесь" уже не было фокуса вы бы украли его откуда-то еще.) Кроме того, вы не можете просто обрабатывать это через декларативные привязки. Излишне говорить, что это уродливо.
Опять же, "уродливые" корабли, так что это.