2025-10-31
C#
00

目录

控件特性概览
关键实现细节
边框绘制
占位符实现
圆角处理
完整代码
属性扩展
事件处理
使用示例
总结

在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 { // 普通矩形边框绘制 } }

占位符实现

通过SetPlaceHolderRemovePlaceholder方法,我们实现了类似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事件,并透传了诸如点击、鼠标进入/离开等事件。

使用示例

image.png

总结

通过自定义BzTextBox控件,我们successfully突破了系统TextBox的限制,为开发者提供了一个更加现代、灵活的输入控件。

这篇文章详细阐述了自定义TextBox控件的设计思路和关键实现。希望对您有所帮助!

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!