没有焦点的ComboBoxItem

Dan*_*Dan 5 c# wpf

我正在处理ComboBox通常包含大量数据的元素; ~250000个数据条目.

当a ComboBox设置有点像这样时,这很好用.

<ComboBox ItemsSource="{Binding Items}">
    <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ComboBox.ItemsPanel>
</ComboBox>
Run Code Online (Sandbox Code Playgroud)

但是,ComboBox我正在使用的一些自定义修改要求ComboBoxItem元素不可聚焦.我通过使用中的setter实现了这一点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>
Run Code Online (Sandbox Code Playgroud)

但这有一个问题.它可以正常工作,直到选择了一个对象.然后当用户ComboBox再次尝试打开时,它会使程序崩溃.

我的问题是,如何ComboBox设置所有ComboBoxItem元素都不可调焦,但它不会使程序崩溃.


问题的GIF


示例代码

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>
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Grx*_*x70 5

我不确定你的设置究竟是什么样的,但是我设法在选择后者的项目的同时拥有一个TextBoxComboBox前一个保留焦点.诀窍是双重的-使ComboBox不可作为焦点(所以点击打开下拉时不偷焦点),并处理PreviewGotKeyboardFocusComboBoxItem,以防止它获得焦点悬停作为替代设置Focusablefalse,这显然是你的问题的原因.这是代码摘录:

<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>
Run Code Online (Sandbox Code Playgroud)

代码隐藏:

private void ComboBoxItem_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    e.Handled = true;
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*kus 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>
Run Code Online (Sandbox Code Playgroud)

并在后面的代码中添加处理程序:

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

        Dispatcher.BeginInvoke(new Action(() => MyComboBox.SelectedItem = selectedItem));
    }
Run Code Online (Sandbox Code Playgroud)

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