Winform控件優(yōu)化之圓角按鈕1
前言
Windows 11下所有控件已經(jīng)默認(rèn)采用圓角,其效果更好、相對(duì)有著更好的優(yōu)化,只是這是默認(rèn)的行為,無(wú)法進(jìn)一步自定義。
圓角按鈕實(shí)現(xiàn)【重寫(xiě)OnPaint實(shí)現(xiàn)圓角繪制】
控件自定義繪制的關(guān)鍵在于:重寫(xiě)OnPaint方法,其參數(shù)提供了用于GDI+繪圖的Graphics對(duì)象,由此實(shí)現(xiàn)繪制需要的圖形效果。
為了更好的顯示繪制的圖形,通常必須設(shè)置控件背景透明(圖形外的控件區(qū)域透明,便于正確顯示繪制的圖形),雖然Winform的背景透明有著一定缺陷,但總體來(lái)說(shuō)這是必須的。
此外,Paint事件方法中,也可以進(jìn)行控件的繪制(重繪),與繼承重寫(xiě)OnPaint沒(méi)有本質(zhì)區(qū)別。
代碼主要關(guān)鍵點(diǎn)或思路、優(yōu)化
- 半徑Radius、Color、TextAlign屬性的賦值,都調(diào)用
Invalidate()方法使控件畫(huà)面無(wú)效并重繪控件。 - 添加文本位置的屬性TextAlign,并在屬性賦值時(shí)調(diào)用
Invalidate()重繪控件,實(shí)現(xiàn)修改文本位置的布局。 - 有一個(gè)bug問(wèn)題,就是在點(diǎn)擊按鈕鼠標(biāo)抬起方法
OnMouseUp中,實(shí)現(xiàn)了修改鼠標(biāo)狀態(tài),對(duì)應(yīng)的背景顏色值也修改了,控件重繪時(shí)也修改了顏色(debug),但絕大多數(shù)情況下,鼠標(biāo)抬起背景顏色未變化。原因在重寫(xiě)的OnMouseUp(MouseEventArgs e)中先調(diào)用的控件基類(lèi)base.OnMouseUp(e);,后修改的狀態(tài)顏色,將base.OnMouseUp(e);改為最后調(diào)用即可。 - 修改和優(yōu)化圓角部分圓弧的繪制,原實(shí)現(xiàn)圓弧半徑處理不合理。
- 【盡可能高質(zhì)量繪制】圖形部分的幾個(gè)模式必須指定,怎么明顯看出顯示的文本、邊框等不清晰、鋸齒驗(yàn)證等問(wèn)題。
- 其他一些小修改和調(diào)整,比如抗鋸齒、高質(zhì)量繪圖、使用控件字體、文本顏色默認(rèn)白色、設(shè)置字體方向等
- Radius 屬性修改邊角半徑大小(即圓角的大小、圓弧的大小)
- NormalColor、HoverColor、PressedColor 屬性設(shè)置按鈕正常狀態(tài)、鼠標(biāo)懸停、鼠標(biāo)按下時(shí)的背景顏色,通常設(shè)置為一致即可。
- 指定Size的Width、Height的大小相同,Radius為正方向邊長(zhǎng)的一半,可以實(shí)現(xiàn)圓形按鈕。

StringFormat對(duì)象,可以提供對(duì)字符串文本的顏色、布局、方向等各種格式的設(shè)置,用于渲染文本效果。
Control.DesignMode屬性可以判斷當(dāng)前代碼的執(zhí)行環(huán)境是否是設(shè)計(jì)器模式,在某些條件下可以通過(guò)此判斷,決定是否在設(shè)計(jì)器下執(zhí)行某段代碼(如果不涉及樣式效果,就可以不需要在設(shè)計(jì)器下執(zhí)行)
使用圓角按鈕
編譯后,直接從工具箱中拖動(dòng)RoundButtons圓角按鈕控件到窗體即可。

