没有可聚焦的ComboBoxItem

时间:2018-11-17 15:14:41

标签: c# wpf

我正在使用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 :(得分:5)

我不确定您的设置看起来如何,但是我设法拥有一个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;
}

答案 1 :(得分:4)

据我所研究的源代码所言,您尝试实现的目标是不可能的。

打开DropDown时,ComboBox尝试navigateSelectedItem

内部调用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设置为{{1 }}。这将导致滚动到最后,因此将处理一整堆项目,在您的情况下,这可能会足够快地进行处理,这取决于您当前的null还剩下多少项目滚动。

SelectedItem始终将GetFirstItemOnCurrentPage设置为firstElement的原因是因为内部调用了FindFocusable

null

请参见https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/ItemsControl.cs第2592-2618行

如您所见,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; } 被调用,在您的情况下始终为Keyboard.IsFocusable

答案 2 :(得分:4)

首先,请澄清一下...您的程序不是执行崩溃(实际运行时错误),而是在执行了给定的操作后挂起(或明显挂起)。

实际上,组合框不是问题,而是其中的项目数。是的,您可能正在使用某些过滤或可搜索的上下文,但是总的挂起是元素的数量。

除了更改随机字符串循环外,无需更改代码中的任何内容,我将列表降低到50,并不断增加double + plus,直到我可以进行更多的测量。.50、100、250、500、1000、2000, 5000等。

当我将计数减少到一个合理的数字时,它可以正常工作……只要将其增加到大约1000个条目,延迟就会变慢,但不会挂起。在5000个条目时,速度仍然较慢,但是打开下拉列表大约用了32秒...这一次是在Dell Alienware计算机i7-2.8Ghz,16gig上。在第二次打开下拉列表后,随后的尝试很快。

现在,有了您的250,000条以上的记录列表,它可能只是为了使系统负荷而使系统窒息,以供以后显示。

所有这些都说明了,这并不是组合框的问题,它绝对是您需要的重新设计的实现。如果您可以编辑原始帖子(仅添加评论),则可以阐明为什么希望该列表包含所有250k +记录而不是子过滤列表。如果进行了子过滤,则最好是根据基础过滤器重新填充列表。

另一个警告...如果我首先选择列表顶部附近的一个项目,请关闭组合框,然后单击以重新打开它,大约需要一分钟以上的时间(10k条记录)才能重新打开。然后,如果我在列表底部附近选择一个条目,将其关闭然后重新打开,它会很快显示。在顶部附近选择一个条目,将其关闭并重新打开,这又需要很长时间。

显然,过分填充列表是行不通的,您需要在用户界面中考虑其他选择。

问题是出在随后的显示尝试中,重新加载了组合框。无论出于何种内部原因,列表中的记录越多,重建显示的时间就越长。

答案 3 :(得分:4)

也许这是一种破解,但是对我有用:(带有Framework 4.7.1的XAML测试项目)

将您的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));
    }

它甚至具有组合框打开时具有与关闭时相同的滚动位置的优点。

答案 4 :(得分:-1)

不确定这是否是您要寻找的东西

cv::Vec3