Winform控件优化之圆角按钮1

目录
  • 前言
  • 圆角按钮实现【重写OnPaint实现圆角绘制】
    • 代码主要关键点或思路、优化
    • 使用圆角按钮
      • 利用填充内外两层圆角矩形路径形成Border
      • 通过缩放实现正确的内外两层圆角矩形路径
      • CDI+路径的填充模式
      • 直接绘制路径作为边框【推荐】**
  • 可以改进和实现的
  • 在Paint事件中重绘控件为圆角

前言

Windows 11下所有控件已经默认采用圆角,其效果更好、相对有着更好的优化,只是这是默认的行为,无法进一步自定义。

圆角按钮实现【重写OnPaint实现圆角绘制】

控件自定义绘制的关键在于:重写OnPaint方法,其参数提供了用于GDI+绘图的Graphics对象,由此实现绘制需要的图形效果。

为了更好的显示绘制的图形,通常必须设置控件背景透明(图形外的控件区域透明,便于正确显示绘制的图形),虽然Winform的背景透明有着一定缺陷,但总体来说这是必须的。

此外,Paint事件方法中,也可以进行控件的绘制(重绘),与继承重写OnPaint没有本质区别。

代码主要关键点或思路、优化

  • 半径Radius、Color、TextAlign属性的赋值,都调用 Invalidate() 方法使控件画面无效并重绘控件。
  • 添加文本位置的属性TextAlign,并在属性赋值时调用Invalidate()重绘控件,实现修改文本位置的布局。
  • 有一个bug问题,就是在点击按钮鼠标抬起方法OnMouseUp中,实现了修改鼠标状态,对应的背景颜色值也修改了,控件重绘时也修改了颜色(debug),但绝大多数情况下,鼠标抬起背景颜色未变化。原因在重写的OnMouseUp(MouseEventArgs e)中先调用的控件基类base.OnMouseUp(e);,后修改的状态颜色,将base.OnMouseUp(e);改为最后调用即可。
  • 修改和优化圆角部分圆弧的绘制,原实现圆弧半径处理不合理。
  • 【尽可能高质量绘制】图形部分的几个模式必须指定,怎么明显看出显示的文本、边框等不清晰、锯齿验证等问题。
  • 其他一些小修改和调整,比如抗锯齿、高质量绘图、使用控件字体、文本颜色默认白色、设置字体方向等
  • Radius 属性修改边角半径大小(即圆角的大小、圆弧的大小)
  • NormalColor、HoverColor、PressedColor 属性设置按钮正常状态、鼠标悬停、鼠标按下时的背景颜色,通常设置为一致即可。
  • 指定Size的Width、Height的大小相同,Radius为正方向边长的一半,可以实现圆形按钮。

StringFormat 对象,可以提供对字符串文本的颜色、布局、方向等各种格式的设置,用于渲染文本效果。

Control.DesignMode属性可以判断当前代码的执行环境是否是设计器模式,在某些条件下可以通过此判断,决定是否在设计器下执行某段代码(如果不涉及样式效果,就可以不需要在设计器下执行)

使用圆角按钮

编译后,直接从工具箱中拖动RoundButtons圆角按钮控件到窗体即可。

