【问题标题】:How to make a custom Button responsiveness smoother?如何使自定义按钮响应更流畅?
【发布时间】:2020-07-06 13:06:48
【问题描述】:

我用按钮的功能制作了一个自定义控件。
此控件的问题在于,当您将鼠标悬停在它上面时,按钮的颜色会发生变化:当快速重复此操作时,它会出现故障。
为了更清楚,我录制了一个视频。

更新 09.07.2020; 我更新了代码,也许这是因为我没有正确添加图像,虽然从外观来看,原来的,一切似乎都是正确的,图像工作,但接下来会发生什么,你已经在视频中看到了。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Test_Project.SupportClass
{
    [DesignerCategory("Code")]
    public class button_check : Control
    {
        private int m_BorderSize = 2;
        private int m_ButtonRoundRadius = 15;

        private bool IsHighlighted = false;
        private bool IsPressed = false;

        private Image _image;
        private ImageLayout LautsCallBack { get; set; }

        public button_check()
        {
            SetStyle(ControlStyles.Opaque |
                     ControlStyles.AllPaintingInWmPaint |
                     ControlStyles.ResizeRedraw |
                     ControlStyles.UserPaint, true);
            // To be explicit...
            SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
            this.DoubleBuffered = false;
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            Size = new Size(100, 40);
            BackColor = Color.Tomato;
            BackColor2 = Color.Tomato;
            ButtonBorderColor = Color.Black;
            ButtonHighlightColor = Color.Orange;
            ButtonHighlightColor2 = Color.OrangeRed;
            ButtonHighlightForeColor = Color.Black;

            ButtonPressedColor = Color.Red;
            ButtonPressedColor2 = Color.Maroon;
            ButtonPressedForeColor = Color.White;
        }
        public ImageLayout LayoutImage
        {
            get
            {
                return LautsCallBack;
            }
            set
            {
                LautsCallBack = value;
                RecreateHandle();
            }
        }
        public Image ImageButtom
        {
            get
            {
                return _image;
            }
            set
            {
                _image = value;
                RecreateHandle();
            }
        }
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
                return cp;
            }
        }

        // Invalidate(rect) in Design-Mode to refresh the view
        public int BorderSize
        {
            get => m_BorderSize;
            set
            {
                m_BorderSize = Math.Max(Math.Min(value, 6), 1);
                RepaintControl();
            }
        }

        // Set Max = 44, Min = 1 to avoid quirks and exceptions
        public int ButtonRoundRadius
        {
            get => m_ButtonRoundRadius;
            set
            {
                m_ButtonRoundRadius = Math.Max(Math.Min(value, 44), 1);
                RepaintControl();
            }
        }

        public override string Text
        {
            get => base.Text;
            set
            {
                base.Text = value;
                RepaintControl();
            }
        }

        // You should Invalidate the Parent also when these change
        public Color BorderColor { get; set; } = Color.Tomato;
        public Color BackColor2 { get; set; } = Color.Tomato;

        public Color ButtonBorderColor { get; set; }
        public Color ButtonHighlightColor { get; set; }
        public Color ButtonHighlightColor2 { get; set; }
        public Color ButtonHighlightForeColor { get; set; }
        public Color ButtonPressedColor { get; set; }
        public Color ButtonPressedColor2 { get; set; }
        public Color ButtonPressedForeColor { get; set; }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.CompositingQuality = CompositingQuality.HighQuality;

            var foreColor = IsPressed ? ButtonPressedForeColor : IsHighlighted ? ButtonHighlightForeColor : ForeColor;
            var backColor = IsPressed ? ButtonPressedColor : IsHighlighted ? ButtonHighlightColor : BackColor;
            var backColor2 = IsPressed ? ButtonPressedColor2 : IsHighlighted ? ButtonHighlightColor2 : BackColor2;
            var rect = Path.GetBounds();
            using (var pen = new Pen(ButtonBorderColor, m_BorderSize))
            using (var pathBrush = new LinearGradientBrush(rect, backColor, backColor2, LinearGradientMode.Vertical))
            using (var textBrush = new SolidBrush(foreColor))
            using (var sf = new StringFormat())
            {
                sf.Alignment = StringAlignment.Center;
                sf.LineAlignment = StringAlignment.Center;

                e.Graphics.FillPath(pathBrush, Path);
                if (m_BorderSize > 0) e.Graphics.DrawPath(pen, Path);

                if (_image != null)
                {
                    switch (LayoutImage)
                    {
                        case ImageLayout.Stretch:
                            e.Graphics.DrawImage(_image, this.ClientRectangle);
                            break;
                        case ImageLayout.Center:
                            int left = (this.ClientSize.Width - _image.Width) / 2;
                            int top = (this.ClientSize.Height - _image.Height) / 2;
                            e.Graphics.DrawImage(_image, left, top);
                            break;
                        case ImageLayout.Tile:
                            using (var texture = new TextureBrush(_image))
                            {
                                e.Graphics.FillRectangle(texture, this.ClientRectangle);
                            }
                            break;
                        case ImageLayout.Zoom:
                            double xr = (double)this.ClientSize.Width / _image.Width;
                            double yr = (double)this.ClientSize.Height / _image.Height;
                            if (xr > yr)
                            {
                                rect.Width = (int)(_image.Width * yr);
                                rect.X = (this.ClientSize.Width - rect.Width) / 2;
                            }
                            else
                            {
                                rect.Height = (int)(_image.Height * xr);
                                rect.Y = (this.ClientSize.Height - rect.Height) / 2;
                            }
                            e.Graphics.DrawImage(_image, rect);
                            break;
                    }
                }


                rect.Inflate(-4, -4);
                e.Graphics.DrawString(Text, Font, textBrush, rect, sf);
            }
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            IsHighlighted = true;
            RepaintControl();
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            IsHighlighted = false;
            IsPressed = false;
            RepaintControl();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            IsPressed = true;
            RepaintControl();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            IsPressed = false;
            RepaintControl();
        }

        private void RepaintControl()
        {
            Parent?.Invalidate(this.Bounds, true);
            Invalidate();
        }

        private GraphicsPath Path
        {
            get
            {
                var rect = ClientRectangle;
                int scaleOnBorder = -((m_BorderSize / 2) + 2);
                rect.Inflate(scaleOnBorder, scaleOnBorder);
                return GetRoundedRectangle(rect, m_ButtonRoundRadius);
            }
        }

        private GraphicsPath GetRoundedRectangle(Rectangle rect, int d)
        {
            var gp = new GraphicsPath();
            gp.StartFigure();
            gp.AddArc(rect.X, rect.Y, d, d, 180, 90);
            gp.AddArc(rect.X + rect.Width - d, rect.Y, d, d, 270, 90);
            gp.AddArc(rect.X + rect.Width - d, rect.Y + rect.Height - d, d, d, 0, 90);
            gp.AddArc(rect.X, rect.Y + rect.Height - d, d, d, 90, 90);
            gp.CloseFigure();
            return gp;
        }
    }
}