代碼如下,關(guān)鍵部分都有相關(guān)注釋?zhuān)梢灾苯舆^(guò)一遍代碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CMControls.RoundButtons
{
public enum ControlState { Hover, Normal, Pressed }
public class RoundButton : Button
{
private int radius;//半徑
//private Color _borderColor = Color.FromArgb(51, 161, 224);//邊框顏色
private Color _hoverColor = Color.FromArgb(220, 80, 80);//基顏色
private Color _normalColor = Color.FromArgb(51, 161, 224);//基顏色
private Color _pressedColor = Color.FromArgb(251, 161, 0);//基顏色
private ContentAlignment _textAlign = ContentAlignment.MiddleCenter;
public override ContentAlignment TextAlign
{
set
{
_textAlign = value;
this.Invalidate();
}
get
{
return _textAlign;
}
}
/// <summary>
/// 圓角按鈕的半徑屬性
/// </summary>
[CategoryAttribute("Layout"), BrowsableAttribute(true), ReadOnlyAttribute(false)]
public int Radius
{
set
{
radius = value;
// 使控件的整個(gè)畫(huà)面無(wú)效并重繪控件
this.Invalidate();
}
get
{
return radius;
}
}
[CategoryAttribute("Appearance"), DefaultValue(typeof(Color), "51, 161, 224")]
public Color NormalColor
{
get
{
return this._normalColor;
}
set
{
this._normalColor = value;
this.Invalidate();
}
}
[CategoryAttribute("Appearance"), DefaultValue(typeof(Color), "220, 80, 80")]
public Color HoverColor
{
get
{
return this._hoverColor;
}
set
{
this._hoverColor = value;
this.Invalidate();
}
}
[CategoryAttribute("Appearance"), DefaultValue(typeof(Color), "251, 161, 0")]
public Color PressedColor
{
get
{
return this._pressedColor;
}
set
{
this._pressedColor = value;
this.Invalidate();
}
}
public ControlState ControlState { get; set; }
protected override void OnMouseEnter(EventArgs e)//鼠標(biāo)進(jìn)入時(shí)
{
ControlState = ControlState.Hover;//Hover
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)//鼠標(biāo)離開(kāi)
{
ControlState = ControlState.Normal;//正常
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs e)//鼠標(biāo)按下
{
if (e.Button == MouseButtons.Left && e.Clicks == 1)//鼠標(biāo)左鍵且點(diǎn)擊次數(shù)為1
{
ControlState = ControlState.Pressed;//按下的狀態(tài)
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)//鼠標(biāo)彈起
{
if (e.Button == MouseButtons.Left && e.Clicks == 1)
{
if (ClientRectangle.Contains(e.Location))//控件區(qū)域包含鼠標(biāo)的位置
{
ControlState = ControlState.Hover;
}
else
{
ControlState = ControlState.Normal;
}
}
base.OnMouseUp(e);
}
public RoundButton()
{
ForeColor = Color.White;
Radius = 20;
this.FlatStyle = FlatStyle.Flat;
this.FlatAppearance.BorderSize = 0;
this.ControlState = ControlState.Normal;
this.SetStyle(
ControlStyles.UserPaint | //控件自行繪制,而不使用操作系統(tǒng)的繪制
ControlStyles.AllPaintingInWmPaint | //忽略背景擦除的Windows消息,減少閃爍,只有UserPaint設(shè)為true時(shí)才能使用。
ControlStyles.OptimizedDoubleBuffer |//在緩沖區(qū)上繪制,不直接繪制到屏幕上,減少閃爍。
ControlStyles.ResizeRedraw | //控件大小發(fā)生變化時(shí),重繪。
ControlStyles.SupportsTransparentBackColor, //支持透明背景顏色
true);
}
//重寫(xiě)OnPaint
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
// base.OnPaintBackground(e);
// 盡可能高質(zhì)量繪制
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
Rectangle rect = new Rectangle(0, 0, this.Width, this.Height);
var path = GetRoundedRectPath(rect, radius);
this.Region = new Region(path);
Color baseColor;
//Color borderColor;
//Color innerBorderColor = this._baseColor;//Color.FromArgb(200, 255, 255, 255); ;
switch (ControlState)
{
case ControlState.Hover:
baseColor = this._hoverColor;
break;
case ControlState.Pressed:
baseColor = this._pressedColor;
break;
case ControlState.Normal:
baseColor = this._normalColor;
break;
default:
baseColor = this._normalColor;
break;
}
using (SolidBrush b = new SolidBrush(baseColor))
{
e.Graphics.FillPath(b, path); // 填充路徑,而不是DrawPath
using (Brush brush = new SolidBrush(this.ForeColor))
{
// 文本布局對(duì)象
using (StringFormat gs = new StringFormat())
{
// 文字布局
switch (_textAlign)
{
case ContentAlignment.TopLeft:
gs.Alignment = StringAlignment.Near;
gs.LineAlignment = StringAlignment.Near;
break;
case ContentAlignment.TopCenter:
gs.Alignment = StringAlignment.Center;
gs.LineAlignment = StringAlignment.Near;
break;
case ContentAlignment.TopRight:
gs.Alignment = StringAlignment.Far;
gs.LineAlignment = StringAlignment.Near;
break;
case ContentAlignment.MiddleLeft:
gs.Alignment = StringAlignment.Near;
gs.LineAlignment = StringAlignment.Center;
break;
case ContentAlignment.MiddleCenter:
gs.Alignment = StringAlignment.Center; //居中
gs.LineAlignment = StringAlignment.Center;//垂直居中
break;
case ContentAlignment.MiddleRight:
gs.Alignment = StringAlignment.Far;
gs.LineAlignment = StringAlignment.Center;
break;
case ContentAlignment.BottomLeft:
gs.Alignment = StringAlignment.Near;
gs.LineAlignment = StringAlignment.Far;
break;
case ContentAlignment.BottomCenter:
gs.Alignment = StringAlignment.Center;
gs.LineAlignment = StringAlignment.Far;
break;
case ContentAlignment.BottomRight:
gs.Alignment = StringAlignment.Far;
gs.LineAlignment = StringAlignment.Far;
break;
default:
gs.Alignment = StringAlignment.Center; //居中
gs.LineAlignment = StringAlignment.Center;//垂直居中
break;
}
// if (this.RightToLeft== RightToLeft.Yes)
// {
// gs.FormatFlags = StringFormatFlags.DirectionRightToLeft;
// }
e.Graphics.DrawString(this.Text, this.Font, brush, rect, gs);
}
}
}
}
/// <summary>
/// 根據(jù)矩形區(qū)域rect,計(jì)算呈現(xiàn)radius圓角的Graphics路徑
/// </summary>
/// <param name="rect"></param>
/// <param name="radius"></param>
/// <returns></returns>
private GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
{
#region 正確繪制圓角矩形區(qū)域
int R = radius*2;
Rectangle arcRect = new Rectangle(rect.Location, new Size(R, R));
GraphicsPath path = new GraphicsPath();
// 左上圓弧 左手坐標(biāo)系,順時(shí)針為正 從180開(kāi)始,轉(zhuǎn)90度
path.AddArc(arcRect, 180, 90);
// 右上圓弧
arcRect.X = rect.Right - R;
path.AddArc(arcRect, 270, 90);
// 右下圓弧
arcRect.Y = rect.Bottom - R;
path.AddArc(arcRect, 0, 90);
// 左下圓弧
arcRect.X = rect.Left;
path.AddArc(arcRect, 90, 90);
path.CloseFigure();
return path;
#endregion
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
}
}
}可以改進(jìn)和實(shí)現(xiàn)的
- 添加Border,實(shí)現(xiàn)Border顏色和寬度的指定(目前的一個(gè)思路時(shí)利用路徑在外層填充一個(gè)圓角矩形,在內(nèi)層再填充一個(gè)圓角矩形,形成有Border的效果;另一個(gè)思路時(shí),畫(huà)路徑時(shí),繪制內(nèi)層路徑和圓環(huán)路徑,Border部分是一個(gè)圓角的圓環(huán)路徑,而后分別填充顏色;還有就是繪制路徑線(xiàn)條,線(xiàn)條作為Border。)
- 通過(guò)百分比實(shí)現(xiàn)圓角
- 完全擴(kuò)展Button,通過(guò)標(biāo)志位啟動(dòng)圓角和修改圓角,做到圓角和普通Button共存。
- 修改使用
RectangleF對(duì)象,使用浮點(diǎn)數(shù)繪制矩形和路徑 - 圓角半徑radius指定為0的處理
Rectangle.Round(RectangleF)將RectangleF對(duì)象轉(zhuǎn)換為Rectangle,通過(guò)舍入最近的數(shù)。
利用填充內(nèi)外兩層圓角矩形路徑形成Border
【有著致命缺陷(隨后介紹了正確處理的方案)】
控件的Region區(qū)域一定指定,并且要包含全部的Graphics繪制的內(nèi)容,否則顯示不全,包含在Region內(nèi)才能全部顯示出來(lái)。
Region區(qū)域指定的是控件的區(qū)域,表示的是控件的范圍
如下,通過(guò)Border大小 _borderWidth 計(jì)算不同的路徑,指定Region。
矩形區(qū)域長(zhǎng)寬不同,無(wú)法按照等比的方式計(jì)算長(zhǎng)寬方向上固定邊框?qū)挾鹊谋壤?;因此,?nèi)部的內(nèi)層圓角半徑也無(wú)法準(zhǔn)確計(jì)算,理論采用比例較小的比較合適
// 外層圓角矩形 Rectangle outRect = new Rectangle(0, 0, this.Width, this.Height); var outPath = outRect.GetRoundedRectPath(_radius); // 計(jì)算內(nèi)存圓角矩形,不嚴(yán)謹(jǐn) Rectangle rect = new Rectangle(_borderWidth, _borderWidth, this.Width - _borderWidth*2, this.Height - _borderWidth*2); var path = rect.GetRoundedRectPath(_radius); //this.Region = new Region(path); // 必須正確指定外層路徑outPath的全部區(qū)域,否則無(wú)法顯示完全填充的全部 this.Region = new Region(outPath);
然后分別填充兩個(gè)路徑:
using (SolidBrush borderB = new SolidBrush(_borderColor))
{
e.Graphics.FillPath(borderB, outPath);
}
using (SolidBrush b = new SolidBrush(baseColor))
{
e.Graphics.FillPath(b, path); // 填充路徑,而不是DrawPath
}
通過(guò)縮放實(shí)現(xiàn)正確的內(nèi)外兩層圓角矩形路徑
通過(guò)縮放實(shí)現(xiàn)正確Border的原理主要如下圖所示,長(zhǎng)寬縮小BorderSize大小,圓角半徑同樣縮小BorderSize,兩個(gè)內(nèi)外層圓角矩形的圓角在共同半徑下繪制圓角弧線(xiàn)。

