2012-05-04 5 views
31

Wir möchten die SelectedItem eines ListBox programmgesteuert festlegen und möchten, dass das Element dann den Fokus hat, damit die Pfeiltasten relativ zu diesem ausgewählten Element funktionieren. Scheint einfach genug.Wie programmierst du den Fokus programmgesteuert auf das SelectedItem in einer WPF-ListBox, die bereits im Fokus ist?

Das Problem ist jedoch, wenn die ListBox bereits Tastaturfokus hat, wenn SelectedItem programmatisch einstellen, während es richtig die IsSelected Eigenschaft auf den ListBoxItem, es es nicht Tastaturfokus nicht aktualisiert und damit die Pfeiltasten relativ zum zuvor fokussierten Element in der Liste verschieben und nicht wie erwartet das neu ausgewählte Element.

Dies ist für den Benutzer sehr verwirrend, da es die Auswahl so aussehen lässt, als würde es bei der Verwendung der Tastatur herumspringen, wenn es zurückspringt, wo es vor der programmatischen Auswahl war.

Hinweis: Wie ich bereits sagte, geschieht dies nur, wenn Sie die Eigenschaft SelectedItem programmatisch auf eine ListBox setzen, die bereits über den Tastaturfokus verfügt. Wenn dies nicht der Fall ist (oder wenn Sie es tun, aber Sie verlassen, dann kommen Sie gleich zurück), wenn der Tastaturfokus auf ListBox zurückkehrt, hat das korrekte Element nun erwartungsgemäß den Tastaturfokus.

Hier ist ein Beispielcode, der dieses Problem zeigt. Um dies zu demonstrieren, führen Sie den Code aus, verwenden Sie die Maus, um 'Seven' in der Liste auszuwählen (also den Fokus auf ListBox zu setzen), klicken Sie dann auf die Schaltfläche 'Test'. Tippen Sie abschließend auf die Taste "Alt" auf Ihrer Tastatur, um das Fokus-Rect anzuzeigen. Sie werden sehen, dass es immer noch auf "Seven" steht und wenn Sie die Pfeile nach oben und nach unten verwenden, sind sie relativ zu dieser Reihe, nicht "Vier", wie ein Benutzer erwarten würde.

Bitte beachten Sie, dass ich Focusable auf false auf der Schaltfläche eingestellt habe, um die Listbox des Fokus beim Drücken nicht zu rauben. Wenn ich das nicht hätte, würde der ListBox Fokus verlieren, wenn Sie auf die Schaltfläche klicken und wenn der Fokus zu der List-Box zurückgegeben wurde, würde es sich also auf dem richtigen Element befinden.

XAML-Datei:

<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-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]; 
     } 

    } 

} 

Hinweis: Einige haben vorgeschlagen, IsSynchronizedWithCurrentItem zu verwenden, aber das Eigentum synchronisiert die SelectedItem der ListBox mit der Current Eigenschaft des zugehörigen Aussicht. Es hängt nicht mit Fokus zusammen, da dieses Problem noch existiert.

Unsere Arbeit-um woanders den Fokus vorübergehend eingestellt ist, dann stellen Sie das ausgewählte Element, und stellen Sie den Fokus wieder auf die ListBox dies hat jedoch den unerwünschten Effekt von uns mit unserer ViewModel sich der ListBox selbst zu machen, dann führe die Logik aus, je nachdem, ob sie den Fokus hat oder nicht. (Du würdest nicht einfach sagen "Fokus woanders, dann komm hierher zurück, wenn 'hier' nicht schon den Fokus hat, wie du es stehst von woanders.) Außerdem kann man das nicht einfach durch deklarative Bindungen handhaben. Unnötig zu sagen, das ist hässlich.

Dann noch einmal, 'hässliche' Schiffe, also gibt es das.

Antwort

47

Es sind ein paar Zeilen Code. Wenn Sie es nicht in Code-Behind wollen, bin ich sicher, dass es in einem angehängten Verhalten verpackt werden könnte.

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 gibt Null zurück, wenn noch kein Container für ihn generiert wurde, was bei einer virtualisierten Liste der Fall ist und das Objekt nicht angezeigt wird. Plus, wenn Sie versuchen, den Wert von einer Bindung zu setzen, bricht es zusammen, wie Sie keinen Zugriff auf die ListBox haben, noch sollten Sie. (Fortsetzung folgt ...) – MarqueIV

+0

Auch ein angehängtes Verhalten wirft Probleme auf, da Sie nicht möchten, dass das Steuerelement blind den Tastaturfokus auf das ListBoxItem setzt, außer a) das Steuerelement hatte bereits den Fokus (einfach zu testen) und b) die Änderung kam aus dem code-behind (nicht einfach ohne eine Unterklasse zu testen). Andernfalls könnten Sie versehentlich den Fokus von einem anderen Steuerelement stehlen (Fall a) oder den Fokus im aktuellen Modus (Fall b) im Multi-select-Modus verwirren und die Auswahl aufheben eine Zeile, die normalerweise den Tastaturfokus beibehalten sollte, aber stattdessen auf das neue SelectedItem gesetzt wird, was zu seltsamen Verhalten führt. – MarqueIV