【问题讨论】:

  • 绘制方式略有不同,通常提供稳定的外部Borders抗锯齿,见:How to avoid visual artifacts of colored border of zoomable UserControl with rounded corners?
  • @Jimi 有些我不明白,我更改了Parent.Invalidate(Bounds, true); 并没有发生任何事情。您可以使用我的代码示例来展示它在最终结果中的样子,我将非常感谢你
  • 只需要进行该更改以防止控件过度失效。在设计器中,它是必需的,否则当您更改某些内容时不会重新绘制自定义控件。在运行时,它是多余的,会导致闪烁。你需要所有这些改变。我看看我是否有时间发布修改后的版本。 -- 您需要添加书面说明,说明预期结果是什么以及您实际得到什么。 书面描述,假设视频实际上可以描述问题所在,任何人都不需要观看视频来理解这一切。
  • @Jimi 好吧,等你发布新版本后,我会准确描述发生了什么变化,什么没有发生变化
  • @Jimi 你能帮我去掉按钮中的这些亮点吗?

标签: c# winforms button gdi+


【解决方案1】:

好的,试试你的 Button 修改:

► SetStyle(ControlStyles.Opaque, true) 已设置。结合WS_EX_TRANSPARENT,您将拥有一个支持TransparentColor 的完全透明控件。不用也设置ControlStyles.SupportsTransparentBackColor,明白了。

