WinForm自定义控件–TextBox扩展
一、简单回顾
在前两节中,对Panel和GroupBox控件进行了相关的扩展应用,主要都是设置控件的边框以及边框颜色等。本节,继续对WinForm现有的控件TextBox进行扩展,来满足实际开发中的需要。
二、TextBox扩展
WinForm现有的TextBox控件功能已然相当强大了,但有些时候仍然不能满足一些实际开发的需要,本节针对开发中的需求问题,对控件进行了以下扩展:
1、 和以往一样,设置控件的边框问题,主要有:边框颜色、边框粗细;
2、 控件添加水印文字效果,可以设置水印文字颜色以及字体等;
3、 实现控件对常规输入项的验证效果,比如:数字、电话号码、电子邮件等;对不符合规则的输入进行错误提示。
(I)、边框扩展
为自定义控件添加边框颜色和边框粗细这两个属性;然后WinProc方法中重绘边框,是重新绘制一个矩形遮盖原有的线条,这里采用的方法和前两节基本相似。
(II)、水印提示
为控件添加水印效果的提示,方便于用户的输入提示,水印文字实际上就是通过TextRenderer的DrawText 方法将提示文字绘制到TextBox文本框中的。
(III)、输入验证
当用户在即时输入的过程中,就对输入内容进行规则验证,判断输入是否正确,若不正确,则提示输入出错。这里主要采用了正则表达式来进行验证,在用户输入过程中,触发OnTextChanged函数,然后验证输入的文本。
本节中主要可以进行如下验证:
默认、数字、汉字、邮政编码、电子邮件、座机电话号码、中国电话号码、手机号码、整数、负整数、浮点数、非负浮点数、正浮点数、非正浮点数、负浮点数、英文字符、大写英文字符、小写英文字符、数字和英文字母、数字、英文字母或下划线、URL、QQ、身份证、IP、"2000-2-28 23:29:59"、"2000-2-28"、年份、月份、日、"23:29:59"、"2000-02-29 10:29:39 pm"、"2009年2月28日"。
关键代码如下:
- protectedoverridevoid WndProc(ref Message m)
- {
- base.WndProc(ref m);
- this.BorderStyle = BorderStyle.FixedSingle;
- if (m.Msg == WM_PAINT || m.Msg == WM_NCPAINT)
- {
- if (this.BorderWeight % 2 == 0)
- {
- this.BorderWeight -= 1;
- }
- using (Graphics g = Graphics.FromHwnd(this.Handle))
- {
- using (Pen pen = new Pen(this.BorderColor, this.BorderWeight))
- {
- g.DrawRectangle(pen, 0, 0, Size.Width - 1, Size.Height - 1);
- }
- }
- WmPaint();
- }
- }
- privatevoid WmPaint()
- {
- using (Graphics graphics = Graphics.FromHwnd(base.Handle))
- {
- if (Text.Length == 0 && !string.IsNullOrEmpty(_waterMarkText) && !Focused)
- {
- TextFormatFlags format = TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter;
- if (RightToLeft == RightToLeft.Yes)
- {
- format |= TextFormatFlags.RightToLeft | TextFormatFlags.Right;
- }
- TextRenderer.DrawText(graphics, _waterMarkText, this.WaterMarkFont, base.ClientRectangle, _waterMarkTextColor, format);
- }
- }
- }
- protectedoverridevoid OnTextChanged(System.EventArgs e)
- {
- if (this.Text != " ")
- {
- bool b = Invalid(this.ControlType, this.Text);
- if (!b)
- {
- this.BorderColor = Color.Red;
- }
- else
- {
- this.BorderColor = r;
- }
- }
- else
- {
- WmPaint();
- }
- base.Invalidate();
- }
- privatebool Invalid(RegexType value, string text)
- {
- bool b = false;
- switch (value)
- {
- case RegexType.Custom:
- b = true;
- break;
- case RegexType.Number:
- b = Validation(text, @"^\d+$");
- break;
- case RegexType.CNString:
- b = Validation(text, @"^[\u4e00-\u9fa5]$");
- break;
- case RegexType.Zip:
- b = Validation(text, @"^[1-9]\d{5}$");
- break;
- case RegexType.Email:
- b = Validation(text, @"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
- break;
- case RegexType.Phone:
- b = Validation(text, @"^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$");
- break;
- case RegexType.CNPhone:
- b = Validation(text, @"^\d{3}-\d{8}|\d{4}-\d{7}$");
- break;
- case RegexType.Mobile:
- b = Validation(text, @"^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$");
- break;
- case RegexType.Integer:
- b = Validation(text, @"^-?\d+$");
- break;
- case RegexType.NInteger:
- b = Validation(text, @"^-[0-9]*[1-9][0-9]*$");
- break;
- case RegexType.Float:
- b = Validation(text, @"^(-?\d+)(\.\d+)?$");
- break;
- case RegexType.NNFloat:
- b = Validation(text, @"^\d+(\.\d+)?$");
- break;
- case RegexType.PFloat:
- b = Validation(text, @"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$");
- break;
- case RegexType.NPFloat:
- b = Validation(text, @"^((-\d+(\.\d+)?)|(0+(\.0+)?))$");
- break;
- case RegexType.NFloat:
- b = Validation(text, @"^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$");
- break;
- case RegexType.ENChar:
- b = Validation(text, @"^[A-Za-z]+$");
- break;
- case RegexType.ENUChar:
- b = Validation(text, @"^[A-Z]+$");
- break;
- case RegexType.ENLChar:
- b = Validation(text, @"^[a-z]+$");
- break;
- case RegexType.MixChar:
- b = Validation(text, @"^[A-Za-z0-9]+$");
- break;
- case RegexType.MixLineChar:
- b = Validation(text, @"^\w+$");
- break;
- case RegexType.Url:
- b = Validation(text, @"^(?:https?|ftp)\:\/\/(?:(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=:]|%[0-9a-f]{2,2})*\@)?(?:((?:(?:[a-z0-9][a-z0-9\-]*[a-z0-9]|[a-z0-9])\.)*(?:[a-z][a-z0-9\-]*[a-z0-9]|[a-z])|(?:\[[^\]]*\]))(?:\:[0-9]*)?)(?:\/(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@]|%[0-9a-f]{2,2})*)*(?:\?(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]|%[0-9a-f]{2,2})*)?(?:\#(?:[a-z0-9\-\._~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\?]|%[0-9a-f]{2,2})*)?$");
- break;
- case RegexType.QQ:
- b = Validation(text, @"^[1-9][0-9]{4,}$");
- break;
- case RegexType.DCard:
- b = Validation(text, @"^((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])|(5[0-4])|(6[1-5])|71|(8[12])|91)\d{4}((19\d{2}(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(19\d{2}(0[13578]|1[02])31)|(19\d{2}02(0[1-9]|1\d|2[0-8]))|(19([13579][26]|[2468][048]|0[48])0229))\d{3}(\d|X|x)?$");
- break;
- case RegexType.IP:
- b = Validation(text, @"^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$");
- break;
- case RegexType.DateTime:
- b = Validation(text, @"^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-)) (20|21|22|23|[0-1]?\d):[0-5]?\d:[0-5]?\d$");
- break;
- case RegexType.Date:
- b = Validation(text, @"^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$");
- break;
- case RegexType.Year:
- b = Validation(text, @"^((1[6-9]|[2-9]\d)\d{2})$");
- break;
- case RegexType.Month:
- b = Validation(text, @"^(0?[123456789]|1[012])$");
- break; span>
- case RegexType.Day:
- b = Validation(text, @"^(0?[1-9]|[12]\d|3[01])$");
- break;
- case RegexType.Time:
- b = Validation(text, @"^(20|21|22|23|[0-1]?\d):[0-5]?\d:[0-5]?\d$");
- break;
- case RegexType.DateTimeAm:
- b = Validation(text, @"^((\d{2}(([02468][048])|([13579][26]))[\-\/\s]?((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|([1-2][0-9])))))|(\d{2}(([02468][1235679])|([13579][01345789]))[\-\/\s]?((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\s(((0?[1-9])|(1[0-2]))\:([0-5][0-9])((\s)|(\:([0-5][0-9])\s))([AM|PM|am|pm]{2,2})))?$");
- break;
- case RegexType.Date2:
- b = Validation(text, @"^((((1[6-9]|[2-9]\d)\d{2})年(0?[13578]|1[02])月(0?[1-9]|[12]\d|3[01])日)|(((1[6-9]|[2-9]\d)\d{2})年(0?[13456789]|1[012])月(0?[1-9]|[12]\d|30)日)|(((1[6-9]|[2-9]\d)\d{2})年0?2月(0?[1-9]|1\d|2[0-8])日)|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))年0?2月29日))$");
- break;
- }
- return b;
- }
- privatebool Validation(string validValue, string regularExpression)
- {
- Regex regex;
- try
- {
- regex = new Regex(regularExpression);
- }
- catch
- {
- returnfalse;
- }
- if (regex.IsMatch(validValue))
- {
- returntrue;
- }
- else
- {
- returnfalse;
- }
- }
关键代码就是这么多,其实也都很简单,下面来看下实现的效果:
控件完成后,添加到窗体当中,则有如下自定义属性:
控件运行效果如下:
运行状态下,红色边框的为出错提示。
三、关联知识补遗
1、C#TextRenderer类相关应用
TextRenderer 类提供了一组 static 方法,可用于在 Windows 窗体控件上测量和绘制文本。
2、正则表达式的相关应用
C#中的正则表达式包含在.NET基础类库的一个名称空间下,这个名称空间是System.Text.RegularExpressions。
该命名空间包括8个类,1个枚举,1个委托。他们分别是:
Capture: 包含一次匹配的结果;
CaptureCollection: Capture的序列;
Group: 一次组记录的结果,由Capture继承而来;
GroupCollection:表示捕获组的集合
Match: 一次表达式的匹配结果,由Group继承而来;
MatchCollection: Match的一个序列;
MatchEvaluator: 执行替换操作时使用的委托;
Regex:编译后的表达式的实例。
RegexCompilationInfo:提供编译器用于将正则表达式编译为独立程序集的信息
RegexOptions 提供用于设置正则表达式的枚举值
Regex类中还包含一些静态的方法:
Escape: 对字符串中的regex中的转义符进行转义;
IsMatch: 如果表达式在字符串中匹配,该方法返回一个布尔值;
Match: 返回Match的实例;
Matches: 返回一系列的Match的方法;
Replace: 用替换字符串替换匹配的表达式;
Split: 返回一系列由表达式决定的字符串;
Unescape:不对字符串中的转义字符转义。
正则表达式在C#中的应该很简单就是一个简单的匹配,但是如何去写这个表达式则需要技巧和实践。
[PS:补充源代码下载]源代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2011-12-24 XML与DataSet的相互转换类
2011-12-24 C# URL返回结果并转换编码 c# HttpWebRequest与HttpWebResponse
2010-12-24 ASP.NET(C#) DataSet数据导出到Excel
2010-12-24 根据年月来判断月里天数