+0

Eigentlich denke ich bei zweiten Gedanken, dass ich das Problem für Fall 'B' oben habe. Sie erstellen das angehängte Verhalten wie angegeben, aber Sie legen es nicht direkt in XAML fest. Stattdessen erstellen Sie eine Eigenschaft in Ihrem ViewModel und binden das Verhalten an dieses. Auf diese Weise müssen Sie nicht wissen (oder kümmern), wer zuhört. Bevor Sie dann das ausgewählte Element aus dem Code entfernen, aktivieren Sie das Verhalten, wählen Sie das Element aus und deaktivieren Sie das Verhalten erneut. Dies adressiert "B" oben. (Du brauchst natürlich immer noch 'A'.) Deine Antwort zu wählen, obwohl sie nicht vollständig ist, hat mich auf diesem Weg geführt. – MarqueIV

2

Vielleicht mit einem angehängten Verhalten?So etwas wie

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

und in XAML

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

Das war, wo ich ursprünglich war auf dem Weg, aber das Problem ist, dieses Element don Es gibt sie noch nicht, wenn Sie eine virtualisierte Liste haben, so dass das angehängte Verhalten noch nicht verdrahtet ist, um zu antworten. Sie müssen die ScrollIntoView-Methode immer noch zuerst ausführen. Zweitens, wenn Sie streng auf IsSelected reagieren, können die Dinge im Multi-Select-Modus ein wenig verrückt werden, aber wiederum könnte dies als erwünschtes Verhalten betrachtet werden. Die Virtualisierung (die standardmäßig aktiviert ist) ist der eigentliche Mörder. – MarqueIV

0

In XAML Sie versucht, diese und hat nicht funktioniert?

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

Und die SelectedItem Eigentum:

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

      _SelectedItem = value; 

      OnPropertyChanged("SelectedItem"); 
     } 
    } 

Jetzt in Ihrem Code können Sie tun:

SelectedItem = theItemYouWant; 

Für mich dieser Ansatz funktioniert immer.

+0

Ich rufe dich an. Hast du es versucht? Laden Sie Ihre Listbox mit sage, 100 Elemente. Klicke den 50. Gegenstand mit der Maus an. Fügen Sie dann eine Schaltfläche in Ihrem Formular hinzu, das programmgesteuert das 'SelectedItem' auf dem 10. Element festlegt. Die Auswahl ändert sich, aber a) das ausgewählte Element wird nicht in die Ansicht gerollt, weil es nicht fokussiert ist. Sie können dies sehen, indem Sie auf die Taste "Alt" tippen und das Element, auf das Sie geklickt haben, immer noch mit dem Tastaturfokus angezeigt wird. – MarqueIV

+0

Übrigens, um sicher zu sein, müssen Sie zuerst die andere Zeile physisch mit der Maus oder der Tastatur auswählen. – MarqueIV

+0

@MarquelV Ich bin wahrscheinlich verwirrt, was Sie wollen. Ich dachte, Sie möchten die ausgewählte Zeile programmatisch setzen. Die ausgewählte Zeile ist die fokussierte Zeile. Sie müssen ScrollIntoView verwenden, um dorthin zu gelangen. Und ja, mein Ansatz funktioniert nicht für Mehrfachauswahl. – Dummy01

0

zuerst) Sie müssen ausgewählte Elemente in listbox mit ListBox.Items.IndexOf (finden). Sekunde) Fügen Sie jetzt Elemente mit ListBox.SelectedItems.Add() hinzu.

Dies ist mein Code:

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

Wenn Sie ein Element in ListBox auswählen möchten Sie auf diese Weise verwenden können:
ListBox.SelectedItem = (Ihr ListBoxItem);

Wenn Sie einige Elemente in List-Box auswählen, die Sie auf diese Weise verwenden müssen:
ListBox.SelectedItems.Add (Ihr ListBoxItem);

+0

Ich bin mir nicht 100% sicher, aber ich denke du hast meine Frage vielleicht falsch verstanden. Auswahl ist nicht das Problem. Fokus ist. Ihr Code wählt die Elemente aus, aber der Fokus wird nicht festgelegt. Dies versuche ich zu beheben. – MarqueIV

+0

Entschuldigung! Also, ich denke, wenn Sie Ihre Kontrolle danach konzentrieren, muss sich Ihr Gegenstand konzentrieren. (Entschuldigung für mein schlechtes Englisch) –

+0

Aber wenn das Steuerelement bereits den Fokus hat, können Sie es nicht zurücksetzen. Du müsstest zuerst etwas anderes fokussieren. – MarqueIV

0

Sie brauchen nur ListBox.SelectedItem zu verwenden und dann ListBox.ScrollIntoView (listBox.SelectedItem)

Beispielcode verwenden:

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


     } 

    } 
Verwandte Themen