代码如下,关键部分都有相关注释,可以直接过一遍代码:

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;
                // 使控件的整个画面无效并重绘控件
                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)//鼠标进入时
        {
            ControlState = ControlState.Hover;//Hover
            base.OnMouseEnter(e);
        }
        protected override void OnMouseLeave(EventArgs e)//鼠标离开
        {
            ControlState = ControlState.Normal;//正常
            base.OnMouseLeave(e);
        }
        protected override void OnMouseDown(MouseEventArgs e)//鼠标按下
        {
            if (e.Button == MouseButtons.Left && e.Clicks == 1)//鼠标左键且点击次数为1
            {
                ControlState = ControlState.Pressed;//按下的状态
            }
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)//鼠标弹起
        {
            if (e.Button == MouseButtons.Left && e.Clicks == 1)
            {
                if (ClientRectangle.Contains(e.Location))//控件区域包含鼠标的位置
                {
                    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 |  //控件自行绘制,而不使用操作系统的绘制
             ControlStyles.AllPaintingInWmPaint | //忽略背景擦除的Windows消息,减少闪烁,只有UserPaint设为true时才能使用。
             ControlStyles.OptimizedDoubleBuffer |//在缓冲区上绘制,不直接绘制到屏幕上,减少闪烁。
             ControlStyles.ResizeRedraw | //控件大小发生变化时,重绘。
             ControlStyles.SupportsTransparentBackColor, //支持透明背景颜色
             true);
        }

        //重写OnPaint
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            base.OnPaint(e);
            // base.OnPaintBackground(e);

            // 尽可能高质量绘制
            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))
                {
                    // 文本布局对象
                    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>
        /// 根据矩形区域rect,计算呈现radius圆角的Graphics路径
        /// </summary>
        /// <param name="rect"></param>
        /// <param name="radius"></param>
        /// <returns></returns>
        private GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
        {
            #region 正确绘制圆角矩形区域
            int R = radius*2;
            Rectangle arcRect = new Rectangle(rect.Location, new Size(R, R));
            GraphicsPath path = new GraphicsPath();
            // 左上圆弧 左手坐标系,顺时针为正 从180开始,转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);
        }
    }
}

参考 C# Winform实现圆角无锯齿按钮

可以改进和实现的

  • 添加Border,实现Border颜色和宽度的指定(目前的一个思路时利用路径在外层填充一个圆角矩形,在内层再填充一个圆角矩形,形成有Border的效果;另一个思路时,画路径时,绘制内层路径和圆环路径,Border部分是一个圆角的圆环路径,而后分别填充颜色;还有就是绘制路径线条,线条作为Border。)
  • 通过百分比实现圆角
  • 完全扩展Button,通过标志位启动圆角和修改圆角,做到圆角和普通Button共存。
  • 修改使用RectangleF对象,使用浮点数绘制矩形和路径
  • 圆角半径radius指定为0的处理

Rectangle.Round(RectangleF) 将RectangleF对象转换为Rectangle,通过舍入最近的数。

利用填充内外两层圆角矩形路径形成Border

【有着致命缺陷(随后介绍了正确处理的方案)】

控件的Region区域一定指定,并且要包含全部的Graphics绘制的内容,否则显示不全,包含在Region内才能全部显示出来。

Region区域指定的是控件的区域,表示的是控件的范围

如下,通过Border大小 _borderWidth 计算不同的路径,指定Region。

矩形区域长宽不同,无法按照等比的方式计算长宽方向上固定边框宽度的比例;因此,内部的内层圆角半径也无法准确计算,理论采用比例较小的比较合适

// 外层圆角矩形
Rectangle outRect = new Rectangle(0, 0, this.Width, this.Height);
var outPath = outRect.GetRoundedRectPath(_radius);

// 计算内存圆角矩形,不严谨
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的全部区域,否则无法显示完全填充的全部
this.Region = new Region(outPath);

然后分别填充两个路径:

using (SolidBrush borderB = new SolidBrush(_borderColor))
{
    e.Graphics.FillPath(borderB, outPath);
}

using (SolidBrush b = new SolidBrush(baseColor))
{
    e.Graphics.FillPath(b, path); // 填充路径,而不是DrawPath
}

通过缩放实现正确的内外两层圆角矩形路径

通过缩放实现正确Border的原理主要如下图所示,长宽缩小BorderSize大小,圆角半径同样缩小BorderSize,两个内外层圆角矩形的圆角在共同半径下绘制圆角弧线。

Rectangle.Inflate()方法用于返回Rectangle结构的放大副本,第二三个参数表示x、y方向放大或缩小的量。

var innerRect = Rectangle.Inflate(outRect, -borderSize, -borderSize);

则对应得到内层圆角路径为:

GraphicsPath innerPath = innerRect.GetRoundedRectPath(borderRadius - borderSize)

从这里可以看出,需要保证borderSize小于borderRadius

CDI+路径的填充模式

GraphicsPath的填充模式FillMode默认是FillMode.Alternate,所以替代填充可以实现内外两层的填充实现Border效果。

填充模式另一个选项为FillMode.Winding,可实现环绕效果,它们都是应用在路径发生重叠(overlap)时,不同的填充效果。可具体测试不同效果

