如何使自定义按钮响应更流畅?


我用 Button.

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

更新 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;
}
}
}

好的,试试你的按钮,进行以下修改:

► 设置了 SetStyle(ControlStyles.Opaque, true(。结合WS_EX_TRANSPARENT,您有一个支持 TransparentColor 的完全透明控件。无需也设置ControlStyles.SupportsTransparentBackColor,这是可以理解的。

► 双缓冲是显式禁用的,因为我们在需要时自己绘制控件(ControlStyles.AllPaintingInWmPaintControlStyles.UserPaintControlStyles.ResizeRedraw都已设置(。

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

► 更改 BorderSize 时重新计算图形路径边界,因此边框始终绘制在控件边界内(以保留抗锯齿,这需要至少一个像素才能正确呈现。当太接近控件的边界时,呈现不完整(。

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

► 添加了对某些属性的最小/最大检查,例如:

  • 边框大小的范围(0, 6)Math.Max(Math.Min(value, 6), 0)
  • ButtonRoundRadius 仅限于(1, 44)Math.Max(Math.Min(value, 44), 1)的范围:图形路径不能添加具有空角度的弧,并且在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;
}
}

最新更新