WPF: Можно ли реализовать автозаполнение в ComboBox с ComboBoxItem?

381
08 октября 2017, 22:08

В ходе обучения разработке приложений WPF экспериментально установил, что функция автозаполнения в ComboBox работает только когда внутри - цельный TextBlock (поправьте, если это не так). Пример:

<ComboBox Name="People" IsEditable="True">
    <TextBlock>Andrew Invanov</TextBlock>
    <TextBlock>Sergey Petrov</TextBlock>
    <TextBlock>Valadimir Sidorov</TextBlock>
    <TextBlock>Nikolay Kuznetsow</TextBlock>
</ComboBox>

Теперь предположим, что мы хотим включить в список не только имя и фамилию, но и какой-нибудь ID, при этом хотим, чтобы автозаполнение работало как при вводе первых цифер ID, так и при вводе первых букв имени. Можно ли это реализовать, и если да, то как? Только программно?

ID и имя разнесены в разные TextBlock затем, чтобы к ID и имени можно было применить разные стили.

<ComboBox Name="People" Height="30" VerticalAlignment="Top" IsEditable="True">
    <ComboBoxItem>
        <StackPanel Orientation="Horizontal">
            <TextBlock Padding="0 0 10 0">3845</TextBlock>
            <TextBlock>Andrew Invanov</TextBlock>
        </StackPanel>
    </ComboBoxItem>
    <ComboBoxItem>
        <StackPanel Orientation="Horizontal">
            <TextBlock Padding="0 0 10 0">3845</TextBlock>
            <TextBlock>Sergey Petrov</TextBlock>
        </StackPanel>
    </ComboBoxItem>
    <ComboBoxItem>
        <StackPanel Orientation="Horizontal">
            <TextBlock Padding="0 0 10 0">3845</TextBlock>
            <TextBlock>Valadimir Sidorov</TextBlock>
        </StackPanel>
    </ComboBoxItem>
    <ComboBoxItem>
        <StackPanel Orientation="Horizontal">
            <TextBlock Padding="0 0 10 0">3845</TextBlock>
            <TextBlock>Nikolay Kuznetsow</TextBlock>
        </StackPanel>
    </ComboBoxItem>
</ComboBox>

Если для ответа на данный вопрос Вам нужно поэкспериментировать с кодом, то в целях экономии Вашего времени я подготовил проект для Visual Studio с начальной разметкой ComboBox.

Ссылка на Яндекс Диск (возможно станет недоступна после получения ответа на вопрос).

Answer 1

Разметка

<ComboBox Name="boxOrig" IsEditable="True" Height="30" >
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Padding="0 0 10 0" Text="{Binding id}"></TextBlock>
                <TextBlock Text="{Binding name}"></TextBlock>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Список с элементами

class Lol
{
    public int id { get; set; }
    public string name { get; set; }
    public override string ToString()
    {
        return id + " " + name;
    }
}
List<Lol> list = new List<Lol>()
{
    new Lol()
    {
        id = 1,
        name = "Andrew Invanov"
    },
    new Lol()
    {
        id = 2,
        name = "Sergey Petrov"
    },
    new Lol()
    {
        id = 3,
        name = "Valadimir Sidorov"
    },
    new Lol()
    {
        id = 4,
        name = "Nikolay Kuznetsow"
    }
};

Фильтрация

public MainWindow()
{
    InitializeComponent();
    boxOrig.ItemsSource = list;
    boxOrig.DropDownOpened+= delegate(object sender, EventArgs args)
    {
        filter(boxOrig.Text, true);
    };
    boxOrig.PreviewTextInput += (sender, args) =>
    {
        string text = boxOrig.Text + args.Text;
        filter(text);
    };
}
private void filter(string text, bool drop=false)
{
    var filtered = list
        .Where(x => x.id.ToString().StartsWith(text, true) || x.name.StartsWith(text, true))
        .ToList();
    if (filtered.Count > 0 )
    {
        boxOrig.ItemsSource = filtered;
        boxOrig.SelectedItem = filtered.First();
        if (!drop) //Обновление текста в ComboBox
        {
            boxOrig.IsEditable = false;
            boxOrig.IsEditable = true;
        }
    }
}
Answer 2

Нашел метод расширения для отображения Dropdown'a при изменении текста в ComboBox и возможности фильтрации не только по началу текста.