Rectangle.Inflate()方法用于返回Rectangle結(jié)構(gòu)的放大副本,第二三個(gè)參數(shù)表示x、y方向放大或縮小的量。
var innerRect = Rectangle.Inflate(outRect, -borderSize, -borderSize);
則對(duì)應(yīng)得到內(nèi)層圓角路徑為:
GraphicsPath innerPath = innerRect.GetRoundedRectPath(borderRadius - borderSize)
從這里可以看出,需要保證borderSize小于borderRadius。
CDI+路徑的填充模式
GraphicsPath的填充模式FillMode默認(rèn)是FillMode.Alternate,所以替代填充可以實(shí)現(xiàn)內(nèi)外兩層的填充實(shí)現(xiàn)Border效果。
填充模式另一個(gè)選項(xiàng)為FillMode.Winding,可實(shí)現(xiàn)環(huán)繞效果,它們都是應(yīng)用在路徑發(fā)生重疊(overlap)時(shí),不同的填充效果。可具體測(cè)試不同效果
GraphicsPath gp = new GraphicsPath(FillMode.Winding);
直接繪制路徑作為邊框【推薦】**
通過(guò)DrawPath直接繪制邊框,注意寬度的處理。
// 繪制邊框
using (Pen pen = new Pen(_borderColor,_borderWidth*2))
{
e.Graphics.DrawPath(pen, path);
// 繪制路徑上,會(huì)有一半位于路徑外層,即Region外面,無(wú)法顯示出來(lái)。因此設(shè)置為雙borderWidth
}記得同時(shí)修改下文字繪制的區(qū)域范圍問(wèn)題,邊框?qū)挾日紦?jù)了區(qū)域的一部分。否則,在空間很小時(shí),文字會(huì)位于邊框上。

