How do you programmatically set focus to the SelectedItem in a WPF ListBox that already has focus?

≡放荡痞女 提交于 2019-12-28 03:46:05

问题


We want to set the SelectedItem of a ListBox programmatically and want that item to then have focus so the arrow keys work relative to that selected item. Seems simple enough.

The problem however is if the ListBox already has keyboard focus when setting SelectedItem programmatically, while it does properly update the IsSelected property on the ListBoxItem, it doesn't set keyboard focus to it, and thus, the arrow keys move relative to the previously-focused item in the list and not the newly-selected item as one would expect.

This is very confusing to the user as it makes the selection appear to jump around when using the keyboard as it snaps back to where it was before the programmatic selection took place.

Note: As I said, this only happens if you programmatically set the SelectedItem property on a ListBox that already has keyboard focus itself. If it doesn't (or if it does but you leave, then come right back), when the keyboard focus returns to the ListBox, the correct item will now have the keyboard focus as expected.

Here's some sample code showing this problem. To demo this, run the code, use the mouse to select 'Seven' in the list (thus putting the focus on the ListBox), then click the 'Test' button. Finally, tap the 'Alt' key on your keyboard to reveal the focus rect. You will see it's still actually on 'Seven' and if you use the up and down arrows, they are relative to that row, not 'Four' as a user would expect.

Note that I have Focusable set to false on the button as not to rob the listbox of focus when pressing it. If I didn't have this, the ListBox would lose focus when you click the button, and thus, when focus returned to the ListBox, it would be on the correct item.

XAML file:

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

    }

}

Note: Some have suggested to use IsSynchronizedWithCurrentItem, but that property synchronizes the SelectedItem of the ListBox with the Current property of the associated view. It isn't related to focus as this problem still exists.

Our work-around is to temporarily set the focus somewhere else, then set the selected item, then set the focus back to the ListBox but this has the undesireable effect of us having to make our ViewModel aware of the ListBox itself, then perform logic depending on whether or not it has the focus, etc. (i.e. you wouldn't want to simply say 'Focus elsewhere then come back here, if 'here' didn't have the focus already as you'd steal it from somewhere else.) Plus, you can't simply handle this through declarative bindings. Needless to say this is ugly.

Then again, 'ugly' ships, so there's that.


回答1:


It's a couple lines of code. If you didn't want it in code-behind, I sure it could be packaged in a attached behaviour.

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:


Maybe with an attached behavior? Something like

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

and in xaml

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



回答3:


In your XAML you tried this and didn't work?

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

And the SelectedItem Property:

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

            _SelectedItem = value;

            OnPropertyChanged("SelectedItem");
        }
    }

Now in your code you can do:

SelectedItem = theItemYouWant;

To me this approach works always.




回答4:


First) You must find selected items in listbox with ListBox.Items.IndexOf().
Second) Now add items with ListBox.SelectedItems.Add().

This is my code :

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

If you want to select an item in ListBox you can use this way :
ListBox.SelectedItem = (Your ListBoxItem);

If you want to select some items in ListBox you must use this way :
ListBox.SelectedItems.Add(Your ListBoxItem);




回答5:


You need only use ListBox.SelectedItem and then use ListBox.ScrollIntoView(listBox.SelectedItem)

Example code:

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


        }

    }


来源:https://stackoverflow.com/questions/10444518/how-do-you-programmatically-set-focus-to-the-selecteditem-in-a-wpf-listbox-that

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!