2012-05-04 17 views
31

Vogliamo impostare lo SelectedItem di un ListBox a livello di programmazione e desidera che quell'elemento abbia il focus in modo che i tasti freccia funzionino relativamente a quell'elemento selezionato. Sembra abbastanza semplice.Come si imposta a livello di programmazione l'elemento SelectedItem in un ListBox WPF che ha già lo stato attivo?

Il problema, tuttavia, è se il ListBox ha già della tastiera quando l'impostazione SelectedItem programmazione, mentre non aggiorna correttamente la proprietà IsSelected sulla ListBoxItem, esso non set tastiera fuoco ad esso, e quindi, i tasti freccia spostati rispetto all'elemento focalizzato in precedenza nell'elenco e non all'elemento appena selezionato come ci si aspetterebbe.

Questo è molto confuso per l'utente in quanto fa apparire la selezione a saltare quando si utilizza la tastiera come si aggancia indietro a dove era prima che la selezione programmatica ha avuto luogo.

Nota: come ho detto, ciò si verifica solo se si imposta la proprietà SelectedItem su un ListBox che ha già l'attivazione della tastiera. Se non lo fa (o se lo fa, ma te ne vai, quindi torna indietro), quando lo stato attivo della tastiera ritorna allo ListBox, l'elemento corretto avrà ora lo stato attivo della tastiera come previsto.

Ecco alcuni esempi di codice che mostrano questo problema. Per dimostrarlo, esegui il codice, usa il mouse per selezionare "Sette" nell'elenco (mettendo così a fuoco lo ListBox), quindi fai clic sul pulsante "Prova". Infine, tocca il tasto "Alt" sulla tastiera per rivelare il punto focale. Vedrai che è ancora su "Sette" e se usi le frecce su e giù, sono relative a quella riga, non "Quattro" come ci si aspetterebbe un utente.

Nota che ho Focusable impostato su false sul pulsante per non derubare la casella di elenco quando lo si preme. Se non avessi questo, lo ListBox perderebbe la messa a fuoco quando clicchi sul pulsante, e quindi, quando il focus tornerà al ListBox, sarebbe sull'elemento corretto.

file 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> 

Codice-behind:

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: alcuni hanno suggerito di usare IsSynchronizedWithCurrentItem, ma che la proprietà sincronizza il SelectedItem del ListBox con l'Current proprietà del associata vista. Non è correlato al focus poiché questo problema esiste ancora.

nostro work-around è quello di impostare temporaneamente la messa a fuoco da qualche altra parte, quindi impostare l'elemento selezionato, quindi impostare la messa a fuoco di nuovo alla ListBox, ma questo ha l'effetto undesireable di noi dover fare la nostra ViewModel consapevoli del ListBox stesso, quindi esegui la logica a seconda che abbia o meno la messa a fuoco, ecc. (es. non vorresti dire semplicemente 'Concentrati altrove, poi torna qui, se' qui 'non avesse già l'obiettivo come lo avresti rubato da qualche altra parte). Inoltre, non è possibile gestirlo semplicemente attraverso associazioni dichiarative. Inutile dire che questo è brutto.

Poi di nuovo, "brutte" navi, quindi c'è.

risposta

47

È un paio di linee di codice. Se non lo volessi in code-behind, sono sicuro che potrebbe essere impacchettato in un comportamento allegato.

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 restituirà Null se non è stato ancora generato alcun contenitore per esso, che è il caso di un elenco virtualizzato e l'elemento è fuori schermo. Inoltre, se si tenta di impostare il valore da un'associazione esso si interrompe non avendo accesso al ListBox, né si dovrebbe. (Continua di seguito ...) – MarqueIV

+0

Anche un comportamento collegato pone problemi in quanto non si vorrebbe che il controllo impostasse ciecamente lo stato attivo della tastiera su ListBoxItem a meno che a) il controllo avesse già lo stato attivo (facile da testare) eb) il cambiamento avvenne dal code-behind (non facile da testare senza sottoclasse.) Altrimenti si potrebbe inavvertitamente rubare la messa a fuoco da un altro controllo (caso a) o rovinare la messa a fuoco in quella corrente (caso b) in modalità multi-select e deselezionando una riga, che normalmente dovrebbe mantenere il focus della tastiera, ma dovrebbe essere impostata sul nuovo SelectedItem, se presente, causando comportamenti strani. – MarqueIV

