Queremos establecer el SelectedItem
de un ListBox
programáticamente y queremos que ese elemento tenga el foco para que las teclas de flecha funcionen en relación con ese elemento seleccionado. Parece lo suficientemente simple.¿Cómo establece programáticamente el foco en SelectedItem en un WPF ListBox que ya tiene foco?
El problema sin embargo es si el ListBox
ya tiene el foco del teclado cuando se configura SelectedItem
mediante programación, si bien no actualiza correctamente la propiedad IsSelected
en el ListBoxItem
, que no conjunto foco del teclado a la misma, y por lo tanto, las teclas de flecha moverse en relación con el elemento previamente enfocado en la lista y no el elemento recién seleccionado como uno esperaría.
Esto es muy confuso para el usuario, ya que hace que parezca que la selección salta cuando se utiliza el teclado, ya que vuelve a estar donde estaba antes de que tuviera lugar la selección programática.
Nota: Como ya he dicho, esto solo ocurre si establece programáticamente la propiedad SelectedItem
en un ListBox
que ya tiene el foco del teclado. Si no lo hace (o si lo hace pero te vas, luego regresa), cuando el foco del teclado vuelva al ListBox
, el elemento correcto ahora tendrá el foco del teclado como se esperaba.
Aquí hay un código de muestra que muestra este problema. Para hacer una demostración de esto, ejecute el código, use el mouse para seleccionar 'Siete' en la lista (enfocando así el ListBox
), luego haga clic en el botón 'Prueba'. Finalmente, toque la tecla 'Alt' en su teclado para revelar el foco rect. Verás que todavía está en 'Siete' y si utilizas las flechas hacia arriba y hacia abajo, están relacionadas con esa fila, no con 'Cuatro' como esperaría un usuario.
Tenga en cuenta que tengo Focusable
configurado en false
en el botón como para no robar el cuadro de lista de enfoque al presionarlo. Si no tuviera esto, el ListBox
perdería el foco al hacer clic en el botón y, por lo tanto, cuando el foco volviera al ListBox, estaría en el elemento correcto.
archivo 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>
de código subyacente:
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];
}
}
}
Nota: Algunos han sugerido el uso de IsSynchronizedWithCurrentItem
, pero que la propiedad sincroniza la SelectedItem
del ListBox
con la propiedad Current
del asociado ver. No está relacionado con el enfoque ya que este problema aún existe.
Nuestra solución alternativa es fijar temporalmente el foco en otro lugar, a continuación, establecer el elemento seleccionado, a continuación, establecer el foco de nuevo a la ListBox
pero esto tiene el efecto indeseable de nosotros tener que hacer nuestra ViewModel
consciente de la ListBox
sí, luego realice la lógica dependiendo de si tiene o no el foco, etc. (es decir, no querría simplemente decir 'Enfoque en otro lado y luego vuelva aquí, si' aquí 'no tuviera el foco ya que lo robaría) desde otro lugar.) Además, no puedes manejar esto simplemente a través de enlaces declarativos. No hace falta decir que esto es feo.
Por otra parte, naves 'feos', entonces eso es todo.
ContainerFromItem devolverá nulo si aún no se ha generado ningún contenedor para él, que es el caso de una lista virtualizada y el elemento está fuera de la pantalla. Además, si intenta establecer el valor de un enlace, se rompe ya que no tiene acceso al ListBox, y tampoco debería hacerlo. (Continúa abajo ...) – MarqueIV
Incluso un comportamiento adjunto plantea problemas ya que no se desea que el control establezca ciegamente el foco del teclado en ListBoxItem a menos que a) el control ya tenga foco (fácil de probar), yb) haya llegado el cambio desde el código subyacente (no es fácil de probar sin una subclase.) De lo contrario, podría robar inadvertidamente el foco desde otro control (caso a) o desordenar el foco en el actual (caso b) cuando esté en modo de selección múltiple y deseleccionar una fila, que normalmente debería conservar el foco del teclado, pero en su lugar se establecería en el nuevo SelectedItem, si lo hubiera, causando comportamientos extraños. – MarqueIV
En realidad, pensándolo bien, creo que tengo el problema para el caso 'B' anterior. Crea el comportamiento adjunto como dijo, pero no lo establece directamente en XAML. En su lugar, crea una propiedad en su ViewModel y vincula el comportamiento a eso. De esta forma, no necesita saber (o preocuparse) quién está escuchando. Luego, justo antes de configurar el elemento seleccionado desde el código, habilita el comportamiento, selecciona el elemento y luego deshabilita el comportamiento nuevamente. Esto aborda 'B' arriba. (Todavía necesitarías 'A', por supuesto.) Votar el tuyo como la respuesta, ya que, aunque no completo, me llevó por este camino. – MarqueIV