自定义搜索组合框

时间:2019-06-05 13:50:42

标签: c# wpf mvvm combobox

我正在创建一个WPF应用程序,其中包含显示一些数据的ComboBox。我想使用combobox集成的文本搜索。但是问题是,如果用户搜索“ llo”,则列表应显示包含此文本片段的所有项目,例如“ Hallo”,“ Hello”,“ Rollo”...。但是搜索不会返回任何结果,因为该属性无项目的名称以“ llo”开头。有人知道如何实现这一目标吗?

我正在使用MVVM模式。视图绑定到DTO的集合(视图模型的属性),在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 个答案:

答案 0 :(得分:2)

姜忍者|凯利| Diederik Krols肯定提供了一种不错的多合一解决方案,但是对于简单的用例而言,它可能在繁重的方面有点不足。例如,派生的ComboBox获得对内部可编辑文本框的引用。正如Diederik指出的,“ “我们需要使用它才能访问选区。” 。可能根本不是必需的。相反,我们可以简单地绑定到Text属性。

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

另一个可能的改进是公开过滤器,因此开发人员可以轻松地对其进行更改。事实证明,这一切都可以通过viewmodel完成。为了使事情有趣,我选择对Agency使用ComboText的{​​{1}}属性,但对自定义过滤器使用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的{​​{1}},则需要设置ComboBox而不是ItemTemplate

答案 1 :(得分:1)

如果您引用this answer

这应该使您的方向正确。它以我相信您在测试时需要的方式运行。为了完整性,请添加代码:

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));
    }
}

如果它不能满足您的确切需求,请确保您可以对其进行编辑。希望这可以帮助。

答案 2 :(得分:0)

使用.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
}