2009/02/08(日)指定した正規表現にマッチしなくなる操作をキャンセルするテキストボックス

はてブ数 2009/02/08 06:02 プログラミング::C#つーさ

追記: TextBox.Validatingでできそうです。車輪の再発明だった模様。

指定フォーマットを入力させたいとき、MaskedTextBoxは便利なんだけど、int型を受け取りたいとか、double型を受け取りたいとかそういう目的にはイマイチ使い勝手が悪いような気がしたので作ってみた。

たとえば、ユーザにdouble型を入れて欲しい場面があった。

ユーザがちゃんと入れてくれることを信じるわけにはいかないので、double.TryParseでエラー処理を書くのはもちろんなのだけど、その前の段階で少しでもエラーをサプレスできないかなと思った。

そして好奇心からごりごりとWin32で作ってみた。

デザイナで配置してRegExPatternプロパティに

^-?\\d*(\\.\\d*)?$

とか、書く。操作後マッチしなくなるような操作は受け付けない*1。Win32で作ったので、x64では動かないかもしれない。WindowsXPでは動いてるが、他のバージョンでは動かないかもしれない。そんなことをするくらいなら、素直にTryParseのエラー処理だけしておくべきなのだとも思うが。まぁ、好奇心だから。

最近、Win32のプラットフォーム呼び出しにあんまり抵抗がなくなってきた。やばいなぁ。

*1 : たとえばaとか入れられないし "-4.004."の2つ目の'.'も入力できなくなる、はず

そーす

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace Tsukikage.Windows.Forms
{
    /// <summary>
    /// 指定した正規表現にマッチしなくなる操作をキャンセルするテキストボックス
    /// </summary>
    public partial class VaildTextBox : TextBox
    {
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern int SendMessage(IntPtr handle, int message, int wparam, int lparam);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern int SendMessage(IntPtr handle, int message, IntPtr wparam, IntPtr lparam);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern int SendMessage(IntPtr handle, int message, int wparam, IntPtr lparam);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern int SendMessage(IntPtr handle, int message, out int wparam, out int lparam);

        string regExPattern;
        Regex expression;

        [Description("操作がキャンセルされたときに発生します。")]
        [Browsable(true)]
        public event EventHandler InputRejected;
        protected virtual void OnInputRejected(EventArgs e)
        {
            if (InputRejected != null)
                InputRejected.Invoke(this, e);
        }

        [Description("このコントロールに入力されるテキストを制限するための正規表現です。操作後のテキストがこの正規表現にマッチしない場合、操作をキャンセルします。")]
        [Browsable(true)]
        public string RegExPattern
        {
            get { return regExPattern; }
            set
            {
                regExPattern = value;
                if (regExPattern != null)
                    expression = new Regex(regExPattern);
                else
                    expression = null;
            }
        }


        string lastValue;
        protected override void WndProc(ref Message m)
        {
            int sel1, sel2;
            switch (m.Msg)
            {
                case 0x0100: // WM_KEYDOWN
                case 0x0102: // WM_CHAR
                    SendMessage(Handle, 0xB, 0, 0); // WM_SETREDRAW to false
                    SendMessage(Handle, 0x0B0, out sel1, out sel2); // EM_GETSEL
                    base.WndProc(ref m);

                    if (!TextIsVaild(GetText()))
                    {
                        Text = lastValue;
                        SendMessage(Handle, 0x0B1, sel1, sel2); // EM_SETSEL
                        OnInputRejected(EventArgs.Empty);
                    }
                    else
                        lastValue = GetText();

                    SendMessage(Handle, 0xB, 1, 0); // WM_SETERDRAW to true
                    Invalidate();
                    return;

                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        bool TextIsVaild(string s)
        {
            return expression == null || expression.IsMatch(s);
        }

        string GetText()
        {
            byte[] s = new byte[1024];
            GCHandle h = GCHandle.Alloc(s, GCHandleType.Pinned);
            SendMessage(Handle, 0xD, 512, h.AddrOfPinnedObject());
            string ret = Marshal.PtrToStringAnsi(h.AddrOfPinnedObject());
            h.Free();
            return ret;
        }

        protected override void OnTextChanged(EventArgs e)
        {
            if (TextIsVaild(GetText()) && lastValue != Text)
                base.OnTextChanged(e);
        }


        /// <summary>
        /// コントロールのテキストを取得または設定します。
        /// </summary>
        public override string Text
        {
            get
            {
                return base.Text;
            }
            set
            {
                if (!TextIsVaild(value))
                    throw new FormatException("指定された正規表現にマッチしません。");
                lastValue = base.Text = value;
            }
        }
    }
}