I made a Custom Control with the functions of a Button.
The problem with this controls is that when you hover over it, the color of the button changes: when this action is repeated quickly, it glitches out.
To make it more clear, I recorded a video.
Update 09.07.2020; I updated the code, perhaps this is because I do not add the image correctly, although judging by the appearance, the original, everything seems to be correct, the image works, but what happens next, you have already seen in the video.
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;
}
}
}
All right, try your Button with these modifications:
► SetStyle(ControlStyles.Opaque, true) is set. Combined with WS_EX_TRANSPARENT
, you have a fully transparent Control that supports TransparentColor. No need to also set ControlStyles.SupportsTransparentBackColor
, it's understood.
► Double buffering is explicitly disable, since we're painting the Control ourselves when required (ControlStyles.AllPaintingInWmPaint
, ControlStyles.UserPaint
and ControlStyles.ResizeRedraw
are all set).
► Moved the invalidating functions to the RepaintControl()
method, including invalidating the parent Control, if available, when in design-mode. At run-time, Invalidate()
is enough.
► Recalculated the GraphicsPath bounds when the BorderSize is changed, so the border is always painted inside the Control's bounds (to preserve anti-aliasing, which requires at least one pixel to render correctly. When too close to the Control's bounds, the rendering is incomplete).
► base.OnPaint(e)
is still called, but you only need it if you want to allow User customization of the Control. Otherwise, you can remove it (or place it after your code, at the bottom of the OnPaint
method).
► Added, as example, Min/Max checkes on some properties:
(0, 6)
with Math.Max(Math.Min(value, 6), 0)
(1, 44)
with Math.Max(Math.Min(value, 44), 1)
: the GraphicsPath cannot add arcs with a null angle and, above 45
, it'll generate re-entrant curves.I don't see any flickering at run-time.
Note that this is tested with .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;
}
}
User contributions licensed under CC BY-SA 3.0