Winform控件優(yōu)化之圓角按鈕1
前言
Windows 11下所有控件已經(jīng)默認(rèn)采用圓角,其效果更好、相對(duì)有著更好的優(yōu)化,只是這是默認(rèn)的行為,無(wú)法進(jìn)一步自定義。
圓角按鈕實(shí)現(xiàn)【重寫OnPaint實(shí)現(xiàn)圓角繪制】
控件自定義繪制的關(guān)鍵在于:重寫OnPaint
方法,其參數(shù)提供了用于GDI+繪圖的Graphics
對(duì)象,由此實(shí)現(xiàn)繪制需要的圖形效果。
為了更好的顯示繪制的圖形,通常必須設(shè)置控件背景透明(圖形外的控件區(qū)域透明,便于正確顯示繪制的圖形),雖然Winform的背景透明有著一定缺陷,但總體來(lái)說(shuō)這是必須的。
此外,Paint
事件方法中,也可以進(jìn)行控件的繪制(重繪),與繼承重寫OnPaint
沒(méi)有本質(zhì)區(qū)別。
代碼主要關(guān)鍵點(diǎn)或思路、優(yōu)化
- 半徑Radius、Color、TextAlign屬性的賦值,都調(diào)用
Invalidate()
方法使控件畫面無(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)抬起背景顏色未變化。原因在重寫的OnMouseUp(MouseEventArgs e)
中先調(diào)用的控件基類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 屬性修改邊角半徑大?。磮A角的大小、圓弧的大小)
- 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)注釋,可以直接過(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è)畫面無(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)離開 { 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); } //重寫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開始,轉(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í),畫路徑時(shí),繪制內(nèi)層路徑和圓環(huán)路徑,Border部分是一個(gè)圓角的圓環(huá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)外層圓角矩形的圓角在共同半徑下繪制圓角弧線。
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í),不同的填充效果??删唧w測(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ò)重寫OnPaint
方法,實(shí)現(xiàn)圓角的繪制,還可以直接在原生控件的Paint
事件方法中,實(shí)現(xiàn)重繪控件為圓角。
后面文章中也介紹了,可以發(fā)現(xiàn)在
Paint
事件方法中重繪比完全用戶繪制控件,圓角和各個(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)和代碼參見 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)文章
C#實(shí)現(xiàn)鐘表程序設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)鐘表程序設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06c# WPF實(shí)現(xiàn)Windows資源管理器(附源碼)
這篇文章主要介紹了c# WPF實(shí)現(xiàn)Windows資源管理器的示例(附源碼),幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03C#實(shí)現(xiàn)把科學(xué)計(jì)數(shù)法(E)轉(zhuǎn)化為正常數(shù)字值
這篇文章主要介紹了C#實(shí)現(xiàn)把科學(xué)計(jì)數(shù)法(E)轉(zhuǎn)化為正常數(shù)字值,本文直接給出代碼實(shí)例,需要的朋友可以參考下2015-06-06C# TreeView從數(shù)據(jù)庫(kù)綁定數(shù)據(jù)的示例
這篇文章主要介紹了C# TreeView從數(shù)據(jù)庫(kù)綁定數(shù)據(jù)的示例,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03C# TextBox控件實(shí)現(xiàn)只能輸入數(shù)字的方法
這篇文章主要介紹了C# TextBox控件實(shí)現(xiàn)只能輸入數(shù)字的方法,本文使用TextBox的keypress事件實(shí)現(xiàn)這個(gè)需求,需要的朋友可以參考下2015-06-06簡(jiǎn)單掌握Windows中C#啟動(dòng)外部程序進(jìn)程的方法
這篇文章主要介紹了Windows中C#啟動(dòng)外部程序進(jìn)程的方法,例子中同時(shí)包括了進(jìn)程關(guān)閉的方法,需要的朋友可以參考下2016-03-03