查看效果如下:

最好的處理不要使用
_borderWidth*2,而是使用原本大小,繪制的Path縮小在半個(gè)_borderWith范圍內(nèi)。比如:new Rectangle(rect.X + _borderWidth / 2, rect.Y + _borderWidth / 2, rect.Width - _borderWidth, rect.Height - _borderWidth)
在Paint事件中重繪控件為圓角
除了繼承控件(如上面Button)通過(guò)重寫(xiě)OnPaint方法,實(shí)現(xiàn)圓角的繪制,還可以直接在原生控件的Paint事件方法中,實(shí)現(xiàn)重繪控件為圓角。
后面文章中也介紹了,可以發(fā)現(xiàn)在
Paint事件方法中重繪比完全用戶(hù)繪制控件,圓角和各個(gè)部分有著更少的鋸齒,幾乎沒(méi)有,看起來(lái)相對(duì)更好一些,也因此較為推薦在Paint事件中實(shí)現(xiàn)圓角。【可以直接對(duì)比兩者效果】
比如,對(duì)于Button設(shè)置如下樣式,并添加Paint事件方法
button1.Paint += Button1_Paint; button1.FlatStyle = FlatStyle.Flat; button1.FlatAppearance.BorderSize = 0; button1.FlatAppearance.MouseDownBackColor = Color.Transparent; button1.FlatAppearance.MouseOverBackColor = Color.Transparent; button1.FlatAppearance.CheckedBackColor = Color.Transparent;
可實(shí)現(xiàn)效果如下:

具體實(shí)現(xiàn)和代碼參見(jiàn) Winform控件優(yōu)化Paint事件實(shí)現(xiàn)圓角組件及提取繪制圓角的方法
到此這篇關(guān)于Winform控件優(yōu)化之圓角按鈕1的文章就介紹到這了,更多相關(guān)Winform 圓角按鈕內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Unity實(shí)現(xiàn)已知落點(diǎn)和速度自動(dòng)計(jì)算發(fā)射角度
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)已知落點(diǎn)和速度自動(dòng)計(jì)算發(fā)射角度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
C#實(shí)現(xiàn)金額轉(zhuǎn)換成中文大寫(xiě)金額
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)金額轉(zhuǎn)換成中文大寫(xiě)金額,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08

