2012-05-04 20 views
31

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.

Respuesta

47

Son un par de líneas de código. Si no lo quería en código subyacente, estoy seguro de que podría estar empaquetado en un comportamiento adjunto.

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    MainListBox.SelectedItem = MainListBox.Items[3]; 
    MainListBox.UpdateLayout(); // Pre-generates item containers 

    var listBoxItem = (ListBoxItem) MainListBox 
     .ItemContainerGenerator 
     .ContainerFromItem(MainListBox.SelectedItem); 

    listBoxItem.Focus(); 
} 
+2

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

+0

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

+0

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

2

¿Tal vez con un comportamiento adjunto?Algo así como

public static DependencyProperty FocusWhenSelectedProperty = DependencyProperty.RegisterAttached(
      "FocusWhenSelected", 
      typeof(bool), 
      typeof(FocusWhenSelectedBehavior), 
      new PropertyMetadata(false, new PropertyChangedCallback(OnFocusWhenSelectedChanged))); 

private static void OnFocusWhenSelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
    { 
     var i = (ListBoxItem)obj; 
     if ((bool)args.NewValue) 
      i.Selected += i_Selected; 
     else 
      i.Selected -= i_Selected; 
    } 

static void i_Selected(object sender, RoutedEventArgs e) 
{ 
    ((ListBoxItem)sender).Focus(); 
} 

y en XAML

 <Style TargetType="ListBoxItem"> 
      <Setter Property="local:FocusWhenSelectedBehavior.FocusWhenSelected" Value="True"/> 
     </Style> 
+1

Este era el lugar donde me dirigía originalmente, pero el problema es los artículos Don Aún existe si tiene una lista virtualizada, por lo que el comportamiento adjunto no se ha conectado aún para responder. Todavía tiene que hacer primero el método ScrollIntoView de alguna manera. En segundo lugar, si responde estrictamente a IsSelected, las cosas pueden volverse un poco locas cuando está en modo de selección múltiple, pero, de nuevo, eso podría considerarse un comportamiento deseado. La virtualización (que está activada por defecto) es el verdadero asesino. – MarqueIV

0

En el XAML que intentó esto y no funcionó?

<ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=YourCollectionView}" SelectedItem="{Binding SelectedItem}"></ListBox> 

Y el SelectedItem propiedad:

private YourObject _SelectedItem; 
    public YourObject SelectedItem 
    { 
     get 
     { 
      return _SelectedItem; 
     } 
     set 
     { 
      if (_SelectedItem == value) 
       return; 

      _SelectedItem = value; 

      OnPropertyChanged("SelectedItem"); 
     } 
    } 

Ahora en su código que puede hacer:

SelectedItem = theItemYouWant; 

Para mí este enfoque funciona siempre.

+0

Te llamaré para eso. ¿Lo has probado? Cargue su listbox con decir, 100 elementos. Haga clic en el 50º elemento con el mouse. A continuación, agregue un botón en su formulario que programáticamente establece el 'SelectedItem' en el décimo elemento. La selección cambia, pero a) ese elemento seleccionado no se desplaza hacia la vista, porque b) no está enfocado. Puede ver que al tocar la tecla "Alt" y aún verá que el elemento en el que hizo clic todavía tiene el foco del teclado. – MarqueIV

+0

Por cierto ... solo para estar seguro, primero tiene que seleccionar físicamente la otra fila usando el mouse o el teclado. – MarqueIV

+0

@MarquelV Probablemente estoy confundido con lo que quieres. Pensé que desea establecer la fila seleccionada programáticamente. La fila seleccionada es la fila enfocada. Tienes que usar ScrollIntoView para moverte allí. Y sí, mi enfoque no funciona para la selección múltiple. – Dummy01

0

primero) Usted debe encontrar los elementos seleccionados en cuadro de lista con ListBox.Items.IndexOf().
Segundo) Ahora agregue elementos con ListBox.SelectedItems.Add().

Este es mi código:

DataRow[] drWidgetItem = dtItemPrice.Select(widgetItemsFilter); 
lbxWidgetItem.SelectedItems.Clear(); foreach(DataRow drvItem in 
drWidgetItem) 
lbxWidgetItem.SelectedItems.Add(lbxWidgetItem.Items[dtItemPrice.Rows.IndexOf(drvItem)]); 

Si desea seleccionar un elemento de cuadro de lista se puede utilizar de esta manera:
ListBox.SelectedItem = (Tu ListBoxItem);

Si desea seleccionar algunos elementos en el cuadro de lista debe utilizar de esta manera:
ListBox.SelectedItems.Add (Su ListBoxItem);

+0

No estoy 100% seguro, pero creo que es posible que haya malentendido mi pregunta. La selección no es el problema. El foco es Su código selecciona los elementos, pero no establece el foco, que es lo que estoy tratando de resolver. – MarqueIV

+0

¡Lo siento! Entonces, creo que si enfocas tu control después de eso, tu elemento debe enfocarse. (perdón por mi mal inglés) –

+0

Pero si el control ya tiene foco, no puede volver a configurarlo. Deberías enfocarte en algo más primero. – MarqueIV

0

Sólo necesita usar ListBox.SelectedItem y luego usar ListBox.ScrollIntoView (listBox.SelectedItem)

código Ejemplo:

 private void textBox2_TextChanged(object sender, TextChangedEventArgs e) 
    { 

     var comparision = StringComparison.InvariantCultureIgnoreCase; 
     string myString = textBox2.Text; 
     List<dynamic> index = listBox.Items.SourceCollection.OfType<dynamic>().Where(x=>x.Nombre.StartsWith(myString,comparision)).ToList(); 
     if (index.Count > 0) { 
     listBox.SelectedItem= index.First(); 


      listBox.ScrollIntoView(listBox.SelectedItem); 


     } 

    } 
Cuestiones relacionadas