пользовательский поиск для выпадающего списка - PullRequest
2 голосов
/ 05 июня 2019

Я создаю приложение WPF, содержащее ComboBox, который показывает некоторые данные. Я хочу использовать комбинированный текстовый поиск. Но проблема в том, что если пользователь ищет «llo», в списке должны отображаться все элементы, содержащие этот текстовый фрагмент, например «Hallo», «Hello», «Rollo» ... Но поиск не возвращает результата, поскольку свойство название элемента не начинается с "llo". У кого-нибудь есть идеи, как этого добиться?

Я использую MVVM-шаблон. Представление привязано к коллекции DTO (свойство viewmodel), в DTO есть два свойства, которые имеют отношение к поиску.

        <ComboBox 
            ItemsSource="{Binding Path=Agencies}"
            SelectedItem="{Binding Path=SelectedAgency}"
            IsTextSearchEnabled="True"
            DisplayMemberPath="ComboText"
            IsEnabled="{Binding IsReady}"
            IsEditable="True"
            Grid.Column="0"
            Grid.Row="0" 
            IsTextSearchCaseSensitive="False"
            HorizontalAlignment="Stretch">
        </ComboBox>

    public class Agency
    {
        public int AgencyNumber { get; set; }
        public string Title { get; set; }
        public string Name { get; set; }
        public string ContactPerson { get; set; }
        public string ComboText => $"{this.AgencyNumber}\t{this.Name}";
    }

Ответы [ 3 ]

2 голосов
/ 06 июня 2019

имбирь ниндзя |Келли |Diederik Krols определенно предлагает отличное решение «все в одном», но для простых случаев использования оно может оказаться сложным.Например, производная ComboBox получает ссылку на внутреннее редактируемое текстовое поле.Как указывает Дидерик, «Нам нужно это, чтобы получить доступ к выбору». .Что не может быть требованием вообще.Вместо этого мы могли бы просто привязать свойство Text.

<ComboBox 
    ItemsSource="{Binding Agencies}"
    SelectedItem="{Binding SelectedAgency}"
    Text="{Binding SearchText}"
    IsTextSearchEnabled="False" 
    DisplayMemberPath="ComboText"
    IsEditable="True" 
    StaysOpenOnEdit="True"
    MinWidth="200" />

Еще одно возможное улучшение - это выставить фильтр, чтобы разработчики могли легко его изменить.Оказывается, все это можно сделать из модели представления.Чтобы было интересно, я решил использовать свойство Agency ComboText для DisplayMemberPath, но его свойство Name для пользовательского фильтра.Конечно, вы можете настроить это так, как вам нравится.

public class MainViewModel : ViewModelBase
{
    private readonly ObservableCollection<Agency> _agencies;

    public MainViewModel()
    {
        _agencies = GetAgencies();
        Agencies = (CollectionView)new CollectionViewSource { Source = _agencies }.View;
        Agencies.Filter = DropDownFilter;
    }

    #region ComboBox

    public CollectionView Agencies { get; } 

    private Agency selectedAgency;
    public Agency SelectedAgency
    {
        get { return selectedAgency; }
        set
        {
            if (value != null)
            {
                selectedAgency = value;
                OnPropertyChanged();
                SearchText = selectedAgency.ComboText;
            }
        }
    }

    private string searchText;
    public string SearchText
    {
        get { return searchText; }
        set
        {
            if (value != null)
            {
                searchText = value;
                OnPropertyChanged();
                if(searchText != SelectedAgency.ComboText) Agencies.Refresh();
            }
        }
    }

    private bool DropDownFilter(object item)
    {
        var agency = item as Agency;
        if (agency == null) return false;

        // No filter
        if (string.IsNullOrEmpty(SearchText)) return true;
        // Filtered prop here is Name != DisplayMemberPath ComboText
        return agency.Name.ToLower().Contains(SearchText.ToLower());
    }

    #endregion ComboBox

    private static ObservableCollection<Agency> GetAgencies()
    {
        var agencies = new ObservableCollection<Agency>
        {
            new Agency { AgencyNumber = 1, Name = "Foo", Title = "A" },
            new Agency { AgencyNumber = 2, Name = "Bar", Title = "C" },
            new Agency { AgencyNumber = 3, Name = "Elo", Title = "B" },
            new Agency { AgencyNumber = 4, Name = "Baz", Title = "D" },
            new Agency { AgencyNumber = 5, Name = "Hello", Title = "E" },
        };
        return agencies;
    }
}

