Поиск в выпадающем списке ComboBox¶
Для использования необходимо подключить nuget-пакет ModPlusStyle версии не ниже 16.0 (если в пакете модуля версия ниже)!
<PackageReference Include="ModPlusStyle" Version="16.0.0" ExcludeAssets="runtime" />
ComboBoxFilterAssist¶
Назначение¶
ComboBoxFilterAssist — присоединённое свойство, которое добавляет в выпадающий список ComboBox
поле поиска. Пользователь открывает список, сразу начинает печатать, и в списке остаются только
элементы, содержащие введённый текст.
Используйте его для длинных списков: семейства, типоразмеры, листы, параметры, уровни и т.п. Если в списке 5–10 элементов, поиск не нужен — он только занимает место.
Подключение¶
Пространство имён — то же, что и для остальных контролов библиотеки:
Достаточно одного свойства:
<ComboBox mps:ComboBoxFilterAssist.IsFilterEnabled="True"
ItemsSource="{Binding Levels}"
SelectedItem="{Binding SelectedLevel}"
DisplayMemberPath="Name" />
Поле поиска встроено в стандартный стиль ComboBox библиотеки и работает во всех вариантах
выпадающего списка: при открытии вниз, вверх и в классическом режиме (mps:ComboBoxAssist.ClassicMode="True").
Подсказка в пустом поле локализуется автоматически, у поля есть кнопка очистки.
Как ищется текст¶
Поиск — по вхождению подстроки без учёта регистра: запрос эт найдёт «Этаж 1» и «Отметка».
Текст, по которому сравнивается элемент, берётся в следующем порядке (используется первый найденный):
| # | Источник | Когда срабатывает |
|---|---|---|
| 1 | TextSearch.Text элемента |
Элемент — контейнер (ComboBoxItem) с заданным TextSearch.Text |
| 2 | Content элемента |
Элемент — ComboBoxItem (или другой ContentControl), объявленный в XAML |
| 3 | DisplayMemberPath |
Задан у ComboBox |
| 4 | TextSearch.TextPath |
Задан у ComboBox |
| 5 | ToString() |
Во всех остальных случаях |
DisplayMemberPath и TextSearch.TextPath поддерживают вложенные пути вида Address.City.
Элементы с ItemTemplate¶
Это самый частый случай, когда поиск «не работает». Если элементы отображаются через ItemTemplate,
фильтр не знает, какой текст видит пользователь, и сравнивает с ToString() — обычно это имя типа.
Укажите свойство для поиска через TextSearch.TextPath:
<ComboBox mps:ComboBoxFilterAssist.IsFilterEnabled="True"
TextSearch.TextPath="Name"
ItemsSource="{Binding FamilySymbols}"
SelectedItem="{Binding SelectedFamilySymbol}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FamilyName}" Opacity="0.6" />
<TextBlock Text="{Binding Name}" Margin="{mps:Margin '1 0 0 0'}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Если искать нужно по нескольким полям сразу, добавьте в модель элемента свойство со склеенным текстом
(например, SearchText => $"{FamilyName} {Name}") и укажите его в TextSearch.TextPath.
Переопределение ToString() тоже работает, но менее очевидно для того, кто будет читать код.
TextSearch.TextPathиспользуется и стандартным поиском WPF по первым буквам в закрытомComboBox, так что его стоит задавать для любого списка сItemTemplate.
Элементы, объявленные в XAML¶
Для ComboBoxItem с текстовым Content ничего дополнительно задавать не нужно:
<ComboBox mps:ComboBoxFilterAssist.IsFilterEnabled="True">
<ComboBoxItem Content="Сверху" />
<ComboBoxItem Content="Снизу" />
<ComboBoxItem Content="По центру" />
</ComboBox>
Если Content не строка (цветной маркер + текст, иконка + текст и т.п.), задайте текст для поиска явно:
<ComboBoxItem TextSearch.Text="Красный">
<StackPanel Orientation="Horizontal">
<Rectangle Width="12" Height="12" Fill="Red" />
<TextBlock Text="Красный" Margin="{mps:Margin '1 0 0 0'}" />
</StackPanel>
</ComboBoxItem>
Работа с клавиатурой¶
| Действие | Результат |
|---|---|
| Открытие списка | Фокус сразу в поле поиска — можно печатать без клика |
| Ввод символов | Список фильтруется по мере ввода. Если фокус «ушёл» на элемент списка (например, после наведения мыши), ввод всё равно попадает в поле поиска |
| Backspace | Удаляет символ в поле поиска, даже если фокус на элементе списка |
| Up / Down / Page Up / Page Down | Переход из поля поиска в список и навигация по нему |
| Enter в поле поиска | Выбирает первый элемент, подходящий под запрос, и закрывает список. Если совпадений нет — список просто закрывается без изменения выбора |
| Esc | Закрывает список без изменения выбора |
При закрытии списка поле поиска очищается, фильтр сбрасывается — при следующем открытии виден весь список.
Для редактируемого
ComboBox(IsEditable="True") перенаправление ввода в поле поиска отключено: там ввод принадлежит самомуComboBox.
Особенности поведения¶
Выбранный элемент всегда в списке¶
Текущий SelectedItem не скрывается фильтром, даже если не подходит под запрос. Это сделано
намеренно: если выбранный элемент выпадает из отфильтрованного представления, Selector сбрасывает
выбор, и TwoWay-привязка записывает null в модель представления.
Поэтому в отфильтрованном списке может оказаться «лишний» элемент — это выбранный, а не ошибка фильтра.
Размер выпадающего списка не меняется¶
Пока поиск активен, выпадающий список сохраняет размер, с которым был открыт, и не «прыгает» по мере
ввода. Без этого список, открытый вверх, мог бы оторваться от ComboBox или перескочить вниз
и закрыться.
Общее представление коллекции¶
Фильтр ставится на представление коллекции, которое использует ComboBox. Если задан ItemsSource,
это представление по умолчанию для исходной коллекции — то же самое, что получают все другие
контролы, привязанные к ней без CollectionViewSource.
Следствие: пока список открыт и в поле поиска есть текст, другие контролы, привязанные к той же коллекции, тоже видят отфильтрованные данные. После закрытия списка всё возвращается.
Обычно это незаметно, но если, например, ComboBox и DataGrid рядом привязаны к одной и той же
коллекции, дайте ComboBox отдельное представление:
<Window.Resources>
<CollectionViewSource x:Key="LevelsForComboBox" Source="{Binding Levels}" />
</Window.Resources>
<ComboBox mps:ComboBoxFilterAssist.IsFilterEnabled="True"
ItemsSource="{Binding Source={StaticResource LevelsForComboBox}}"
DisplayMemberPath="Name" />
По той же причине не стоит одновременно использовать ComboBoxFilterAssist и собственный
Filter на представлении коллекции, к которой привязан ComboBox: на время поиска фильтр
будет заменён, а после закрытия списка — сброшен.
Когда фильтрация не работает¶
Фильтр применяется только если представление коллекции поддерживает фильтрацию (CanFilter).
Для обычных коллекций (List<T>, ObservableCollection<T>, массивы) это так. Не поддерживает
фильтрацию, например, DataView (DataTable.DefaultView) — для такого источника поле поиска
отображается, но список не фильтруется. Преобразуйте данные в коллекцию объектов.
Когда использовать¶
- Длинные списки, в которых пользователю проще набрать часть названия, чем прокручивать.
- Списки, в которых названия похожи и отличаются окончанием (типоразмеры, листы, марки).
Когда не использовать:
- Короткие списки из нескольких элементов.
- Редактируемый
ComboBoxс автодополнением — у него уже есть своё поле ввода.