► 明确禁用双缓冲,因为我们在需要时自己绘制控件(ControlStyles.AllPaintingInWmPaintControlStyles.UserPaintControlStyles.ResizeRedraw 均已设置)。

► 将无效函数移至RepaintControl() 方法,包括在设计模式下使父控件(如果可用)无效。在运行时,Invalidate() 就足够了。

► 当 BorderSize 改变时重新计算 GraphicsPath 的边界,所以边框总是在 Control 的边界内绘制(以保持抗锯齿,这需要至少一个像素才能正确渲染。当太接近 Control 的边界时,渲染不完整)。

base.OnPaint(e) 仍会被调用,但仅当您希望允许用户自定义控件时才需要它。否则,您可以将其删除(或将其放在代码之后,位于 OnPaint 方法的底部)。

► 例如,添加了对某些属性的 Min/Max 检查:

  • BorderSize 限制在(0, 6)Math.Max(Math.Min(value, 6), 0) 的范围内
  • ButtonRoundRadius 被限制在(1, 44)Math.Max(Math.Min(value, 44), 1) 的范围内:GraphicsPath 无法添加具有零角的弧线,并且在45 之上,它将生成重入曲线。

我在运行时没有看到任何闪烁。
请注意,这是使用 .Net Framework 4.8 测试的


using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

[DesignerCategory("Code")]
public class l1_Button : Control
{
    private int m_BorderSize = 2;
    private int m_ButtonRoundRadius = 15; 

    private bool IsHighlighted = false;
    private bool IsPressed = false;

    public l1_Button()
    {
        SetStyle(ControlStyles.Opaque | 
                 ControlStyles.AllPaintingInWmPaint | 
                 ControlStyles.ResizeRedraw | 
                 ControlStyles.UserPaint, true);
        // To be explicit...
        SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
        this.DoubleBuffered = false;
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        Size = new Size(100, 40);
        BackColor = Color.Tomato;
        BackColor2 = Color.Tomato;
        ButtonBorderColor = Color.Black;
        ButtonHighlightColor = Color.Orange;
        ButtonHighlightColor2 = Color.OrangeRed;
        ButtonHighlightForeColor = Color.Black;

        ButtonPressedColor = Color.Red;
        ButtonPressedColor2 = Color.Maroon;
        ButtonPressedForeColor = Color.White;
    }

    protected override CreateParams CreateParams {
        get {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
            return cp;
        }
    }

    // Invalidate(rect) in Design-Mode to refresh the view
    public int BorderSize {
        get => m_BorderSize;
        set {
            m_BorderSize = Math.Max(Math.Min(value, 6), 1);
            RepaintControl();
        }
    }

    // Set Max = 44, Min = 1 to avoid quirks and exceptions
    public int ButtonRoundRadius {
        get => m_ButtonRoundRadius;
        set {
            m_ButtonRoundRadius = Math.Max(Math.Min(value, 44), 1);
            RepaintControl();
        }
    }

    public override string Text {
        get => base.Text;
        set {
            base.Text = value;
            RepaintControl();
        }
    }

    // You should Invalidate the Parent also when these change
    public Color BorderColor { get; set; } = Color.Tomato;
    public Color BackColor2 { get; set; } = Color.Tomato;

    public Color ButtonBorderColor { get; set; }
    public Color ButtonHighlightColor { get; set; }
    public Color ButtonHighlightColor2 { get; set; }
    public Color ButtonHighlightForeColor { get; set; }
    public Color ButtonPressedColor { get; set; }
    public Color ButtonPressedColor2 { get; set; }
    public Color ButtonPressedForeColor { get; set; }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.CompositingQuality = CompositingQuality.HighQuality;

