入力テキストに基づいてコンボボックスアイテムをフィルタリングするにはどうすればよいですか?

Nov 21 2020

ビューモデルからアイテムが提供されるコンボボックスを表示したいと思います。コンボボックスは編集可能である必要があります。ユーザーが現在入力しているテキストに基づいて、コンボボックスの項目をフィルタリングする必要があります。

私は、このトピックに関するさまざまなリソース(その質問、その質問、その記事、その質問、そのブログ投稿、そのチュートリアルなど)で指摘されている次の解決策を適用しようとしています。

  • 私のビューモデルは、アイテムの周りのコレクションビューを提供します。
  • TextコンボボックスのCustomTextプロパティをビューモデルのプロパティに双方向でバインドしました。
  • Filterコレクションビューの述語は、表示名にが含まれているかどうかに基づいてアイテムをチェックするように設定されていCustomTextます。
  • ときにCustomText変更され、Refreshアイテムのコレクションビューのメソッドが呼び出されます。

これにより、テキストを変更するたびに、コンボボックスのドロップダウンリストの項目のリストが更新されると思います。残念ながら、リストは同じままです。

Filter述語にブレークポイントを設定すると、ブレークポイントがヒットしますが、どういうわけか、必ずしも各アイテムに対してではありません。


最小限の例を次に示します。

ウィンドウのXaml:

<Window x:Class="ComboBoxFilterTest.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:ComboBoxFilterTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ComboBox
            VerticalAlignment="Center"
            ItemsSource="{Binding Items}"
            DisplayMemberPath="Name"
            IsEditable="True"
            Text="{Binding CustomText}"
            IsTextSearchEnabled="False"/>
    </Grid>
</Window>

ウィンドウのコードビハインド:

using System.Windows;

namespace ComboBoxFilterTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new MainViewModel();
        }
    }
}

そして、ビューモデル(ここでは、Item通常は他の場所に存在するデータクラスを使用):

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

namespace ComboBoxFilterTest
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private sealed class Item
        {
            public int Id { get; set; }

            public string Name { get; set; }
        }

        public MainViewModel()
        {
            Items = new CollectionView(items)
            {
                Filter = item =>
                {
                    if (string.IsNullOrEmpty(customText))
                    {
                        return true;
                    }

                    if (item is Item typedItem)
                    {
                        return typedItem.Name.ToLowerInvariant().Contains(customText.ToLowerInvariant());
                    }
                    return false;
                }
            };
        }

        private readonly ObservableCollection<Item> items = new ObservableCollection<Item>
        {
            new Item{ Id = 1, Name = "ABC" },
            new Item{ Id = 2, Name = "ABCD" },
            new Item{ Id = 3, Name = "XYZ" }
        };

        public ICollectionView Items { get; }

        private string customText = "";

        public event PropertyChangedEventHandler PropertyChanged;

        public string CustomText
        {
            get => customText;
            set
            {
                if (customText != value)
                {
                    customText = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomText)));

                    Items.Refresh();
                }
            }
        }
    }
}

基本的には別の質問で説明されているのと同じことをしていると思いますが、私の場合はうまくいかないので、どうやらまだ何かが違うようです。

わずかな違いの1つはデフォルトのビューをCollectionViewSource.GetDefaultView取得するのではなく、同じコレクションで複数の異なるフィルター処理されたビューを使用したいため、を使用していないことです。


回避策として、もちろん、適切にフィルタリングされたアイテムの列挙可能オブジェクトを自分で返し、フィルターが変更されるたびに、そのような列挙可能プロパティのプロパティ変更イベントを発生させることができることに注意してください。ただし、コレクションビューに依存することが適切なWPFの方法であることを理解しているので、「正しく」実行したいと思います。

回答

BionicCode Nov 22 2020 at 13:30

発生しているような問題を回避するために推奨されるパターンはCollectionViewSource、バインディングソースとして使用することです。

ドキュメントにも記載されているように、インスタンスをCollectionView手動で作成しないでください。ソースコレクションの実際のタイプに応じて、特殊なサブタイプを使用する必要があります。

CollectionViewコードでこのクラス[ ]のオブジェクトを作成しないでください。IEnumerableのみを実装するコレクションのコレクションビューを作成するには、CollectionViewSourceオブジェクトを作成し、コレクションをSourceプロパティに追加して、Viewプロパティからコレクションビューを取得します。 。」Microsoft Docs:CollectionView

CollectionViewSource内部で型チェックをICollectionView行い、実際のソースコレクションに適した適切に初期化された実装を作成します。CollectionViewSource、XAMLまたはC#で作成されているかどうかにかかわらICollectionViewず、デフォルトのビューでは不十分な場合は、のインスタンスを取得するための推奨される方法です。

public ICollectionView Items { get; }
public CollectionViewSource ItemsViewSource { get; }

public ctor()
{      
  ObservableCollection<object> items = CreateObservableItems();
  this.ItemsViewSource = new CollectionViewSource() {Source = items};
  this.Items = this.ItemsViewSource.View;
}
F-H Nov 22 2020 at 11:55

私は解決策を見つけたと思います:関連トピックの回答で示唆されたように、私はのListCollectionView代わりに使用しましたCollectionView

何らかの理由で、は動作しListCollectionViewませんがCollectionView、は動作しますが、後者は動作してはならないことを示していません(たとえば、CollectionView.CanFilterreturns true)。

私は今のところこの答えを受け入れるつもりですが、誰かが実際にこの振る舞いを説明する答えを提供できれば、代わりにそのような答えを受け入れて喜んでいます。