Нет фокусируемого ComboBoxItem - PullRequest
       2

Нет фокусируемого ComboBoxItem

0 голосов
/ 17 ноября 2018

Я работаю с ComboBox элементами, которые часто содержат очень большие объемы данных; ~ 250000 записей данных.

Это прекрасно работает, когда ComboBox настроен так:

<ComboBox ItemsSource="{Binding Items}">
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
</ComboBox>

Однако некоторые пользовательские модификации ComboBox, с которыми я работаю, требуют, чтобы элементы ComboBoxItem не были фокусируемыми. Я достиг этого, используя сеттер в ComboBox.ItemContainerStyle.

<ComboBox ItemsSource="{Binding Items}">
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
    <ComboBox.ItemContainerStyle>
        <Style TargetType="ComboBoxItem">
            <Setter
                Property="Focusable"
                Value="False" />
        </Style>
    </ComboBox.ItemContainerStyle>
</ComboBox>

Но есть проблема с этим. Он работает нормально, пока объект не был выбран. Затем, когда пользователь пытается снова открыть ComboBox, он завершает работу программы.

У меня вопрос: как настроить ComboBox, чтобы все его элементы ComboBoxItem не были сфокусированы, но это не привело к сбою программы.


GIF of Problem


Пример кода

XAML

<Window x:Class="FocusableTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Viewbox Stretch="Uniform"
                 Grid.ColumnSpan="3">
            <Label Content="Welcome"
                   FontWeight="Bold"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
        </Viewbox>

        <StackPanel Grid.Row="1"
                    Grid.Column="1">
            <ComboBox ItemsSource="{Binding Items}">
                <ComboBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel />
                    </ItemsPanelTemplate>
                </ComboBox.ItemsPanel>
                <ComboBox.ItemContainerStyle>
                    <Style TargetType="ComboBoxItem">
                        <Setter
                            Property="Focusable"
                            Value="False" />
                    </Style>
                </ComboBox.ItemContainerStyle>
            </ComboBox>
        </StackPanel>
    </Grid>
</Window>

C #

using System.Collections.ObjectModel;
using System.Security.Cryptography;
using System.Text;

namespace FocusableTest
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            for (int i = 0; i < 250000; i++)
            {
                Items.Add(GetUniqueKey());
            }
            InitializeComponent();
            DataContext = this;
        }

        public ObservableCollection<string> Items { get; } = new ObservableCollection<string>();

        private static string GetUniqueKey(int maxSize = 20)
        {
            char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
            byte[] data = new byte[1];
            using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
            {
                crypto.GetNonZeroBytes(data);
                data = new byte[maxSize];
                crypto.GetNonZeroBytes(data);
            }
            StringBuilder result = new StringBuilder(maxSize);
            foreach (byte b in data)
            {
                result.Append(chars[b % (chars.Length)]);
            }
            return result.ToString();
        }
    }
}

Ответы [ 5 ]

0 голосов
/ 27 декабря 2018

Возможно, это своего рода хак, но он работает для меня: (Тестовый проект XAML с Framework 4.7.1)

Измените свой XAML на это:

        <ComboBox ItemsSource="{Binding Items}" x:Name="MyComboBox" DropDownOpened="DropDownWasOpened">
            <ComboBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </ComboBox.ItemsPanel>
            <ComboBox.ItemContainerStyle>
                <Style TargetType="ComboBoxItem">
                    <Setter Property="Focusable" Value="False" />
                </Style>
            </ComboBox.ItemContainerStyle>
        </ComboBox>

И в коде позадидобавьте обработчик:

    private void DropDownWasOpened(object sender, EventArgs e) {
        var selectedItem = MyComboBox.SelectedItem;
        MyComboBox.SelectedItem = null;

        Dispatcher.BeginInvoke(new Action(() => MyComboBox.SelectedItem = selectedItem));
    }

Он даже имеет то преимущество, что ComboBox открывается с той же позицией прокрутки, что и при закрытии.

0 голосов
/ 27 декабря 2018

Не уверен, что это то, что вы ищете:

    <ComboBox.ItemContainerStyle>
      <Style TargetType="ComboBoxItem">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate>
              <TextBlock Text="{Binding}" Focusable="False" />
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    </ComboBox.ItemContainerStyle>
0 голосов
/ 25 декабря 2018

Во-первых, давайте уточним ... Ваша программа не CRASHING (фактическая ошибка во время выполнения), а зависает (или очевидное зависание) после выполнения заданного действия.

Фактически, это НЕпроблема со списком, но количество элементов в нем.Да, вы можете использовать некоторый фильтрующий контекст или контекст с возможностью поиска, но общее зависание - это количество элементов.

