Подтвердить что ты не робот

Как программно установить фокус на SelectedItem в WPF ListBox, который уже имеет фокус?

Мы хотим программно установить SelectedItem из ListBox и хотим, чтобы этот элемент имел фокус, поэтому клавиши со стрелками работают относительно выбранного элемента. Кажется, достаточно просто.

Однако проблема в том, что если ListBox уже имеет фокус клавиатуры при настройке SelectedItem программно, в то время как он правильно обновляет свойство IsSelected на ListBoxItem, он не устанавливает на него фокус клавиатуры и таким образом, клавиши со стрелками перемещаются относительно предварительно сфокусированного элемента в списке, а не вновь выбранного элемента, как можно было бы ожидать.

Это очень запутанно для пользователя, так как он делает выбор, по-видимому, прыгающим при использовании клавиатуры, когда он возвращается назад туда, где он был до того, как был выбран программный выбор.

Примечание. Как я уже сказал, это происходит только в том случае, если вы программно установите свойство SelectedItem на ListBox, у которого уже есть клавиатура. Если это не так (или если это произойдет, но вы уйдете, а затем вернитесь назад), когда фокус клавиатуры вернется к ListBox, правильный элемент теперь будет иметь фокус клавиатуры, как ожидалось.

Вот пример кода, показывающий эту проблему. Для демонстрации этого, запустите код, используйте мышь, чтобы выбрать "Семь" в списке (таким образом, сосредоточив внимание на ListBox), затем нажмите кнопку "Тест". Наконец, нажмите клавишу "Alt" на клавиатуре, чтобы открыть фокус rect. Вы увидите это на самом деле на "Семь", и если вы используете стрелки вверх и вниз, они относятся к этой строке, а не "четыре", как ожидал пользователь.

Обратите внимание, что у меня есть Focusable, установленный на false на кнопке, чтобы не грабить список фокуса при нажатии. Если бы у меня не было этого, ListBox потерял бы фокус, когда вы нажмете кнопку, и, таким образом, когда фокус вернется в ListBox, он будет на правильном элементе.

Файл 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>

Code-за:

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

    }

}

Примечание. Некоторые предложили использовать IsSynchronizedWithCurrentItem, но это свойство синхронизирует SelectedItem объекта ListBox с свойством Current соответствующего представления. Это не связано с фокусом, поскольку эта проблема все еще существует.

Наша рабочая задача - временно установить фокус в другом месте, затем установить выбранный элемент, а затем установить фокус на ListBox, но это имеет нежелательный эффект от того, что мы должны сделать наш ViewModel осведомленным о ListBox, затем выполните логику в зависимости от того, имеет ли она фокус и т.д. (т.е. вы не захотите просто сказать "Фокус в другом месте", а затем вернуться сюда, если "здесь" уже не было фокуса вы бы украли его откуда-то еще.) Кроме того, вы не можете просто обрабатывать это через декларативные привязки. Излишне говорить, что это уродливо.

Опять же, "уродливые" корабли, так что это.

4b9b3361

Ответ 1

Это пара строк кода. Если вы не хотите, чтобы это было в коде, я уверен, что он может быть упакован в приложенном поведении.

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

Может быть, с приложенным поведением? Что-то вроде

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

и в xaml

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

Ответ 3

В вашем XAML вы пробовали это и не работали?

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

И свойство SelectedItem:

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

            _SelectedItem = value;

            OnPropertyChanged("SelectedItem");
        }
    }

Теперь в вашем коде вы можете:

SelectedItem = theItemYouWant;

Для меня этот подход работает всегда.

Ответ 4

Сначала) Вы должны найти выбранные элементы в списке со списком ListBox.Items.IndexOf().
Второй) Теперь добавьте элементы со списком ListBox.SelectedItems.Add().

Это мой код:

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

Если вы хотите выбрать элемент в ListBox, вы можете использовать этот способ:
ListBox.SelectedItem = (ваш ListBoxItem);

Если вы хотите выбрать некоторые элементы в ListBox, вы должны использовать этот способ:
ListBox.SelectedItems.Add(ваш ListBoxItem);

Ответ 5

Вам нужно использовать ListBox.SelectedItem, а затем использовать ListBox.ScrollIntoView(listBox.SelectedItem)

Пример кода:

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


        }

    }