テキスト入力中に選択候補を一覧表示して入力効率化！ [XamMultiColumnComboEditor]

# はじめに
Googleの検索候補、IMEなど、入力中に表示される予測変換の機能は便利ですよね。
今回紹介する [XamMultiColumnComboEditor](https://jp.infragistics.com/help/wpf/xammulticolumncomboeditor) は、同様の機能が実装でき、さらにオプション機能の設定により様々な表現が可能です。
例えば、後ほど紹介するサンプルアプリケーションでは、都道府県の検索を例に、漢字での検索、ひらがなでの検索、ローマ字での予測入力を同時に対応することが可能です。

# アプリケーションサンプル
都道府県の入力を例に、漢字、ひらがな、ローマ字。いずれの入力でも選択候補が表示されるサンプルをご用意しました。
漢字、ひらがな、ローマ字のいずれでもフィルタリングができ、また選択候補から選択された場合に採用されるのは漢字の都道府県となります。（フィルタの対象や、採用される項目の変更などはオプションで選択可能です。）
![Capture.gif](https://qiita-image-store.s3.amazonaws.com/0/48623/31099276-161b-dc7d-f0f9-383a6381eea7.gif)

# サンプルコード
GitHub
https://github.com/furugen/wpf-samples-keyboard-tips/tree/sample1

```xml:MainWindow.xaml
<Window  x:Class="wpf_samples_keyboard_tips.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:wpf_samples_keyboard_tips"
        xmlns:ig="http://schemas.infragistics.com/xaml"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="comboItemTemplate" >
            <TextBlock Text="{Binding Path=Name}"></TextBlock>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal" Height="30">
                <TextBlock>
                都道府県
                </TextBlock>
                <ig:XamMultiColumnComboEditor Width="300" DisplayMemberPath="Name" x:Name="xamComboEditor" ItemsSource="{Binding SampleData}" FilterMode="FilterOnAllColumns" >
                    <ig:XamMultiColumnComboEditor.Columns>
                        <ig:TextComboColumn Key="Name" HeaderText="都道府県"></ig:TextComboColumn>
                        <ig:TextComboColumn Key="Hiragana" HeaderText="都道府県(かな)"></ig:TextComboColumn>
                        <ig:TextComboColumn Key="Romaji" HeaderText="都道府県(ローマ字)"></ig:TextComboColumn>
                    </ig:XamMultiColumnComboEditor.Columns>
                </ig:XamMultiColumnComboEditor>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>
```

```csharp:SampleData.cs
    public class SampleData
    {
        public string Name { get; set; }
        public string Hiragana { get; set; }
        public string Romaji { get; set; }
    }
```

```csharp:MainWindowViewModel.cs
   public class MainWindowViewModel : INotifyPropertyChanged
    {
        public MainWindowViewModel()
        {
            #region サンプルデータを設定
            this.SampleData = new ObservableCollection<SampleData>();
            this.SampleData.Add(new SampleData() { Name = "北海道", Hiragana = "ほっかいどう", Romaji = "hokkaido" });
            this.SampleData.Add(new SampleData() { Name = "青森", Hiragana = "あおもり", Romaji = "aomori" });
            this.SampleData.Add(new SampleData() { Name = "岩手", Hiragana = "いわて", Romaji = "iwate" });
            this.SampleData.Add(new SampleData() { Name = "宮城", Hiragana = "みやぎ", Romaji = "miyagi" });
            this.SampleData.Add(new SampleData() { Name = "秋田", Hiragana = "あきた", Romaji = "akita" });
            this.SampleData.Add(new SampleData() { Name = "山形", Hiragana = "やまがた", Romaji = "yamagata" });
            this.SampleData.Add(new SampleData() { Name = "福島", Hiragana = "ふくしま", Romaji = "fukushima" });
            this.SampleData.Add(new SampleData() { Name = "茨城", Hiragana = "いばらき", Romaji = "ibaraki" });
            this.SampleData.Add(new SampleData() { Name = "栃木", Hiragana = "とちぎ", Romaji = "tochigi" });
            this.SampleData.Add(new SampleData() { Name = "群馬", Hiragana = "ぐんま", Romaji = "gunma" });
            this.SampleData.Add(new SampleData() { Name = "埼玉", Hiragana = "さいたま", Romaji = "saitama" });
            this.SampleData.Add(new SampleData() { Name = "千葉", Hiragana = "ちば", Romaji = "chiba" });
            this.SampleData.Add(new SampleData() { Name = "東京", Hiragana = "とうきょう", Romaji = "tokyo" });
            this.SampleData.Add(new SampleData() { Name = "神奈川", Hiragana = "かながわ", Romaji = "kanagawa" });
            this.SampleData.Add(new SampleData() { Name = "新潟", Hiragana = "にいがた", Romaji = "niigata" });
            this.SampleData.Add(new SampleData() { Name = "富山", Hiragana = "とやま", Romaji = "toyama" });
            this.SampleData.Add(new SampleData() { Name = "石川", Hiragana = "いしかわ", Romaji = "ishikawa" });
            this.SampleData.Add(new SampleData() { Name = "福井", Hiragana = "ふくい", Romaji = "fukui" });
            this.SampleData.Add(new SampleData() { Name = "山梨", Hiragana = "やまなし", Romaji = "yamanashi" });
            this.SampleData.Add(new SampleData() { Name = "長野", Hiragana = "ながの", Romaji = "nagano" });
            this.SampleData.Add(new SampleData() { Name = "岐阜", Hiragana = "ぎふ", Romaji = "gifu" });
            this.SampleData.Add(new SampleData() { Name = "静岡", Hiragana = "しずおか", Romaji = "shizuoka" });
            this.SampleData.Add(new SampleData() { Name = "愛知", Hiragana = "あいち", Romaji = "aichi" });
            this.SampleData.Add(new SampleData() { Name = "三重", Hiragana = "みえ", Romaji = "mie" });
            this.SampleData.Add(new SampleData() { Name = "滋賀", Hiragana = "しが", Romaji = "shiga" });
            this.SampleData.Add(new SampleData() { Name = "京都", Hiragana = "きょうと", Romaji = "kyoto" });
            this.SampleData.Add(new SampleData() { Name = "大阪", Hiragana = "おおさか", Romaji = "osaka" });
            this.SampleData.Add(new SampleData() { Name = "兵庫", Hiragana = "ひょうご", Romaji = "hyogo" });
            this.SampleData.Add(new SampleData() { Name = "奈良", Hiragana = "なら", Romaji = "nara" });
            this.SampleData.Add(new SampleData() { Name = "和歌山", Hiragana = "わやかま", Romaji = "wakayama" });
            this.SampleData.Add(new SampleData() { Name = "鳥取", Hiragana = "とっとり", Romaji = "tottori" });
            this.SampleData.Add(new SampleData() { Name = "島根", Hiragana = "しまね", Romaji = "shimane" });
            this.SampleData.Add(new SampleData() { Name = "岡山", Hiragana = "おかやま", Romaji = "okayama" });
            this.SampleData.Add(new SampleData() { Name = "広島", Hiragana = "ひろしま", Romaji = "hiroshima" });
            this.SampleData.Add(new SampleData() { Name = "山口", Hiragana = "やまぐち", Romaji = "yamaguchi" });
            this.SampleData.Add(new SampleData() { Name = "徳島", Hiragana = "とくしま", Romaji = "tokushima" });
            this.SampleData.Add(new SampleData() { Name = "香川", Hiragana = "かがわ", Romaji = "kagawa" });
            this.SampleData.Add(new SampleData() { Name = "愛媛", Hiragana = "えひめ", Romaji = "ehime" });
            this.SampleData.Add(new SampleData() { Name = "高知", Hiragana = "こうち", Romaji = "kochi" });
            this.SampleData.Add(new SampleData() { Name = "福岡", Hiragana = "ふくおか", Romaji = "fukuoka" });
            this.SampleData.Add(new SampleData() { Name = "佐賀", Hiragana = "さが", Romaji = "saga" });
            this.SampleData.Add(new SampleData() { Name = "長崎", Hiragana = "ながさき", Romaji = "nagasaki" });
            this.SampleData.Add(new SampleData() { Name = "熊本", Hiragana = "くまもと", Romaji = "kumamoto" });
            this.SampleData.Add(new SampleData() { Name = "大分", Hiragana = "おおいた", Romaji = "oita" });
            this.SampleData.Add(new SampleData() { Name = "宮崎", Hiragana = "みやざき", Romaji = "miyazaki" });
            this.SampleData.Add(new SampleData() { Name = "鹿児島", Hiragana = "かごしま", Romaji = "kagoshima" });
            this.SampleData.Add(new SampleData() { Name = "沖縄", Hiragana = "おきなわ", Romaji = "okinawa" });
            #endregion 
        }


        private ObservableCollection<SampleData> _SampleData = new ObservableCollection<SampleData>();
        public ObservableCollection<SampleData> SampleData
        {
            get { return _SampleData; }
            set { _SampleData = value; OnPropertyChanged(); }
        }

        /// <summary>
        /// プロパティ値の変更をクライアントに通知する。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// PropertyChanged イベント を発生させる。
        /// </summary>
        /// <param name="propertyName">変更されたプロパティ名</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
```

# サンプルコードの解説
まず都道府県を例にサンプルのデータを作成しています。

```csharp:SampleData.cs
    public class SampleData
    {
        public string Name { get; set; }
        public string Hiragana { get; set; }
        public string Romaji { get; set; }
    }
```

これらの要素をXamMultiColumnComboEditorで下記のように指定して表示しています。

```xml:MainWindow.xaml
<ig:XamMultiColumnComboEditor Width="300" DisplayMemberPath="Name" x:Name="xamComboEditor" ItemsSource="{Binding SampleData}" FilterMode="FilterOnAllColumns" >
    <ig:XamMultiColumnComboEditor.Columns>
        <ig:TextComboColumn Key="Name" HeaderText="都道府県"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Hiragana" HeaderText="都道府県(かな)"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Romaji" HeaderText="都道府県(ローマ字)"></ig:TextComboColumn>
    </ig:XamMultiColumnComboEditor.Columns>
</ig:XamMultiColumnComboEditor>
```

### DisplayMemberPath
テキストボックスで表示するプロパティパスを指定してます。表示される場所は下記の画像の赤枠部分。
Nameを指定、SampleData.Nameのプロパティが採用され、例えば、北海道のデータの場合は"北海道"と表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/48623/550c1f63-766c-0b37-5f0b-22a3ea79372f.png)

### ItemsSource
選択候補として表示する一覧を指定します。

### FilterMode = FilterOnAllColumns
選択候補に表示している列がフィルタリングの候補となります。今回のサンプルでは、都道府県名の漢字、ひらがな、ローマ字が検索対象ですね。
プライマリ列のみフィルタリング対象とするFilterOnPrimaryColumnOnlyの列挙値もあります。
https://jp.infragistics.com/help/wpf/xammulticee-configuring-item-filtering

### Columnsとig:TextComboColumn
Columsnにて表示したいプロパティのカラム列を記載しています。
今回は、SampleDataのName(漢字),Hiragana(ひらがな),Romaji(ローマ字)の３つの要素を表示したいので３つのカラムを定義しています。Keyにはバインディング対象のプロパティを、HeaderTextには列の先頭に表示するヘッダーの文字列を記載しています。

```xml:MainWindow.xaml
    <ig:XamMultiColumnComboEditor.Columns>
        <ig:TextComboColumn Key="Name" HeaderText="都道府県"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Hiragana" HeaderText="都道府県(かな)"></ig:TextComboColumn>
        <ig:TextComboColumn Key="Romaji" HeaderText="都道府県(ローマ字)"></ig:TextComboColumn>
    </ig:XamMultiColumnComboEditor.Columns>
```

# まとめ
如何でしたでしょうか。今回のサンプルアプリケーションのように、フィルタリングは複数候補(漢字、ひらがな、ローマ字)だけど最終的に採用する文字列は１つだけ(都道府県の漢字)などは日本のソフトウェアではよくあるシナリオですが、対応しているコントロールはなかなか少ないかと思います。この記事が参考になれば幸いでございます。

次は、今回の記事で紹介した要素や、入力機能のノウハウをふんだんに取り入れた、快適な入力画面のサンプルをご紹介したいと思います！