+0

In realtà, a pensarci bene, penso di avere il rimedio per il caso 'B' sopra. Crea il comportamento allegato come hai detto tu, ma non lo imposta direttamente in XAML. Invece si crea una proprietà sul ViewModel e si lega il comportamento a quello. In questo modo non hai bisogno di sapere (o preoccuparti) chi sta ascoltando. Quindi, appena prima di impostare l'elemento selezionato dal codice sottostante, abiliti il ​​comportamento, seleziona l'elemento, quindi disattiva nuovamente il comportamento. Questo indirizzo 'B' sopra. (Avresti comunque bisogno di "A", ovviamente.) Votare la tua come risposta, anche se non completa, mi ha portato su questa strada. – MarqueIV

2

Forse con un comportamento collegato?Qualcosa di simile

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(); 
} 

e in XAML

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

Questo era dove stavo dirigendo in origine, ma la questione è quegli elementi don Esistono ancora se si dispone di un elenco virtualizzato, in modo che il comportamento collegato non è stato ancora cablato per rispondere. Devi ancora prima eseguire il metodo ScrollIntoView. In secondo luogo, se rispondi in modo rigoroso a IsSelected, le cose possono diventare un po 'folle quando si è in modalità di selezione multipla, ma poi di nuovo si potrebbe considerare un comportamento desiderato. La virtualizzazione (attivata per impostazione predefinita) è il vero killer. – MarqueIV

0

Nel vostro XAML si è provato questo e non ha funzionato?

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

E il SelectedItem Proprietà:

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

      _SelectedItem = value; 

      OnPropertyChanged("SelectedItem"); 
     } 
    } 

Ora nel codice che si può fare:

SelectedItem = theItemYouWant; 

Per me questo approccio funziona sempre.

+0

Ho intenzione di chiamarti su quello. L'hai provato? Carica la tua lista con diciamo, 100 voci. Fai clic sul 50esimo elemento con il mouse. Quindi aggiungere un pulsante nel modulo che imposta "SelectedItem" al 10 ° elemento al livello di programmazione. La selezione cambia, ma a) quella voce selezionata non scorre in vista, perché b) non è focalizzata. Puoi vederlo toccando il tasto "Alt" e continuerai a vedere che l'elemento su cui hai fatto clic ha ancora il focus della tastiera. – MarqueIV

+0

A proposito ... per sicurezza, devi prima selezionare fisicamente l'altra riga usando il mouse o la tastiera. – MarqueIV

+0

@MarquelV Sono probabilmente confuso su quello che vuoi. Ho pensato di voler impostare la riga selezionata in modo programmatico. La riga selezionata è la riga focalizzata. Devi usare ScrollIntoView per spostarti lì. E sì, il mio approccio non funziona per la selezione multipla. – Dummy01

0

First) È necessario trovare elementi selezionati in listbox con ListBox.Items.IndexOf().
Secondo) Ora aggiungere elementi con ListBox.SelectedItems.Add().

Questo è il mio codice:

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

Se si desidera selezionare un elemento nella ListBox si può usare in questo modo:
ListBox.SelectedItem = (Il tuo ListBoxItem);

Se si desidera selezionare alcuni elementi in ListBox è necessario utilizzare in questo modo:
ListBox.SelectedItems.Add (Il tuo ListBoxItem);

+0

Non sono sicuro al 100%, ma penso che potresti aver frainteso la mia domanda. La selezione non è il problema. Il fuoco è. Il tuo codice seleziona gli articoli, ma non mette a fuoco, che è quello che sto cercando di risolvere. – MarqueIV

+0

Siamo spiacenti! Quindi, penso che se focalizzi il tuo controllo dopo che il tuo oggetto deve focalizzarsi. (mi dispiace per il mio cattivo inglese) –

+0

Ma se il controllo ha già lo stato attivo, non è possibile reimpostarlo. Dovresti mettere a fuoco qualcos'altro prima. – MarqueIV

0

è necessario utilizzare solo ListBox.SelectedItem e quindi utilizzare ListBox.ScrollIntoView (listBox.SelectedItem)

codice Esempio:

 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); 


     } 

    } 
Problemi correlati