2016年1月10日日曜日

C#のWPFのTextBoxで「数値入力専用」を実現するサンプルです。

C#のWPFのTextBoxで「数値入力専用」を実現するサンプルです。
改変自由です。 勝手に使ってください。 ただし、試行錯誤のコメントがそのままです。
Visual Studio 2015 で実験しています。

添付ビヘイビアにすることは面倒なのでしていません。その場合は次のサイトがお勧め:
http://d.hatena.ne.jp/hilapon/20101021/1287641423
添付ビヘイビアを使い TextBox で数値以外を入力できなくする。 - (憂国のプログラマ Hatena版 改め) 周回遅れのブルース

添付ビヘイビアにする際に、列挙体をWPFのデザイン時のプロパティに一覧するには TypeConverter を継承すると出来そうでした。
で [AttachedPropertyBrowsableForType(typeof(コンバーターの名前))] を使えばよいかと。
なお、Visual Studio 2013 で EnumConverter を使うとうまくできませんでした。


using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfAppCSharp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // ----
        // 以下は、「数値入力専用」を実現するサンプル。
        // ----

        // 数字の種類の定義。
        enum NumericInputTypeDef
        {
            NonNumericType,
            IntegerType,
            NonNegativeIntegerType,
            FloatingPointType,
            NonNegativeFloatingPointType,
        }

        // 数字の種類。
        private NumericInputTypeDef mNumericInputType = NumericInputTypeDef.FloatingPointType;

        // 数値の小数点以下の精度。 0 のとき精度を考慮しない。
        private uint mPrecision = 0;

        // 事前テキスト入力時 PreviewTextInput
        // テキスト入力に伴う変更前後を知ることが出来る。 Selection して Delete のときは反応してくれない。
        // ※なお、削除のみは TextChanged で知ることが出来る。
        // "TextInput" イベントは設定できるが発生しない。 e.Handled = True で入力キャンセルできる。
        private void txtNormalEvent_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            //var textBox = sender as TextBox;
            var textBox = e.Source as TextBox;
            if (textBox == null) return;

            var insertText = e.Text;

            var beforeText = textBox.Text;

            var afterText = beforeText;
            // 削除範囲あり。
            if (textBox.SelectionLength != 0)
                afterText = afterText.Remove(textBox.SelectionStart, textBox.SelectionLength);
            // 追加文字あり。
            if (e.Text.Length != 0)
                afterText = afterText.Insert(textBox.SelectionStart, e.Text);

            Int64 parsedInt64 = 0;
            UInt64 parsedUInt64 = 0;
            Double parsedDouble = 0;

            if (afterText.Length >= 1)
            {
                switch(mNumericInputType)
                {
                    case NumericInputTypeDef.NonNumericType:
                        break;
                    case NumericInputTypeDef.IntegerType:
                        if (!Int64.TryParse(afterText, out parsedInt64))
                        {
                            // "" "-" "-1" という入力手順を許可する。
                            if (!(beforeText == "" && afterText == "-"))
                                e.Handled = true;
                        }
                        break;
                    case NumericInputTypeDef.NonNegativeIntegerType:
                        // "-0" を許してしまう。 でも続きを入力できないので実害は無い。
                        if (!UInt64.TryParse(afterText, out parsedUInt64))
                        {
                            e.Handled = true;
                        }
                        break;
                    case NumericInputTypeDef.FloatingPointType:
                        if (!Double.TryParse(afterText, out parsedDouble))
                        {
                            // "" "-" "-1" という入力手順を許可する。
                            if (!(beforeText == "" && afterText == "-"))
                                e.Handled = true;
                        }
                        break;
                    case NumericInputTypeDef.NonNegativeFloatingPointType:
                        // "-0" を許してしまう。 でも続きを入力できないので実害は無い。
                        if (!Double.TryParse(afterText, out parsedDouble))
                        {
                            e.Handled = true;
                        }
                        else
                        {
                            if (parsedDouble < 0)
                            {
                                e.Handled = true;
                            }
                        }
                        break;
                }
            }

            Console.WriteLine("beforeText: [{0}]", beforeText);
            Console.WriteLine("afterText: [{0}]", afterText);
            Console.WriteLine("SelectionText, SelectionStart, SelectionLength: [{0}], [{1}], [{2}]",
                textBox.SelectedText,
                textBox.SelectionStart,
                textBox.SelectionLength
                );
            Console.WriteLine("InsertText: [{0}]", e.Text);
        }

        // テキスト変更時 TextChanged
        // 変更前後の数と位置と種類を知ることが出来るが追加文字列を知ることが出来ない。
        // e.Handled = True では入力キャンセルできないようです。
        // ※なお、追加文字列は PreviewTextInput で知ることが出来る。
        private void txtNormalEvent_TextChanged(object sender, TextChangedEventArgs e)
        {
        }

        // キー押下時 KeyDown
        // Shiftキーなどの面倒を見る必要があるので、使わないことにする。つまり $ でも 4 と勘違いされる。

        // 入るとき GotFocus
        // 入るときにテキストを全選択する。
        private void txtNormalEvent_GotFocus(object sender, RoutedEventArgs e)
        {
            //var textBox = sender as TextBox;
            var textBox = e.Source as TextBox;
            if (textBox == null) return;

            if (textBox.Text.Length != 0)
            {
                textBox.SelectAll();
            }
        }

        // 出るとき LostFocus
        // 出るときに空欄ならば "0" にする。 精度が設定と合っていないのならば直す。
        private void txtNormalEvent_LostFocus(object sender, RoutedEventArgs e)
        {
            //var textBox = sender as TextBox;
            var textBox = e.Source as TextBox;
            if (textBox == null) return;

            var currentText = textBox.Text;

            // 全角空白も半角空白も除外する。
            System.Text.RegularExpressions.RegexOptions regopt = System.Text.RegularExpressions.RegexOptions.Singleline;
            var reg = new System.Text.RegularExpressions.Regex(@"\s+", regopt);
            var textWithoutSpace = reg.Replace(currentText, "");

            // 空白が無いときに長さがゼロならば空欄だから "0" にする。
            if (textWithoutSpace.Length == 0)
            {
                textBox.Text = "0";
            }

            // 小数点以下の精度を考慮する場合は、カットしたり足したりする。
            if (mPrecision != 0)
            {
                Double parsedDouble = 0;

                if (currentText.Length >= 1)
                {
                    switch (mNumericInputType)
                    {
                        case NumericInputTypeDef.NonNumericType:
                            break;
                        case NumericInputTypeDef.IntegerType:
                            break;
                        case NumericInputTypeDef.NonNegativeIntegerType:
                            break;
                        case NumericInputTypeDef.FloatingPointType:
                            // break しないで続ける。
                        case NumericInputTypeDef.NonNegativeFloatingPointType:
                            // "-0" を許してしまう。 でも続きを入力できないので実害は無い。
                            if (!Double.TryParse(currentText, out parsedDouble))
                            {
                                e.Handled = true;
                            }
                            else
                            {
                                var afterText = parsedDouble.ToString("F" + mPrecision + "");
                                if (currentText != afterText)
                                    textBox.Text = afterText;
                            }
                            break;
                    }
                }
            }

        }

    }
}

0 件のコメント:

コメントを投稿