        var foreColor = IsPressed ? ButtonPressedForeColor : IsHighlighted ? ButtonHighlightForeColor : ForeColor;
        var backColor = IsPressed ? ButtonPressedColor : IsHighlighted ? ButtonHighlightColor : BackColor;
        var backColor2 = IsPressed ? ButtonPressedColor2 : IsHighlighted ? ButtonHighlightColor2 : BackColor2;
        var rect = Path.GetBounds();

        using (var pen = new Pen(ButtonBorderColor, m_BorderSize))
        using (var pathBrush = new LinearGradientBrush(rect, backColor, backColor2, LinearGradientMode.Vertical))
        using (var textBrush = new SolidBrush(foreColor))
        using (var sf = new StringFormat()) {
            sf.Alignment = StringAlignment.Center;
            sf.LineAlignment = StringAlignment.Center;

            e.Graphics.FillPath(pathBrush, Path);
            if (m_BorderSize > 0) e.Graphics.DrawPath(pen, Path);

            rect.Inflate(-4, -4);
            e.Graphics.DrawString(Text, Font, textBrush, rect, sf);
        }
    }

    protected override void OnMouseEnter(EventArgs e)
    {
        base.OnMouseEnter(e);
        IsHighlighted = true;
        RepaintControl();
    }

    protected override void OnMouseLeave(EventArgs e)
    {
        base.OnMouseLeave(e);
        IsHighlighted = false;
        IsPressed = false;
        RepaintControl();
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        IsPressed = true;
        RepaintControl();
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        IsPressed = false;
        RepaintControl();
    }

    private void RepaintControl() {
        Parent?.Invalidate(this.Bounds, true);
        Invalidate();
    }

    private GraphicsPath Path {
        get {
            var rect = ClientRectangle;
            int scaleOnBorder = -((m_BorderSize / 2) + 2);
            rect.Inflate(scaleOnBorder, scaleOnBorder);
            return GetRoundedRectangle(rect, m_ButtonRoundRadius);
        }
    }

    private GraphicsPath GetRoundedRectangle(Rectangle rect, int d)
    {
        var gp = new GraphicsPath();
        gp.StartFigure();
            gp.AddArc(rect.X, rect.Y, d, d, 180, 90);
            gp.AddArc(rect.X + rect.Width - d, rect.Y, d, d, 270, 90);
            gp.AddArc(rect.X + rect.Width - d, rect.Y + rect.Height - d, d, d, 0, 90);
            gp.AddArc(rect.X, rect.Y + rect.Height - d, d, d, 90, 90);
        gp.CloseFigure();
        return gp;
    }
}

【讨论】:

  • 它工作正常,没有注意到闪烁,只是它坏了,我点击按钮,默认颜色是红色,当你点击按钮时,它的颜色没有改变......谢谢你做了这么好的工作
  • @Сергей Маерович 没有损坏:在OnMouseDown 中,缺少对RepaintControl(); 的调用(忘记添加回来)。现已更正。
  • 如果你把m_BorderSize 0 和多次恢复顺序,并从我们的按钮上拿鼠标,结果是zabaletta,如果你选择形象地说原色78; 184; 206,但是除了ButtonBorderColor,其他颜色都没有变化,他设置的是Transparent颜色,如果鼠标来回驱动很多时间,你会看到背面是橙色的,如果你加上即使按下按钮@Jimi 也可以更清楚地看到图片的按钮
  • 我不太明白你在说什么(zebaletta 是什么?还有primary color?是哪一种?)。无论如何,据我所知,尝试将using (var pen = new Pen(ButtonBorderColor, m_BorderSize)) 更改为using (var pen = new Pen(m_BorderSize == 0 ? Color.Transparent : ButtonBorderColor, m_BorderSize))。但是,您是否使用(全部)此代码,或者您只是在这里和那里更改了一些东西?我对控件的背景没有任何问题。不管我把它加到哪里。我在这里提出的编辑,最终可以在边框设置为 0 时消除空白背景。
  • 我认为它工作得很好。但是,一旦我尝试制作图像,一切都会立即崩溃并停止工作@Jimi
猜你喜欢
  • 2018-10-30
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-22
  • 2019-08-23
相关资源
最近更新 更多