WPF AutoCompleteBox dynamic sorting

2019-08-03 18:23发布

I am using the WPF AutoCompleteBox and I have it working great, but one thing I would like to do is sort the suggestion list on the fly after each letter is entered into the primary TextBox. Does anyone know how to do this? I tried using an ICollectionView property with the DefaultView logic and adding SortDescriptions but it doesn't seem to phase the suggestion list. To make sure my collection view sorting was working I put a normal ListBox control and an AutoCompleteBox control on the same window and bound both controls to the same observable collection with the same collection view and the normal ListBox control showed the items sorted correctly using the SortDescriptions, but the AutoCompleteBox list didn't have the items sorted. It had them in the order they were added to the collection.

Thoughts? Suggestions? Has anyone done this?

1条回答
老娘就宠你
2楼-- · 2019-08-03 19:17

I have no idea how @user1089031 done this, but here is working sample for anyone who could be interested in (updated to @adabyron's comment!):

ViewModel.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

namespace WpfApplication12
{
    public class Item
    {
        public string Name { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }

    public class ViewModel: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = delegate {};

        private readonly ObservableCollection<Item> source;
        private readonly ICollectionView items;
        private string searchText;

        public ViewModel()
        {
            source = new ObservableCollection<Item>
                         {
                             new Item {Name = "111111111 Test abb - (1)"},
                             new Item {Name = "22222 Test - (2)"},
                             new Item {Name = "333 Test - (3)"},
                             new Item {Name = "44444 Test abc - (4)"},
                             new Item {Name = "555555 Test cde - (5)"},
                             new Item {Name = "66 Test - bbcd (6)"},
                             new Item {Name = "7 Test - cd (7)"},
                             new Item {Name = "Test - ab (8)"},
                         };

            items = new ListCollectionView(source);
        }

        public ICollectionView Items
        {
            get { return items; }
        }

        public IEnumerable<Item> ItemsSorted
        {
            get 
            {
                return string.IsNullOrEmpty(SearchText)
                        ? source
                        : (IEnumerable<Item>)source
                            .OrderBy(item => item.Name.IndexOf(SearchText,
                                StringComparison.InvariantCultureIgnoreCase));
            }
        }

        public Item Selected { get; set; }

        public string SearchText
        {
            get { return searchText; }
            set
            {
                searchText = value;
                PropertyChanged(this,
                            new PropertyChangedEventArgs("SearchText"));
                PropertyChanged(this,
                            new PropertyChangedEventArgs("ItemsSorted"));
            }
        }
    }
}

MainWindow.xaml:

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
        xmlns:wpfApplication2="clr-namespace:WpfApplication12"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="MainWindow" Height="200" Width="500"
        DataContext="{DynamicResource viewModel}">

    <Window.Resources>

        <wpfApplication2:ViewModel x:Key="viewModel" />

        <DataTemplate DataType="{x:Type wpfApplication2:Item}">
            <TextBlock Text="{Binding Name}" FontFamily="Courier New" />
        </DataTemplate>

    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <controls:AutoCompleteBox
            ItemsSource="{Binding ItemsSorted}"
            FilterMode="ContainsOrdinal"
            SelectedItem="{Binding Selected, Mode=TwoWay}"
            MinimumPrefixLength="0" 
            VerticalAlignment="Top" Margin="5">

            <i:Interaction.Behaviors>
                <wpfApplication2:SearchTextBindBehavior
                            BoundSearchText="{Binding SearchText,
                                                      Mode=OneWayToSource}" />
            </i:Interaction.Behaviors>

        </controls:AutoCompleteBox>

        <ListBox Grid.Column="1"
                 ItemsSource="{Binding Items}" Margin="5" />

    </Grid>
</Window>

As you could notice I've add one custom behavior to AutoCompleteBox control:

<i:Interaction.Behaviors>
    <wpfApplication2:SearchTextBindBehavior
            BoundSearchText="{Binding SearchText,
                                      Mode=OneWayToSource}" />
</i:Interaction.Behaviors>

This is because AutoCompleteBox's own SearchText property is read-only. So here is the code of this behavior:

SearchTextBindBehavior.cs (Updated)

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace WpfApplication12
{
    public class SearchTextBindBehavior : Behavior<AutoCompleteBox>
    {
        public static readonly DependencyProperty BoundSearchTextProperty =
                            DependencyProperty.Register("BoundSearchText",
                            typeof(string), typeof(SearchTextBindBehavior));

        public string BoundSearchText
        {
            get { return (string)GetValue(BoundSearchTextProperty); }
            set { SetValue(BoundSearchTextProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.TextChanged += OnTextChanged;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.TextChanged -= OnTextChanged;
        }

        private void OnTextChanged(object sender, RoutedEventArgs args)
        {
            if(AssociatedObject.Text.Length == 0)
            {
                BoundSearchText = string.Empty;
                return;
            }

            if(AssociatedObject.SearchText ==
                AssociatedObject.Text.Substring(0,
                                           AssociatedObject.Text.Length - 1))
            {
                BoundSearchText = AssociatedObject.Text;
            }
        }
    }
}

Note: To make it all work you will need to add reference to the System.Windows.Interactivity.dll from the Expression Blend 4 SDK. This is just where Behavior<T> and a few its friends live.

If you have Expression Blend already installed, you already have all the SDK and there is no need to download anything. Just in case - on my machine the assembly located here:

C:\Program Files\Microsoft SDKs\Expression\Blend.NETFramework\v4.0\Libraries\System.Windows.Interactivity.dll

And, finally, if you have some good reason to do NOT add reference to this popular official library, feel free to re-implemented this custom behavior in "the old way" via plain old attached properties.

Hope that helps.

查看更多
登录 后发表回答