本篇内容介绍了“Winform控件优化之圆角按钮怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
圆角按钮实现的进一步优化
【最终实现,兼容默认按钮】
主要功能【圆角方面】
结合前面两部分介绍和代码,最终优化实现ButtonPro按钮(继承自Button),既提供Button原生功能,又提供扩展功能,除了圆角以外,还实现了圆形、圆角矩形的脚尖效果、边框大小和颜色、背景渐变颜色、圆角的绘制模式和创建Region模式、常用按钮图标快速使用(此项暂未实现)等其他功能。
圆角按钮控件相关属性和实现:
RoundRadius
:圆角半径,>=0时启用圆角按钮,等于0为直角(但可使用背景色等所有Round圆角相关属性),<0时使用默认Button样式。RegionNewModel
:创建新Region的模式,使用'绘制范围'创建新的Region,实现控件区域贴合绘制范围,实现图形外的部分"正确的透明",但相对会有些锯齿。ShowCusp
:是否显示尖角,默认不显示,当启用Radius圆角(RoundRadius>=0)时才有效。CuspAlign
:(三角)尖角的显示位置,当启用圆角按钮(RoundRadius>=0),且显示尖角时有效。RoundBorderWidth
:启用Radius圆角(RoundRadius>=0)时边框宽度,默认0。RoundBorderColor
:启用Radius圆角(RoundRadius>=0)时边框颜色,默认黑色。EnableBGGradient
:启用渐变背景色(需要RoundRadius>=0),启用后RoundNormalColor、RoundHoverColor、RoundPressedColor颜色无效。GradientModel
:线性渐变的模式,默认垂直渐变。BGColorBegin
:渐变开始色。BGColorEnd
:渐变结束色。RoundNormalColor
:启用Radius圆角(RoundRadius>=0)时按钮标准颜色。RoundHoverColor
:启用Radius圆角(RoundRadius>=0)鼠标位于按钮上时的按钮颜色。RoundPressedColor
:启用Radius圆角(RoundRadius>=0)鼠标按下时的按钮颜色。圆形按钮,长宽一样,圆角半径为长宽的一半,可实现圆形按钮。
扩展控件属性分类修改为“高级”,使用
CategoryAttribute
特性。
注意:
borderPen绘制线条的对齐方式:
borderPen.Alignment = PenAlignment.Inset;。但是指定Inset绘制边框也有小问题,如果是重新创建Region,则绘制边框后内部变为直角矩形(先填充再绘制边框线条);如果使用就有Region(不新建),也会变成内部直角,并且如果不指定Inset则会外部绘制的Border线条变成直接(由原本直角的Region承载圆角之外的部分)。总之都有些问题,可自行测试。
在绘制边框时,不推荐使用
PenAlignment.Inset,通过计算减少Rectangle的范围为半个Border的路径范围(宽高-RoundBorderWidth),绘制时在路径内外正好有一个Border的大小来实现。而且这样不会发生上面介绍的内或外直角而非圆角的情况
OnPaint方法中不要使用e.ClipRectangle
应该使用控件的宽高(Width、Height)计算Rectangle矩形,或者使用ClientRectangle
属性。
相互影响的问题,复制的或拖拽的ButtonPro控件,会被其他控件影响,即调整某个控件,会导致ButtonPro的一个或多个也会牵连变化(很杂乱的关联变化),如何解决?应该是独立的才对!且在设计器中没法通过撤销操作还原效果
与SetStyle设置无关,后面测试和重新指定Region有关,在后续测试发现最终导致此类问题的原因,在于使用了OnPaint参数
e.ClipRectangle作为控件绘制范围绘制产生的。【比如
rect = e.Graphics.DrawRoundRectAndCusp(e.ClipRectangle, roundRadius, baseColor, showCusp, cuspAlign)】
直接使用控件的
Width和
Height定义绘制绘制范围,替换到
e.ClipRectangle。
OnPaint方法中不要使用
e.ClipRectangle作为控件绘制范围
以下问题均是由于OnPaint方法中使用
e.ClipRectangle来绘制绘制范围导致的(它是个自动被影响的值,应该使用Width、Height创建)
不重新赋值Region,拖动或调整按钮,会重写显示相互影响:
如果赋值新的Region,当调整或移动控件时,就有可能影响显示的布局或大小,且无法通过撤销还原
重新创建Region的锯齿问题和优势
【写错代码实现圆形导致的错误探索,但也很有意义】
重新赋值
Region一个最大的确实是会产生一些锯齿,即使使用抗锯齿和最好质量绘制,要想绘制圆形效果,必须重新赋值
Region,否则控件只会是圆角。因此,提供了
RoundCircleModel属性,用于是否启用可绘制圆形按钮的模式,会有些锯齿,默认不启用,如果需要时启用即可。
需要记住的几点:
Region
定义的是控件的区域,通过GDI+绘制可以实现一个自定义的区域,从而解除默认的宽高矩形区域控件的限制。重新定义和赋值
Region
的缺点是会产生一定锯齿,这个锯齿是Region
产生的,而不是GDI+绘制填充产生的。无法消除创建的
Region
锯齿,至少没提供相关API,因此实际重绘控件时,通常不要创建新的Region
由于设置中使用了Winform透明父控件的样式,因此要注意其正确的父控件设置。
直接使用绘制后的正确的绘制范围创建新的
Region区域,则没有透明父控件的问题,可以实现“正确的透明”,具体可参加下图所示【新建
Region绘制图形后圆角边缘部分出现的1像素的控件颜色可以通过调整创建
Region时和绘制时的范围消除(多余的1像素白边问题无法简单的通过调整
Region和绘制范围解决,具体可自行测试)】。
【若是消除新建Region的锯齿问题,将会非常完美】
若是能实现直接绘制无锯齿的圆角Region区域,则,直接在Paint事件中实现控件的圆角Region即可,无需在额外重新绘制背景和文字。【更简单、更完美的方案】
目前所知,无法对Region进行抗锯齿,即使使用使用GDI+的API
CreateRoundRectRgn方法。相关介绍参见 c# How to make smooth arc region using graphics path、Winforms: Smooth the rounded edges for panel
控件上重绘可以使用
Graphics对象在背景透明的Region区域控件上实现,其背后至少一个父控件(或顶层的Form窗体)。
但是,对于一个Form,要想实现圆角或多边形窗体,则必须重新生成Region,但创建Region在不规则形状时边缘锯齿无法解决(如果有大牛,应该可以应用消除锯齿的算法),后面会介绍一种取巧或者Win32窗体推荐的一种方式,即,使用Layered Windows。
代码具体实现
将绘制方法精简为扩展方法后,扩展控件的全部源代码如下:
扩展方法参见Winform控件优化Paint事件实现圆角组件及提取绘制圆角的方法
using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace CMControls { public class ButtonPro : Button { private int roundRadius;//半径 private bool showCusp = false;//显示尖角 private RectangleAlign cuspAlign = RectangleAlign.RightTop;//三角尖角位置 private Color roundBorderColor = Color.Black;//边框颜色 private int roundBorderWidth = 0;//边框宽度 private Color roundHoverColor = Color.FromArgb(220, 80, 80);//鼠标位于控件时颜色 private Color roundNormalColor = Color.FromArgb(51, 161, 224);//基颜色 private Color roundPressedColor = Color.FromArgb(251, 161, 0);//鼠标按下控件时基颜色 // 鼠标相对控件的状态位置,对应上面不同颜色 private MouseControlState mouseControlState = MouseControlState.Normal; private bool regionNewModel = false; // 创建新Region的模式,使用"绘制范围"创建新的Region,实现控件区域贴合绘制范围,实现图形外的部分"正确的透明",但相对会有些锯齿 private Color beginBGColor; //= Color.FromArgb(251, 161, 0);//渐变开始色 private Color endBGColor; //= Color.FromArgb(251, 161, 0);//渐变结束色 private bool enableBGGradient = false; //使用渐变色 private LinearGradientMode gradientModel = LinearGradientMode.Vertical; //线性渐变的模式 private Region originRegion; /// <summary> /// 圆形按钮的半径属性 /// </summary> [CategoryAttribute("高级"), DefaultValue(20), Description("圆角半径,>=0时启用圆角按钮,等于0为直角(但可使用背景色等所有Round圆角相关属性),<0时使用默认Button样式")] public int RoundRadius { set { roundRadius = value; // 使控件的整个画面无效并重绘控件 this.Invalidate(); } get { return roundRadius; } } /// <summary> /// 圆角下创建新Region模式 /// </summary> [CategoryAttribute("高级"), DefaultValue(false), Description("创建新Region的模式,使用'绘制范围'创建新的Region,实现控件区域贴合绘制范围,实现'正确的透明',但相对会有些的锯齿")] public bool RegionNewModel { set { regionNewModel = value; this.Invalidate(); } get { return regionNewModel; } } /// <summary> /// 三角尖角位置,当启用圆角 /// </summary> [CategoryAttribute("高级"), Description("(三角)尖角的显示位置,当启用圆角按钮(RoundRadius>=0),且显示尖角时有效"), DefaultValue(RectangleAlign.RightTop)] public RectangleAlign CuspAlign { set { cuspAlign = value; this.Invalidate(); } get { return cuspAlign; } } [CategoryAttribute("高级"), Description("是否显示尖角,默认不显示,当启用Radius圆角(RoundRadius>=0)时才有效"), DefaultValue(false)] public bool ShowCusp { set { showCusp = value; this.Invalidate(); } get { return showCusp; } } [CategoryAttribute("高级"), DefaultValue(0), Description("启用Radius圆角(RoundRadius>=0)时边框宽度,默认0")] public int RoundBorderWidth { set { roundBorderWidth = value; this.Invalidate(); } get { return roundBorderWidth; } } [CategoryAttribute("高级"), DefaultValue(typeof(Color), "0, 0, 0"), Description("启用Radius圆角(RoundRadius>=0)时边框颜色,默认黑色")] public Color RoundBorderColor { get { return this.roundBorderColor; } set { this.roundBorderColor = value; this.Invalidate(); } } /// <summary> /// 是否启用背景渐变色,启用后RoundNormalColor、RoundHoverColor、RoundPressedColor颜色无效 /// </summary> [CategoryAttribute("高级"), DefaultValue(false), Description("启用渐变背景色(需要RoundRadius>=0),启用后RoundNormalColor、RoundHoverColor、RoundPressedColor颜色无效")] public bool EnableBGGradient { get { return this.enableBGGradient; } set { this.enableBGGradient = value; this.Invalidate(); } } /// <summary> /// 线性渐变的模式,默认垂直渐变 /// </summary> [CategoryAttribute("高级"), DefaultValue(LinearGradientMode.Vertical), Description("线性渐变的模式,默认垂直渐变")] public LinearGradientMode GradientModel { get { return this.gradientModel; } set { this.gradientModel = value; this.Invalidate(); } } /// <summary> /// 背景渐变色 /// </summary> [CategoryAttribute("高级"), DefaultValue(typeof(Color), "0, 122, 204"), Description("渐变开始色")] public Color BGColorBegin { get { return this.beginBGColor; } set { this.beginBGColor = value; this.Invalidate(); } } /// <summary> /// 背景渐变色 /// </summary> [CategoryAttribute("高级"), DefaultValue(typeof(Color), "8, 39, 57"), Description("渐变结束色")] public Color BGColorEnd { get { return this.endBGColor; } set { this.endBGColor = value; this.Invalidate(); } } [CategoryAttribute("高级"), DefaultValue(typeof(Color), "51, 161, 224"), Description("启用Radius圆角(RoundRadius>=0)时按钮标准颜色")] public Color RoundNormalColor { get { return this.roundNormalColor; } set { this.roundNormalColor = value; this.Invalidate(); } } [CategoryAttribute("高级"), DefaultValue(typeof(Color), "220, 80, 80"), Description("启用Radius圆角(RoundRadius>=0)鼠标位于按钮上时的按钮颜色")] public Color RoundHoverColor { get { return this.roundHoverColor; } set { this.roundHoverColor = value; this.Invalidate(); } } [CategoryAttribute("高级"), DefaultValue(typeof(Color), "251, 161, 0"), Description("启用Radius圆角(RoundRadius>=0)鼠标按下时的按钮颜色")] public Color RoundPressedColor { get { return this.roundPressedColor; } set { this.roundPressedColor = value; this.Invalidate(); } } protected override void OnMouseEnter(EventArgs e)//鼠标进入时 { mouseControlState = MouseControlState.Hover;//Hover base.OnMouseEnter(e); } protected override void OnMouseLeave(EventArgs e)//鼠标离开 { mouseControlState = MouseControlState.Normal;//正常 base.OnMouseLeave(e); } protected override void OnMouseDown(MouseEventArgs e)//鼠标按下 { if (e.Button == MouseButtons.Left && e.Clicks == 1)//鼠标左键且点击次数为1 { mouseControlState = MouseControlState.Pressed;//按下的状态 } base.OnMouseDown(e); } protected override void OnMouseUp(MouseEventArgs e)//鼠标弹起 { if (e.Button == MouseButtons.Left && e.Clicks == 1) { if (ClientRectangle.Contains(e.Location))//控件区域包含鼠标的位置 { mouseControlState = MouseControlState.Hover; } else { mouseControlState = MouseControlState.Normal; } } base.OnMouseUp(e); } public ButtonPro() { ForeColor = Color.White; this.FlatStyle = FlatStyle.Flat; this.FlatAppearance.BorderSize = 0; FlatAppearance.MouseDownBackColor = Color.Transparent; FlatAppearance.MouseOverBackColor = Color.Transparent; FlatAppearance.CheckedBackColor = Color.Transparent; RoundRadius = 20; // 似乎当值为默认20时重新生成设计器或者重新打开项目后,此属性就会变为0,必须在构造函数中指定20来解决 this.mouseControlState = MouseControlState.Normal; // 原始Region originRegion = Region; } public override void NotifyDefault(bool value) { base.NotifyDefault(false); // 去除窗体失去焦点时最新激活的按钮边框外观样式 } //重写OnPaint protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); //base.OnPaintBackground(e); // 不能使用 e.ClipRectangle.GetRoundedRectPath(_radius) 计算控件全部的Region区域,e.ClipRectangle 似乎是变化的,必须使用固定的Width和Height,包括下面的绘制也不能使用e.ClipRectangle // 在Paint事件中也不推荐使用e.ClipRectangle时没问题的 Rectangle controlRect = new Rectangle(0, 0, this.Width, this.Height); // roundRadius 修改回来是要还原 if (roundRadius >= 0 && regionNewModel) // 圆角下创建新Region模式,使用自定义Region { var controlPath = controlRect.GetRoundedRectPath(roundRadius); // 要在绘制之前指定Region,否则无效 this.Region = new Region(controlPath); } else // 修改对应调整 { //this.Region = new Region(controlRect);//也属于重新修改 this.Region = originRegion; } if (roundRadius >= 0) { Rectangle rect; if (enableBGGradient) { rect = e.Graphics.DrawRoundRectAndCusp(controlRect, roundRadius, beginBGColor, endBGColor, true, CuspAlign, gradientModel, roundBorderWidth > 0 ? new Pen(roundBorderColor, roundBorderWidth) : null); } else { Color baseColor; switch (mouseControlState) { case MouseControlState.Hover: baseColor = this.roundHoverColor; break; case MouseControlState.Pressed: baseColor = this.roundPressedColor; break; case MouseControlState.Normal: baseColor = this.roundNormalColor; break; default: baseColor = this.roundNormalColor; &