GraphicsPath gp = new GraphicsPath(FillMode.Winding);

直接绘制路径作为边框【推荐】**

通过DrawPath直接绘制边框,注意宽度的处理。

// 绘制边框
using (Pen pen = new Pen(_borderColor,_borderWidth*2))
{
    e.Graphics.DrawPath(pen, path);
    // 绘制路径上,会有一半位于路径外层,即Region外面,无法显示出来。因此设置为双borderWidth
}

记得同时修改下文字绘制的区域范围问题,边框宽度占据了区域的一部分。否则,在空间很小时,文字会位于边框上。

查看效果如下:

最好的处理不要使用_borderWidth*2,而是使用原本大小,绘制的Path缩小在半个_borderWith范围内。比如:new Rectangle(rect.X + _borderWidth / 2, rect.Y + _borderWidth / 2, rect.Width - _borderWidth, rect.Height - _borderWidth)

在Paint事件中重绘控件为圆角

除了继承控件(如上面Button)通过重写OnPaint方法,实现圆角的绘制,还可以直接在原生控件的Paint事件方法中,实现重绘控件为圆角。

后面文章中也介绍了,可以发现在Paint事件方法中重绘比完全用户绘制控件,圆角和各个部分有着更少的锯齿,几乎没有,看起来相对更好一些,也因此较为推荐在Paint事件中实现圆角。

【可以直接对比两者效果】

比如,对于Button设置如下样式,并添加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;

可实现效果如下:

具体实现和代码参见 Winform控件优化Paint事件实现圆角组件及提取绘制圆角的方法

