在Windows Forms应用程序中,系统默认的TextBox控件功能相对简单,且样式单一。为了满足现代UI设计的需求,我们需要一个更加灵活、可定制的输入控件。本文将详细介绍如何通过继承UserControl和内置TextBox来创建一个增强版本的文本输入控件BzTextBox。
我们的BzTextBox控件具备以下高级特性:
控件最大的亮点在于其自定义边框绘制逻辑。通过重写OnPaint方法,我们实现了多种边框样式:
C#protected override void OnPaint(PaintEventArgs e)
{
// 支持圆角边框
if (borderRadius > 1)
{
// 复杂的圆角路径绘制逻辑
using (GraphicsPath pathBorderSmooth = GetFigurePath(rectBorderSmooth, borderRadius))
{
// 边框绘制
}
}
else
{
// 普通矩形边框绘制
}
}
通过SetPlaceHolder和RemovePlaceholder方法,我们实现了类似HTML5的输入框占位符效果:
C#private void SetPlaceHolder()
{
if (string.IsNullOrWhiteSpace(txt.Text) && placeholderText != "")
{
isPlaceholder = true;
txt.Text = placeholderText;
txt.ForeColor = placeholderColor;
}
}
为了支持圆角,我们编写了GetFigurePath方法,生成圆角矩形路径:
C#private GraphicsPath GetFigurePath(RectangleF rect, float radius)
{
GraphicsPath path = new GraphicsPath();
float curveSize = radius * 2F;
path.AddArc(rect.X, rect.Y, curveSize, curveSize, 180, 90);
// 添加其他弧形
path.CloseAllFigures();
return path;
}
C#namespace AppControls
{
partial class ModernTextBox
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
txt = new TextBox();
SuspendLayout();
//
// txt
//
txt.BorderStyle = BorderStyle.None;
txt.Dock = DockStyle.Fill;
txt.Location = new Point(5, 5);
txt.Name = "txt";
txt.Size = new Size(258, 16);
txt.TabIndex = 0;
txt.Click += txt_Click;
txt.TextChanged += txt_TextChanged;
txt.Enter += txt_Enter;
txt.Leave += txt_Leave;
txt.MouseEnter += txt_MouseEnter;
txt.MouseLeave += txt_MouseLeave;
//
// ModernTextBox
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
Controls.Add(txt);
Name = "ModernTextBox";
Padding = new Padding(5);
Size = new Size(268, 34);
ResumeLayout(false);
PerformLayout();
}
#endregion
private TextBox txt;
}
}
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppControls
{
[DefaultEvent("TextChanged")]
public partial class ModernTextBox : UserControl
{
public ModernTextBox()
{
InitializeComponent();
}
private Color borderColor = Color.MediumSlateBlue;
private int borderSize = 2;
private bool underlinedStyle = false;
private Color borderFocuseColor = Color.HotPink;
private bool isFocused = false;
private int borderRadius = 0;
private Color placeholderColor = Color.DarkGray;
private string placeholderText = "";
private bool isPlaceholder = false;
private bool isPasswordChar = false;
public new event EventHandler TextChanged;
[Category("Advance")]
public Color BorderColor
{
get
{
return borderColor;
}
set
{
borderColor = value;
this.Invalidate();
}
}
[Category("Advance")]
public int BorderSize
{
get
{
return borderSize;
}
set
{
borderSize = value;
this.Invalidate();
}
}
[Category("Advance")]
public bool UnderlinedStyle
{
get
{
return underlinedStyle;
}
set
{
underlinedStyle = value;
this.Invalidate();
}
}
[Category("Advance")]
public bool PasswordChar
{
get
{
return isPasswordChar;
}
set
{
isPasswordChar = value;
txt.UseSystemPasswordChar = value;
}
}
[Category("Advance")]
public bool Multiline
{
get
{
return txt.Multiline;
}
set
{
txt.Multiline = value;
}
}
[Category("Advance")]
public override Color BackColor
{
get
{
return base.BackColor;
}
set
{
base.BackColor = value;
txt.BackColor = value;
}
}
[Category("Advance")]
public override Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
base.ForeColor = value;
txt.ForeColor = value;
}
}
[Category("Advance")]
public override Font Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
txt.Font = value;
if (this.DesignMode)
{
UpdateControlHeight();
}
}
}
[Category("Advance")]
public string Texts
{
get
{
if (isPlaceholder)
{
return "";
}
else
{
return txt.Text;
}
}
set
{
txt.Text = value;
SetPlaceHolder();
}
}
[Category("Advance")]
public ScrollBars ScrollBars
{
get
{
return txt.ScrollBars;
}
set
{
if (Multiline)
{
txt.ScrollBars = value;
UpdateControlHeight();
}
}
}
[Category("Advance")]
public Color BorderFocuseColor
{
get
{
return borderFocuseColor;
}
set
{
borderFocuseColor = value;
}
}
[Category("Advance")]
public int BorderRadius
{
get
{
return borderRadius;
}
set
{
if (value >= 0)
{
borderRadius = value;
this.Invalidate();
}
}
}
[Category("Advance")]
public Color PlaceHolderColor
{
get
{
return placeholderColor;
}
set
{
placeholderColor = value;
if (isPasswordChar)
{
txt.ForeColor = value;
}
}
}
[Category("Advance")]
public string PlaceholderText
{
get
{
return placeholderText;
}
set
{
placeholderText = value;
txt.Text = "";
SetPlaceHolder();
}
}
private void SetPlaceHolder()
{
if (string.IsNullOrWhiteSpace(txt.Text) && placeholderText != "")
{
isPlaceholder = true;
txt.Text = placeholderText;
txt.ForeColor = placeholderColor;
if (isPasswordChar)
{
txt.UseSystemPasswordChar = false;
}
}
}
private void RemovePlaceholder()
{
if (isPlaceholder && placeholderText != "")
{
isPlaceholder = false;
txt.Text = "";
txt.ForeColor = this.ForeColor;
if (isPasswordChar)
{
txt.UseSystemPasswordChar = true;
}
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics graphics = e.Graphics;
if (borderRadius > 1)
{
var rectBorderSmooth = this.ClientRectangle;
var rectBorder = Rectangle.Inflate(rectBorderSmooth, -BorderSize, -BorderSize);
int smoothSize = BorderSize > 0 ? BorderSize : 1;
using (GraphicsPath pathBorderSmooth = GetFigurePath(rectBorderSmooth, borderRadius))
using (GraphicsPath pathBorder = GetFigurePath(rectBorder, borderRadius - BorderSize))
using (Pen penBorderSmooth = new Pen(this.Parent.BackColor, smoothSize))
using (Pen penBorder = new Pen(BorderColor, BorderSize))
{
this.Region = new Region(pathBorderSmooth);
if (BorderRadius > 15)
{
SetTextBoxRoundedRegion();
}
graphics.SmoothingMode = SmoothingMode.AntiAlias;
penBorder.Alignment = PenAlignment.Center;
if (isFocused)
{
penBorder.Color = BorderFocuseColor;
}
if (underlinedStyle)
{
graphics.DrawPath(penBorderSmooth, pathBorderSmooth);
graphics.SmoothingMode = SmoothingMode.None;
graphics.DrawLine(penBorder, 0, this.Height - 1, this.Width, this.Height - 1);
}
else
{
graphics.DrawPath(penBorderSmooth, pathBorderSmooth);
graphics.DrawPath(penBorder, pathBorder);
}
}
}
else
{
using (Pen penBorder = new Pen(borderColor, BorderSize))
{
this.Region = new Region(this.ClientRectangle);
penBorder.Alignment = PenAlignment.Inset;
if (!isFocused)
{
if (underlinedStyle)
{
graphics.DrawLine(penBorder, 0, this.Height - 1, this.Width, this.Height - 1);
}
else
{
graphics.DrawRectangle(penBorder, 0, 0, this.Width - 0.5F, this.Height - 0.5F);
}
}
else
{
penBorder.Color = borderFocuseColor;
if (underlinedStyle)
{
graphics.DrawLine(penBorder, 0, this.Height - 1, this.Width, this.Height - 1);
}
else
{
graphics.DrawRectangle(penBorder, 0, 0, this.Width - 0.5F, this.Height - 0.5F);
}
}
}
}
}
private void SetTextBoxRoundedRegion()
{
GraphicsPath pathText;
if (Multiline)
{
pathText = GetFigurePath(txt.ClientRectangle, borderRadius - borderSize);
txt.Region = new Region(pathText);
}
else
{
pathText = GetFigurePath(txt.ClientRectangle, borderSize * 2);
txt.Region = new Region(pathText);
}
}
private GraphicsPath GetFigurePath(RectangleF rect, float radius)
{
GraphicsPath path = new GraphicsPath();
float curveSize = radius * 2F;
path.StartFigure();
path.AddArc(rect.X, rect.Y, curveSize, curveSize, 180, 90);
path.AddArc(rect.Right - curveSize, rect.Y, curveSize, curveSize, 270, 90);
path.AddArc(rect.Right - curveSize, rect.Bottom - curveSize, curveSize, curveSize, 0, 90);
path.AddArc(rect.X, rect.Bottom - curveSize, curveSize, curveSize, 90, 90);
path.CloseAllFigures();
return path;
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
UpdateControlHeight();
}
private void UpdateControlHeight()
{
if (txt.Multiline == false)
{
int txtHeight = TextRenderer.MeasureText("Text", this.Font).Height + 1;
txt.Multiline = true;
txt.MinimumSize = new Size(0, txtHeight);
txt.Multiline = false;
this.Height = txt.Height + this.Padding.Top + this.Padding.Bottom;
}
}
private void txt_TextChanged(object sender, EventArgs e)
{
if (TextChanged != null)
{
TextChanged.Invoke(sender, e);
}
}
private void txt_KeyPress(object sender, KeyPressEventArgs e)
{
this.OnKeyPress(e);
}
private void txt_Click(object sender, EventArgs e)
{
this.OnClick(e);
}
private void txt_MouseEnter(object sender, EventArgs e)
{
this.OnMouseEnter(e);
}
private void txt_MouseLeave(object sender, EventArgs e)
{
this.OnMouseLeave(e);
}
private void txt_Enter(object sender, EventArgs e)
{
isFocused = true;
this.Invalidate();
RemovePlaceholder();
}
private void txt_Leave(object sender, EventArgs e)
{
isFocused = false;
this.Invalidate();
SetPlaceHolder();
}
}
}
控件通过大量自定义属性,为开发者提供了极大的灵活性:
BorderColor:边框颜色BorderSize:边框粗细BorderRadius:圆角半径PlaceholderText:占位符文本PasswordChar:密码模式Multiline:多行文本控件重新定义了TextChanged事件,并透传了诸如点击、鼠标进入/离开等事件。

通过自定义BzTextBox控件,我们successfully突破了系统TextBox的限制,为开发者提供了一个更加现代、灵活的输入控件。
这篇文章详细阐述了自定义TextBox控件的设计思路和关键实现。希望对您有所帮助!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!