boxOrig.MakeComboBoxSearchable(
    searchText => (item =>
    {
        Lol lolItem = (Lol) item;
        return lolItem.id.ToString().StartsWith(searchText, true, CultureInfo.InvariantCulture) 
            || lolItem.name.StartsWith(searchText, true, CultureInfo.InvariantCulture);
    })
);

Класс с расширением

public static class Extensions
{
    public static void MakeComboBoxSearchable(this ComboBox targetComboBox,
        Func<string, Predicate<object>> itemsFilter = null)
    {
        if (itemsFilter == null)
        {
            itemsFilter = searchText => (item =>
                    item.ToString().ToLower().Contains(searchText.ToLower()));
        }
        targetComboBox.Loaded += (sender, e) => TargetComboBox_Loaded(sender, e, itemsFilter);
    }
    private static void TargetComboBox_Loaded(object sender, RoutedEventArgs e,
        Func<string, Predicate<object>> itemsFilter)
    {
        var targetComboBox = sender as ComboBox;
        var targetTextBox = targetComboBox?.Template.FindName("PART_EditableTextBox", targetComboBox) as TextBox;
        if (targetTextBox == null) return;
        targetComboBox.Tag = "TextInput";
        targetComboBox.StaysOpenOnEdit = true;
        targetComboBox.IsEditable = true;
        targetComboBox.IsTextSearchEnabled = false;
        targetTextBox.TextChanged += (o, args) =>
        {
            var textBox = (TextBox) o;
            var searchText = textBox.Text;
            if (targetComboBox.Tag.ToString() == "Selection")
            {
                targetComboBox.Tag = "TextInput";
                targetComboBox.IsDropDownOpen = true;
            }
            else
            {
                if (targetComboBox.SelectionBoxItem != null)
                {
                    targetComboBox.SelectedItem = null;
                    targetTextBox.Text = searchText;
                    textBox.CaretIndex = targetTextBox.Text.Length;
                }
                if (string.IsNullOrEmpty(searchText))
                {
                    targetComboBox.Items.Filter = item => true;
                    targetComboBox.SelectedItem = default(object);
                }
                else
                    targetComboBox.Items.Filter = itemsFilter(searchText);
                Keyboard.ClearFocus();
                Keyboard.Focus(targetTextBox);
                targetComboBox.IsDropDownOpen = true;
                targetTextBox.SelectionStart = targetTextBox.Text.Length;
            }
        };

        targetComboBox.SelectionChanged += (o, args) =>
        {
            var comboBox = o as ComboBox;
            if (comboBox?.SelectedItem == null) return;
            comboBox.Tag = "Selection";
        };
    }
}

Разметка

<ComboBox Name="boxOrig" Height="30" >
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Padding="0 0 10 0" Text="{Binding id}"></TextBlock>
                <TextBlock Text="{Binding name}"></TextBlock>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Список с элементами

class Lol
{
    public int id { get; set; }
    public string name { get; set; }
    public override string ToString()
    {
        return id + " " + name;
    }
}
List<Lol> list = new List<Lol>()
{
    new Lol() {id = 1, name = "Andrew Invanov"},
    new Lol() {id = 2, name = "Sergey Petrov"},
    new Lol() {id = 3, name = "Valadimir Sidorov"},
    new Lol() {id = 4, name = "Nikolay Kuznetsow"},
    new Lol() {id = 5, name = "Nikolay Petrov"}
};
READ ALSO
WPF&amp;MVVM: Ввод начальных данных в Model

WPF&MVVM: Ввод начальных данных в Model

Как это было отмечено в одном из комментариев к вопросу Ввод данных во ViewModel, хранение данных во ViewModel является противоречием шаблону MVVM, но во всех...

309
WPF: Как правильно с точки зрения концепции MVVM вызывать новое окно командой ?

WPF: Как правильно с точки зрения концепции MVVM вызывать новое окно командой ?

Без шаблона MVVM, вызов нового окна в приложениях WPF довольно прост:

264
Не удается подключиться к серверу со статическим IP

Не удается подключиться к серверу со статическим IP

ПриветсвтуюНаписал на c# простое приложение клиент-сервер

273
C# Как отредактировать текст, выведенный в консоль

C# Как отредактировать текст, выведенный в консоль

Помогите пожалуйстаМне нужно, чтобы пользователь мог вводить символы (на месте курсора), и введенные данные отображались в том же месте (на месте...

234