WPF 入力補完の効くTextBox

「WPFの標準コントロールが足りていない!」との声が良く聞かれます。以前WPF版NumericUpDownの自作を紹介しました。とは言え毎回毎回要望の度に作ってられないのでライブラリを使うことを考えましょう。

 

1.Extended WPF Toolkitを使ったNumericUpDown

WPFヘビーユーザーの方々はとっくにご存じでしょうが、Extended WPF ToolkitというWPFコントロールライブラリがあります。これを少し簡単にご紹介。WPFアプリケーションを新規に作成後、Nugetから

>PM install-package extended.wpf.toolkit

としてライブラリをインストール。あとはXAMLで使うだけ。

 MainWindow.xaml

<Window x:Class="WpfToolkitSampleWithCustomTextBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ewt="http://schemas.xceed.com/wpf/xaml/toolkit"
        Title="MainWindow" Height="116" Width="296">
    <Grid>
        <ewt:DecimalUpDown Height="30" Width="200" Value="20"/>
    </Grid>
</Window>

numericupdown_using_extendedwpftoolkit

このライブラリには他にもColorPicker,Calculator,MagniferなどWindowsFormsでは存在しなかったもの、自作しても作成が困難だったものがあり非常にありがたいライブラリです。

 

2.入力補完の効くTextBox

Extended Wpf Toolkiの中にWatermarkTextBoxというコントロールがあります。Watermarkは日本語でいうと”透かし”、文字が空の場合に「コレコレを入力ください」とグレー文字で表示され、入力しようとすると消えるアレです。これビヘイビアを使えばできるなと思ったと同時に、同じものを作ってもしょうがない。似たところで、入力補完が効くTextBoxを作成してみようと思います。入力補完も表示にしか関係しない部分なので、ビヘイビアがぴったりです。

ビヘイビアとはなんぞやというとViewの表示,ユーザーインタラクションに関わる処理のことで、こちらを参照ください。(ライブラリ使おうって言っといて早速作るんかいという声が・・

 InputComplementBehavior.cs

namespace WpfToolkitSampleWithCustomTextBox
{
    using System;
    using System.Collections;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Controls;
    using System.Windows.Interactivity;

    public class InputComplementBehavior : Behavior<TextBox>
    {
        private string currentCandidate;
        private Brush defaultBrush;

        // 補完候補文字列コレクションの依存関係プロパティ
        public static DependencyProperty InputCandidatesProperty;
        static InputComplementBehavior()
        {
            InputComplementBehavior.InputCandidatesProperty =
                DependencyProperty.Register(
                "InputCandidates",
                typeof(IEnumerable),
                typeof(InputComplementBehavior),
                new FrameworkPropertyMetadata(String.Empty));
        }
        public IEnumerable InputCandidates
        {
            get { return (IEnumerable)GetValue(InputComplementBehavior.InputCandidatesProperty); }
            set { SetValue(InputComplementBehavior.InputCandidatesProperty, value); }
        }

        // Behavior<T>としての接合部
        protected override void OnAttached()
        {
            this.AssociatedObject.Initialized += AssociatedObject_Initialized;
            this.AssociatedObject.TextChanged += AssociatedObject_TextChanged;
            this.AssociatedObject.KeyDown += AssociatedObject_KeyDown;
            base.OnAttached();
        }
        protected override void OnDetaching()
        {
            this.AssociatedObject.Initialized -= AssociatedObject_Initialized;
            this.AssociatedObject.TextChanged -= AssociatedObject_TextChanged;
            this.AssociatedObject.KeyDown -= AssociatedObject_KeyDown;
            base.OnDetaching();
        }

        // 初期化時にデフォルト背景を取得、保持しておく
        void AssociatedObject_Initialized(object sender, EventArgs e)
        {
            TextBox textBox = sender as TextBox;
            defaultBrush = textBox.Background;
        }

        // 補完処理
        void AssociatedObject_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            // 候補文字がある状態でTabキーが押されれば補完処理
            if (!string.IsNullOrEmpty(currentCandidate) && Key.Tab == e.Key)
            {
                TextBox textBox = sender as TextBox;
                textBox.Text = currentCandidate;
                textBox.CaretIndex = textBox.Text.Length;
                e.Handled = true;
            }
        }

        // 文字入力の度に候補を探し、背景描画
        void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
        {
            TextBox textBox = sender as TextBox;
            currentCandidate = GetMostProbableComplementText(textBox.Text);
            if (!String.IsNullOrEmpty(currentCandidate))
            {
                // 候補文字があるのでグレー背景で表示する
                textBox.Background = GetComplementTextBackgroundBrush();
            }
            else
            {
                // 元に戻す
                textBox.Background = defaultBrush;
            }
        }

        private string GetMostProbableComplementText(string currentText)
        {
            // 先頭からの一致を探す(カスタマイズ要素)
            if (string.IsNullOrEmpty(currentText))
            {
                return String.Empty;
            }
            foreach (var item in InputCandidates)
            {
                string text = item as String;
                if (!String.IsNullOrEmpty(text) && text.StartsWith(currentText))
                {
                    return text;
                }
            }
            return String.Empty;
        }

        private Brush GetComplementTextBackgroundBrush()
        {
            TextBox textBoxVisual = new TextBox()
            {
                Text = currentCandidate,
                BorderBrush = null,
                Foreground = new SolidColorBrush(Colors.Gray),
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Center
            };
            return new VisualBrush(textBoxVisual)
            {
                Stretch = Stretch.None,
                TileMode = TileMode.None,
                AlignmentX = AlignmentX.Left,
                AlignmentY = AlignmentY.Top
            };
        }

    }
}

補完候補となる文字はバインドして設定することが容易に想像つくので、依存関係プロパティとして作成しています。このビヘイビアの使い方はこちら↓

MainWindow.xaml

<Window x:Class="WpfToolkitSampleWithCustomTextBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:me="clr-namespace:WpfToolkitSampleWithCustomTextBox"
        Title="MainWindow" Height="100" Width="300">
    <Grid>
        <TextBox Width="200" Height="25">
            <i:Interaction.Behaviors>
                <me:InputComplementBehavior>
                    <me:InputComplementBehavior.InputCandidates>
                        <x:Array Type="sys:String">
                            <sys:String>One</sys:String>
                            <sys:String>Two</sys:String>
                            <sys:String>Three</sys:String>
                            <sys:String>Four</sys:String>
                            <sys:String>Five</sys:String>
                        </x:Array>
                    </me:InputComplementBehavior.InputCandidates>
                </me:InputComplementBehavior>
            </i:Interaction.Behaviors>
        </TextBox>
    </Grid>
</Window>

このTextBoxはもちろん通常入力が可能です。

normal_usage

しかし候補文字にある文字列の一部を入力すると背景がグレーの文字が現れ、

complement_start この状態でTabキーをおすと、、

complement_done 補完が効きます!

今回は候補文字との比較を単純な登録順の前方一致でしていますが、もっとインテリジェントにもできそうです。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中