到此这篇关于Winform控件优化之圆角按钮1的文章就介绍到这了,更多相关Winform 圆角按钮内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C# Winform实现圆角无锯齿按钮

    本文实例为大家分享了C# Winform实现圆角无锯齿按钮的具体代码,供大家参考,具体内容如下 发现用Winform做一个圆角按钮遇到麻烦,主要是锯齿问题,后面想了想办法解决问题了. 主要方法是按钮的区域通过Region指定,但按钮需要自己画,否则怎么搞都出现锯齿,网上有朋友提供一个漂亮的方案,可是代码不完整无法使用,我的解决方案现在分享如下: 代码: public enum ControlState { Hover , Normal, Pressed }     public class Ro

  • Winform控件优化之圆角按钮2

    目录 前言 圆角按钮实现的进一步优化 主要功能[圆角方面] OnPaint方法中不要使用e.ClipRectangle 重新创建Region的锯齿问题和优势 代码具体实现 测试扩展按钮控件ButtonPro TextRenderer.DrawText绘制文本 使用StringFormat.GenericTypographic 仅仅重写OnPaintBackground 使用TextRenderer.DrawText绘制文本 前言 接上一篇Winform控件优化之圆角按钮1继续介绍圆角按钮的实现和

  • C# Winform实现圆角无锯齿按钮

    前言: 发现用Winform做一个圆角按钮遇到麻烦,主要是锯齿问题,后面想了想办法解决问题了. 主要方法是按钮的区域通过Region指定,但按钮需要自己画,否则怎么搞都出现锯齿,网上有朋友提供一个漂亮的方案,可是代码不完整无法使用,我的解决方案现在分享如下: public enum ControlState { Hover , Normal, Pressed } public class RoundButton : Button { private int radius;//半径 private

  • Winform控件优化Paint事件实现圆角组件及提取绘制圆角的方法

    目录 前言 Paint事件中实现圆角控件 提取绘制圆角矩形和旁边小尖角的代码为扩展方法 介绍 扩展方法 测试尖角的显示位置 重绘控件后文本的处理 同时重绘文本 通过添加Label控件实现对文本的处理[有尖角时需要额外处理] Paint事件中绘制圆角的优点 窗体失去焦点时按钮外观边框问题 Paint事件中绘制控件的问题[不要使用(事件)参数e.ClipRectangle] 前言 Windows 11下所有控件已经默认采用圆角,其效果更好.相对有着更好的优化,只是这是默认的行为,无法进一步自定义.

  • Winform控件优化之圆角按钮1

    目录 前言 圆角按钮实现[重写OnPaint实现圆角绘制] 代码主要关键点或思路.优化 使用圆角按钮 利用填充内外两层圆角矩形路径形成Border 通过缩放实现正确的内外两层圆角矩形路径 CDI+路径的填充模式 直接绘制路径作为边框[推荐]** 可以改进和实现的 在Paint事件中重绘控件为圆角 前言 Windows 11下所有控件已经默认采用圆角,其效果更好.相对有着更好的优化,只是这是默认的行为,无法进一步自定义. 圆角按钮实现[重写OnPaint实现圆角绘制] 控件自定义绘制的关键在于:重

  • C#开发Winform控件之打开文件对话框OpenFileDialog类

    OpenFileDialog类提供了用户打开文件的功能,它有如下属性: 属性 InitialDirectory:设置对话框的初始目录. Filter: 要在对话框中显示的文件筛选器,例如,"文本文件(*.txt)|*.txt|所有文件(*.*)||*.*". FilterIndex: 在对话框中选择的文件筛选器的索引,如果选第一项就设为1. RestoreDirectory: 控制对话框在关闭之前是否恢复当前目录. FileName:第一个在对话框中显示的文件或最后一个选取的文件. T

  • C#的winform控件命名规范

    本文详细讲述了C#的winform控件命名规范.分享给大家供大家参考.具体如下: 注:这里用红字标记的部分表示有重复出现,括号内为替代表示方案 1.标准控件 序号 控件类型简写 控件类型 1 btn Button 2 chk CheckBox 3 ckl CheckedListBox 4 cmb ComboBox 5 dtp DateTimePicker 6 lbl Label 7 llb LinkLabel 8 lst ListBox 9 lvw ListView 10 mtx MaskedT

  • C#中WinForm控件的拖动和缩放的实现代码

    C# WinForm控件的拖动和缩放是个很有用的功能.实现起来其实很简单的,主要是设计控件的MouseDown.MouseLeave.MouseMove事件,下面的几个步骤将逐步实现C# WinForm控件的拖动和缩放的功能. 1.定义一个枚举类型,描述光标状态 private enum EnumMousePointPosition { MouseSizeNone = 0, //'无 MouseSizeRight = 1, //'拉伸右边框 MouseSizeLeft = 2, //'拉伸左边框

  • C# WinForm控件对透明图片重叠时出现图片不透明的简单解决方法

    本文实例讲述了C# WinForm控件对透明图片重叠时出现图片不透明的简单解决方法.分享给大家供大家参考,具体如下: 在Winform中如果将一个透明图片放在窗体上能正常显示透明,但是如果将该图片放在另一个控件上会导致不能显示透明效果. 解决这种情况,可以采取在控件上使用GDI+绘画出透明图片. 这里我们就以一个pictureBox2控件上面重叠一张png透明图片为例: 我们只需要给pictureBox2控件添加Paint事件,然后对png透明图片进行绘制即可,代码如下: private voi

  • Winform控件SaveFileDialog用于保存文件

    SaveFileDialog用于保存文件,供大家参考,具体内容如下 1.新建Winform窗体应用程序,命名为SaveFileDialogDemo. 2.在界面上添加一个按钮的控件(用于打开保存文件对话框),添加文本控件,用于输入要保存的内容. 3.后台代码实现: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing;

  • c#中多线程访问winform控件的若干问题小结

    我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题.然而我们并不能用传统方法来解决这个问题,下面我将详细的介绍. 首先来看传统方法: 复制代码 代码如下: public partial class Form1 : Form     {        public Form1()        {            InitializeComponent();        } private void Form1_Load(object sender,

  • 深入分析C#中WinForm控件之Dock顺序调整的详解

    最近被.net winform中的控件布局搞困惑了,由于控件都是使用Dock方式的,操作起来也是比较方便,如果最大化,窗口大小调整等,都可以随着窗口大小的变化而变化.但问题是,.net winform的dock方式是根据先添加的控件顺序进行优先级Dock排列的,假设我想目前有三个控件A.B.C分别以 Top.Bottom以及Fill方式填充窗口即窗口看起来应该是这样的:---------------------A---------------------C--------------------

随机推荐