Не изменяя ничего в вашем коде, кроме цикла случайных строк, я опустил список до 50 и сохранилувеличивая удвоение + плюс, пока я не смогу получить что-то более измеримое ... 50, 100, 250, 500, 1000, 2000, 5000 и т. д.... как только я набрал примерно 1000 записей, задержка стала медленнее, но не зависла.При 5000 записей, все еще медленнее, но для раскрывающегося списка потребовалось около 32 секунд ... На этот раз на компьютере Dell Alienware i7-2.8Ghz, 16 гигабайт.После того, как второй раз открылся раскрывающийся список, последующие попытки были быстрыми.

Теперь, когда в вашем списке более 250 000 записей, это, вероятно, просто душит систему загрузкой значений для последующего отображения.

Все это, как говорится, и это не проблема с выпадающим списком, это определенно больше нужной вам реализации редизайна.Если вы можете отредактировать исходное сообщение (вместо добавления комментариев), вы сможете уточнить, почему вы хотите, чтобы в списке были все записи с 250 тыс. + Против списка с фильтром.если субфильтр, возможно, лучше было бы повторно заполнить список на основе базового фильтра.

Еще одно предостережение ... Если я сначала выберу элемент в верхней части списка, закройте выпадающий список, затемнажмите, чтобы открыть его, это займет около минуты + (10k записей), прежде чем открыть.Затем, если я выберу запись в нижней части списка, закрою ее и затем снова открою, она появится быстро.Выберите запись в верхней части, закройте ее и снова откройте, это займет много времени.

Очевидно, что переполнение списка - это не тот путь, и вам нужно рассмотреть альтернативы в вашем пользовательском интерфейсе.

В чем проблема, так это в перезагрузке комбинированного списка при последующих попытках отображения.По любым внутренним причинам, чем больше записей в списке, тем дольше время для восстановления дисплея.

0 голосов
/ 26 декабря 2018

Я не уверен, как именно выглядит ваша установка, но мне удалось получить TextBox и ComboBox с первым, сохраняющим фокус при выборе элементов из второго.Уловка была двоякой - сделать ComboBox не фокусируемым (чтобы он не крал фокус при нажатии, чтобы открыть раскрывающийся список), и обрабатывать PreviewGotKeyboardFocus на ComboBoxItem, чтобы он не фокусировался на зависании в качестве альтернативы настройке Focusable до false, что, по-видимому, является причиной вашей проблемы.Вот выдержка из кода:

<StackPanel>
    <TextBox></TextBox>
    <ComboBox ItemsSource="{Binding Items}" Focusable="False">
        <ComboBox.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel />
            </ItemsPanelTemplate>
        </ComboBox.ItemsPanel>
        <ComboBox.ItemContainerStyle>
            <Style TargetType="ComboBoxItem">
                <EventSetter Event="PreviewGotKeyboardFocus"
                             Handler="ComboBoxItem_PreviewGotKeyboardFocus" />
            </Style>
        </ComboBox.ItemContainerStyle>
    </ComboBox>
</StackPanel>

И выделенный код:

private void ComboBoxItem_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    e.Handled = true;
}
0 голосов
/ 25 декабря 2018

То, что вы пытаетесь достичь, невозможно, насколько я могу судить, копаясь в исходном коде.

Когда открывается DropDown ComboBox пытается перейти кSelectedItem

Внутренне MakeVisible вызывается для прокрутки до SelectedItem

Интересная часть здесь

GetFirstItemOnCurrentPage(container, FocusNavigationDirection.Up, out firstElement);
while (firstElement != container)

Смотрите https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ItemsControl.cs Линия 2735,2736

Этот while-loop никогда не будет оцениваться в true при установке Focusable в false, потому что GetFirstItemOnCurrentPage всегда будет установлен firstElement до null.Это приведет к прокрутке до самого конца и, следовательно, к обработке макета для целого ряда элементов, в вашем случае слишком много, поскольку это может быть обработано достаточно быстро, это зависит также от вашего текущего SelectedItem, сколько осталось элементов для прокрутки,

Причина, по которой GetFirstItemOnCurrentPage устанавливает firstElement всегда на null, заключается в том, что внутренне FindFocusable вызывается.

private object FindFocusable(int startIndex, int direction, out int foundIndex, out FrameworkElement foundContainer)
{
    // HasItems may be wrong when underlying collection does not notify, but this function
    // only cares about what's been generated and is consistent with ItemsControl state.
    if (HasItems)
    {
        int count = Items.Count;
        for (; startIndex >= 0 && startIndex < count; startIndex += direction)
        {
            FrameworkElement container = ItemContainerGenerator.ContainerFromIndex(startIndex) as FrameworkElement;

            // If the UI is non-null it must meet some minimum requirements to consider it for
            // navigation (focusable, enabled).  If it has no UI we can make no judgements about it
            // at this time, so it is navigable.
            if (container == null || Keyboard.IsFocusable(container))
            {
                foundIndex = startIndex;
                foundContainer = container;
                return Items[startIndex];
            }
        }
    }

    foundIndex = -1;
    foundContainer = null;
    return null;
}

См. https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ItemsControl.cs Строка 2592-2618

Как видите, вызывается Keyboard.IsFocusable, что в вашем случае всегда false

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...