Windows 控件限制用户的基本法门(.NET 篇)

/******************************************************************
         Windows 控件限制用户的基本法门(.NET 篇)
                C#.NET 的在下面 
-------------------------------------------------------------------

     本代码演示 控制用户的输入的基本方式(屏蔽非数字字符输入)

     .net 下限制用户输入,看见很多人是在 键盘,或 textBox 的 TextChanged 事件里做

     个人认为那样是不正确的,

     1.不能限制用户的粘贴
     2.严重干扰数据绑定等操作
     3.有时还需要备份原始数据进行还原   

     其实正确的限制输入的时机是在,windows 消息 WM_CHAR 触发时
     但.net 恰恰没有提供这个消息的事件映射.怎么办?     

     提供方案两列:    

     1)继承TextBox 重写 WndProc 函数 (优点点oo编程的优点我不说了)

        处理

            if (m.Msg==WM_CHAR){
                // 然后取 m.WParam 进行判断 m.WParam 就是用户输入的字符的 int 表示方式
                // 如果是被限制的字符 直接 Return
                // 不走 base.WndProc (ref m);
            }
            if(m.Msg==WM_PASTE)
            {
                //判断剪贴板的数据是否是符合要求如果符合不做任何处理
                //否则 Return 不走默然处理即可
            }

            base.WndProc (ref m);

     2)利用API SetWindowLong 替换默认的处理消息的函数进行处理

       本文写的就是这种 ,演示如何声明API 而且本方法很多语言都可以使用,
       但如果程序中有多个需要限制输入的控件而且相做通用类库的话
       使用建议使用方案一

废话不多说了看代码吧.
*******************************************************************/

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

namespace SETWNDPROC
{
    /// <summary>
    /// Form1 的摘要说明。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        //声明一个委托
        public delegate IntPtr NewWndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        //API 具体帮助请察看 MSDN 或到 MS 网站上去找
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, NewWndProc wndproc);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

        //没用到
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr CallWindowProc(IntPtr wndProc, IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        //SetWindowLong 用的常数,不知道什么意识的去看 msdn吧
        public const int GWL_WNDPROC = -4;

        //右键菜单消息
        public const int WM_CONTEXTMENU = 0x007b;

        //粘贴消息
        public const int WM_PASTE = 0x0302;

        //输入字符消息(键盘输入的,输入法输入的好像不是这个消息)
        public const int WM_CHAR = 0x0102;

        //一定要声明为实列变量否则,局部变量发送给API后很容易被_u71 ?C 回收,
        //会出现根本无法捕获的异常
        private NewWndProc wpr = null;

        //备份的默然处理函数
        private IntPtr oldWndProc = IntPtr.Zero;
        private System.Windows.Forms.TextBox textBox1;

        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows 窗体设计器支持所必需的
            //
            InitializeComponent();
            //
            // TODO: 在 InitializeComponent_u-29693 ?用后添加任何构造函数代码
            //
        }

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要使用代码编辑器修改
        /// 此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(32, 16);
            this.textBox1.Name = "textBox1";
            this.textBox1.TabIndex = 0;
            this.textBox1.Text = "555";
            this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
            this.ClientSize = new System.Drawing.Size(152, 53);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.Closed += new System.EventHandler(this.Form1_Closed);
            this.ResumeLayout(false);
        }

        #endregion

        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        private IntPtr TextBoxWndProc(IntPtr_u104? Wnd, int msg, IntPtr wParam, IntPtr lParam)
        {
            IntPtr returnVar = IntPtr.Zero;
            switch (msg)
            {
                //粘贴消息包括 Ctrl+V Or 右键菜单粘贴
                case WM_PASTE:
                    //取剪贴板对象
                    IDataObject iData = Clipboard.GetDataObject();
                    //判断是否是Text
                    if (iData.GetDataPresent(DataFormats.Text))
                    {
                        //取数据
                        string str;
                        str = (String)iData.GetData(DataFormats.Text);
                        /*
                          如果需要正负号,先要判断TextBox 上光标的位置
                          如果光标在最前面可以用这个,^(((\+|-)\d)?\d*)$ 
                          下面的 WM_CHAR 也要做相应变化
                        */
                        //如果是数字(可以粘贴跳出)
                        if (Regex.IsMatch(str, @"^(\d{1,})$")) break;
                    }
                    //不可以粘贴
                    return (IntPtr)0;
                case WM_CHAR:
                    int keyChar = wParam.ToInt32();
                    Debug.WriteLine(keyChar);
                    bool charOk = (keyChar > 47 && keyChar < 58) ||   //数字
                         keyChar == 8 ||                                 //退格
                         keyChar == 3 || keyChar == 22 || keyChar == 24;//拷贝,粘贴,剪切
                    //如果不是需要的的字符 wParam 改为字符 0
                    //return (IntPtr)0; 也行不过没有禁止输入的 键盘音
                    if (!charOk) wParam = (IntPtr)0;
                    break;
                    //禁止右键菜单(如果需要的话)
                    //case WM_CONTEXTMENU:
                    //return (IntPtr)0;
            }
            
            //回调备份的默认处理的函数
            returnVar = CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
            return returnVar;
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            this.Show();
            //备份默认处理函数
            //oldWndProc=GetWindowLong(textBox1.Handle,GWL_WNDPROC);
            //实列化委托(这里就是回调函数)
            wpr = new NewWndProc(this.TextBoxWndProc);
            //替换控件的默认处理函数(并且返回原始的 默认处理函数,是一个函数指针的地质)
            oldWndProc = SetWindowLong(textBox1.Handle, GWL_WNDPROC, wpr);
        }

        private void Form1_Closed(object sender, System.EventArgs e)
        {
            //还原默认处理函数
            if (!oldWndProc.Equals(IntPtr.Zero))
                SetWindowLong(textBox1.Handle, GWL_WNDPROC, oldWndProc);
        }
    }
}

不错,引自 FlashElf 的文章
http://blog.csdn.net/flashelf/archive/2004/10/31/161024.aspx

Contributors: FHL