DelegateとDataSourceを使ったTableViewにインクリメンタルサーチを実装する

5 min read読了の目安(約4900字

やりたいこと

Xamarin.Macを使ったデスクトップアプリで、TableViewに絞り込み機能を実装したい。

表の用意

適当な表の例として、以下のブログ記事を参考にVNOSのメンバー(の一部)とそのTwitterのIDを一覧表示する表を用意しました。

テーブルビューを表示してみました

ソースコードは以下の通り。

using System;
using System.Collections.Generic;
using AppKit;
using Foundation;
namespace tablesearch
{
    public partial class ViewController : NSViewController
    {
        readonly MemberTableViewDataSource dataSource = new MemberTableViewDataSource();
        IReadOnlyCollection<Member> Members;
        public ViewController(IntPtr handle) : base(handle)
        {
            Members = new List<Member>
            {
                new Member("a2see","a2see"),
                new Member("うぇるち","welchi_"),
                new Member("想間ミレイ","zerovabi"),
                new Member("月見ねぎとろ","tukimi_negitoro"),
                new Member("創好リナ","TsukusuLina"),
                new Member("でんじぃ","den_oldman"),
                new Member("とりえ","trpla226"),
                new Member("なもなき","nam0naki"),
            }.AsReadOnly();
            dataSource.Members = new List<Member>(Members);
        }
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            MemberTableView.DataSource = dataSource;
            MemberTableView.Delegate = new MemberTableViewDelegate(dataSource);
        }
        public override NSObject RepresentedObject
        {
            get
            {
                return base.RepresentedObject;
            }
            set
            {
                base.RepresentedObject = value;
                // Update the view, if already loaded.
            }
        }
    }
    public class MemberTableViewDataSource : NSTableViewDataSource
    {
        public List<Member> Members { get; set; } = new List<Member>();
        public override nint GetRowCount(NSTableView tableView)
        {
            return Members.Count;
        }
    }
    public class MemberTableViewDelegate : NSTableViewDelegate
    {
        private const string CellIdentifier = "MemberCell";
        private MemberTableViewDataSource dataSource;
        public MemberTableViewDelegate(MemberTableViewDataSource dataSource)
        {
            this.dataSource = dataSource;
        }
        public override NSView GetViewForItem(NSTableView tableView,
            NSTableColumn tableColumn,
            nint row)
        {
            NSTextField view = (NSTextField)tableView.MakeView(CellIdentifier, this);
            if (view == null)
            {
                view = new NSTextField();
                view.Identifier = CellIdentifier;
                view.BackgroundColor = NSColor.Clear;
                view.Bordered = false;
                view.Editable = false;
                view.Selectable = false;
            }
            var member = dataSource.Members[(int)row];
            switch(tableColumn.Title)
            {
                case "Name":
                    view.StringValue = member.Name;
                    break;
                case "Twitter ID":
                    view.StringValue = member.TwitterId;
                    break;
            }
            return view;
        }
    }
}

名前とTwitter IDのふたつのプロパティを持つ単純なモデルであるMemberクラスを用意し、TableViewDataSource内にリストで保持し、表示時にはNSTableViewDelegateがViewを生成するようになっています。

また、ViewControllerからTableViewにアクセスできるよう、あらかじめXCode Interface BuilderにてTableViewのReferencing Outletを作ってあります。

絞り込み機能の実装

Search FieldのActionを作成

Search Fieldに入力されるたびに絞り込み処理が呼ばれるよう、ActionをViewControllerに追加します。

ViewController.hを別ウィンドウで開き、Controlキーを押下しながらInterface Builder上のSearch FieldからViewController.hのエディタ上にドラッグ&ドロップします。


OutletからActionに変更し、KeywordChangedと名づけます。

KeywordChangedアクションの実装

Visual Studioに戻り、先ほどのViewController.csに処理を実装していきます。

Actionを追加したことで、View ControllerにはすでにKeywordChangedメソッドのシグネチャが定義されています。ViewControllerにpartialなメソッドとして処理を実装します。

partial void KeywordChanged(NSObject sender)
{
    var keyword = ((NSSearchField)sender).StringValue;
    dataSource.Members = Members
        .Where(member => member.Name.Contains(keyword))
        .ToList();
    MemberTableView.ReloadData();
}

マスタになるListの中から条件に一致するものをWhereメソッドでフィルタリングして、dataSource内のListに再代入しています。

実行結果

フィールドにテキストを入力して、エンターキーで確定すると条件に合うものだけが表示されます。また、フィールド右端の×印をクリックすると絞り込みがクリアされます。

おわりに

今回、単純な部分一致で実装していますが、実際には大文字小文字の違いを無視したり、半角全角の違いを無視して検索した方が使いやすい場合が多いと思いますので、工夫してみてください。

検索キーワードのラベルとフィールドのラインが綺麗に揃っていなくて美しくないですね。上手く揃える方法を知っていたら教えてください。

環境

Mac OS X 10.15.6
Visual Studio for Mac Community 8.8.1(build 37)
Xamarin Mac 7.0.0.15
XCode Version 12.2