Основные ошибки:

  • Когда пользователь вводит запрос, а затем выбирает элемент из отфильтрованного списка,мы хотим, чтобы SearchText соответственно обновлялся.
  • Когда это происходит, мы не хотим обновлять фильтр.Для этой демонстрации мы используем другое свойство для DisplayMemberPath и наш пользовательский фильтр.Поэтому, если бы мы позволили фильтру обновиться, отфильтрованный список был бы пустым (совпадений не найдено), а выбранный элемент также был бы очищен.

В заключительной ноте, если указать ComboBox ItemTemplate, вам нужно будет установить TextSearch.TextPath вместо DisplayMemberPath.

1 голос
/ 05 июня 2019

Если вы ссылаетесь на этот ответ

Это должно направить вас в правильном направлении.Он работал так, как я считаю, вам нужно, когда я проверял это.Для полноты я добавлю код:

public class FilteredComboBox : ComboBox
{
    private string oldFilter = string.Empty;

    private string currentFilter = string.Empty;

    protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;


    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        if (newValue != null)
        {
            var view = CollectionViewSource.GetDefaultView(newValue);
            view.Filter += FilterItem;
        }

        if (oldValue != null)
        {
            var view = CollectionViewSource.GetDefaultView(oldValue);
            if (view != null) view.Filter -= FilterItem;
        }

        base.OnItemsSourceChanged(oldValue, newValue);
    }

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        switch (e.Key)
        {
            case Key.Tab:
            case Key.Enter:
                IsDropDownOpen = false;
                break;
            case Key.Escape:
                IsDropDownOpen = false;
                SelectedIndex = -1;
                Text = currentFilter;
                break;
            default:
                if (e.Key == Key.Down) IsDropDownOpen = true;

                base.OnPreviewKeyDown(e);
                break;
        }

        // Cache text
        oldFilter = Text;
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        switch (e.Key)
        {
            case Key.Up:
            case Key.Down:
                break;
            case Key.Tab:
            case Key.Enter:

                ClearFilter();
                break;
            default:
                if (Text != oldFilter)
                {
                    RefreshFilter();
                    IsDropDownOpen = true;

                }

                base.OnKeyUp(e);
                currentFilter = Text;
                break;
        }
    }

    protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
    {
        ClearFilter();
        var temp = SelectedIndex;
        SelectedIndex = -1;
        Text = string.Empty;
        SelectedIndex = temp;
        base.OnPreviewLostKeyboardFocus(e);
    }

    private void RefreshFilter()
    {
        if (ItemsSource == null) return;

        var view = CollectionViewSource.GetDefaultView(ItemsSource);
        view.Refresh();
    }

    private void ClearFilter()
    {
        currentFilter = string.Empty;
        RefreshFilter();
    }

    private bool FilterItem(object value)
    {
        if (value == null) return false;
        if (Text.Length == 0) return true;

        return value.ToString().ToLower().Contains(Text.ToLower());
    }
}

XAML, который я использовал для проверки:

<Window x:Class="CustomComboBox.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"
    xmlns:local="clr-namespace:CustomComboBox"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
    <local:MainWindowVM/>
</Window.DataContext>
<Grid>
    <local:FilteredComboBox IsEditable="True" x:Name="MyThing" HorizontalAlignment="Center" VerticalAlignment="Center" 
                            Height="25" Width="200" 
                            ItemsSource="{Binding MyThings}"
                            IsTextSearchEnabled="True"
                            IsEnabled="True"
                            StaysOpenOnEdit="True">

    </local:FilteredComboBox>
</Grid>

Моя ViewModel:

public class MainWindowVM : INotifyPropertyChanged
{

    private ObservableCollection<string> _myThings;
    public ObservableCollection<string> MyThings { get { return _myThings;} set { _myThings = value; RaisePropertyChanged(); } }

    public MainWindowVM()
    {
        MyThings = new ObservableCollection<string>();
        MyThings.Add("Hallo");
        MyThings.Add("Jello");
        MyThings.Add("Rollo");
        MyThings.Add("Hella");
    }



    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Если он не соответствует вашим точным потребностям, я уверен, что вы можете отредактировать его.Надеюсь это поможет.

0 голосов
/ 05 июня 2019

Используйте метод .Contains.

Этот метод вернет true, если строка содержит строку, которую вы передаете в качестве параметра.В противном случае он вернет false.

if(agency.Title.Contains(combobox.Text))
{
     //add this object to the List/Array that contains the object which will be shown in the combobox
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...