diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs
index a85dd3ed4cc..db79aee1946 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeAdapter.cs
@@ -53,6 +53,7 @@ private Color GetButtonBackColor(PushButtonState state)
{
Color backColor;
+ // Set the initial back color.
if (Control.BackColor != Forms.Control.DefaultBackColor)
{
backColor = Control.BackColor;
@@ -67,6 +68,21 @@ private Color GetButtonBackColor(PushButtonState state)
backColor = ButtonDarkModeRenderer.GetBackgroundColor(state, Control.IsDefault);
}
+ // Override the back color for FlatStyle buttons if specific colors are set.
+ if (Control.FlatStyle == FlatStyle.Flat)
+ {
+ if (state == PushButtonState.Pressed && !Control.FlatAppearance.MouseDownBackColor.IsEmpty)
+ {
+ backColor = Control.FlatAppearance.MouseDownBackColor;
+ }
+ else if (state == PushButtonState.Hot
+ && !Control.FlatAppearance.MouseOverBackColor.IsEmpty
+ && !IsHighContrastHighlighted())
+ {
+ backColor = Control.FlatAppearance.MouseOverBackColor;
+ }
+ }
+
return backColor;
}
@@ -86,6 +102,8 @@ internal override void PaintUp(PaintEventArgs e, CheckState state)
g,
Control.ClientRectangle,
Control.FlatStyle,
+ Control.FlatAppearance.BorderSize,
+ Control.FlatAppearance.BorderColor,
pushButtonState,
Control.IsDefault,
Control.Focused,
@@ -124,6 +142,8 @@ internal override void PaintDown(PaintEventArgs e, CheckState state)
g,
Control.ClientRectangle,
Control.FlatStyle,
+ Control.FlatAppearance.BorderSize,
+ Control.FlatAppearance.BorderColor,
PushButtonState.Pressed,
Control.IsDefault,
Control.Focused,
@@ -162,6 +182,8 @@ internal override void PaintOver(PaintEventArgs e, CheckState state)
g,
Control.ClientRectangle,
Control.FlatStyle,
+ Control.FlatAppearance.BorderSize,
+ Control.FlatAppearance.BorderColor,
PushButtonState.Hot,
Control.IsDefault,
Control.Focused,
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeRendererBase.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeRendererBase.cs
index 9d655a618b0..885c34f0d6e 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeRendererBase.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/ButtonDarkModeRendererBase.cs
@@ -32,6 +32,8 @@ public void RenderButton(
Graphics graphics,
Rectangle bounds,
FlatStyle flatStyle,
+ int borderSize,
+ Color borderColor,
PushButtonState state,
bool isDefault,
bool focused,
@@ -61,11 +63,16 @@ public void RenderButton(
height: bounds.Height - padding.Vertical);
// Draw button background and get content bounds
- Rectangle contentBounds = DrawButtonBackground(graphics, paddedBounds, state, isDefault, backColor);
+ Rectangle contentBounds =
+ DrawButtonBackground(graphics, paddedBounds, state, isDefault, backColor, borderSize, borderColor);
- // Paint image and field using the provided delegates
+ // Paint image using the provided delegates
paintImage(contentBounds);
+ // Draw button border
+ DrawButtonBorder(graphics, bounds, borderSize, borderColor, state, isDefault);
+
+ // Paint field using the provided delegates
paintField();
if (focused && showFocusCues)
@@ -76,7 +83,13 @@ public void RenderButton(
}
}
- public abstract Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault, Color backColor);
+ public virtual void DrawButtonBorder(Graphics graphics, Rectangle bounds, int borderSize,
+ Color borderColor, PushButtonState state, bool isDefault)
+ {
+ }
+
+ public abstract Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds,
+ PushButtonState state, bool isDefault, Color backColor, int borderSize, Color borderColor);
public abstract void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault);
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/FlatButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/FlatButtonDarkModeRenderer.cs
index 2541cb1b6ad..d01e5e97aa2 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/FlatButtonDarkModeRenderer.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/FlatButtonDarkModeRenderer.cs
@@ -17,20 +17,20 @@ namespace System.Windows.Forms;
internal sealed class FlatButtonDarkModeRenderer : ButtonDarkModeRendererBase
{
private const int FocusIndicatorInflate = -3;
- private const int CornerRadius = 6;
- private static readonly Size s_corner = new(CornerRadius, CornerRadius);
private protected override Padding PaddingCore { get; } = new(0);
public override Rectangle DrawButtonBackground(
- Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault, Color backColor)
+ Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault,
+ Color backColor, int borderSize, Color borderColor)
{
// fill background
using var back = backColor.GetCachedSolidBrushScope();
- graphics.FillRectangle(back, bounds);
+ Rectangle rectangle = bounds;
+ // Shrink the drawing area to avoid overlapping the border, consistent with DrawButtonBorder's inset logic
+ rectangle.Inflate(-borderSize - 1, -borderSize - 1);
- // draw border identical to Win32
- DrawButtonBorder(graphics, bounds, state, isDefault);
+ graphics.FillRectangle(back, rectangle);
// return inner content area (border + 1 px system padding)
return Rectangle.Inflate(bounds, -3, -3);
@@ -77,33 +77,29 @@ public override Color GetBackgroundColor(PushButtonState state, bool isDefault)
_ => DefaultColors.StandardBackColor
};
- private static void DrawButtonBorder(Graphics g, Rectangle bounds, PushButtonState state, bool isDefault)
+ public override void DrawButtonBorder(Graphics g, Rectangle bounds, int borderSize, Color borderColor, PushButtonState state, bool isDefault)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
// Win32 draws its stroke fully *inside* the control → inset by 1 px
Rectangle outer = Rectangle.Inflate(bounds, -1, -1);
- DrawSingleBorder(g, outer, GetBorderColor(state));
+ DrawSingleBorder(g, outer, borderSize, borderColor, GetBorderColor(state));
// Default button gets a second 1‑px border one pixel further inside
if (isDefault)
{
Rectangle inner = Rectangle.Inflate(outer, -1, -1);
- DrawSingleBorder(g, inner, DefaultColors.AcceptFocusIndicatorBackColor);
+ DrawSingleBorder(g, inner, borderSize, borderColor, DefaultColors.AcceptFocusIndicatorBackColor);
}
}
- private static void DrawSingleBorder(Graphics g, Rectangle rect, Color color)
+ private static void DrawSingleBorder(Graphics g, Rectangle rect, int borderSize, Color borderColor, Color color)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
- using var path = new GraphicsPath();
- path.AddRoundedRectangle(rect, s_corner);
-
- // a 1‑px stroke, aligned *inside*, is exactly what Win32 draws
- using var pen = new Pen(color) { Alignment = PenAlignment.Inset };
- g.DrawPath(pen, path);
+ using var pen = new Pen(!borderColor.IsEmpty ? borderColor : color, borderSize) { Alignment = PenAlignment.Inset };
+ g.DrawRectangle(pen, rect);
}
private static Color GetBorderColor(PushButtonState state) =>
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/IButtonRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/IButtonRenderer.cs
index ab1f7844042..bb9ed59a522 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/IButtonRenderer.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/IButtonRenderer.cs
@@ -32,6 +32,8 @@ static void DrawButtonBorder(Graphics graphics, GraphicsPath path, Color borderC
/// The graphics context to draw on.
/// The bounds of the button.
/// The flat style of the button.
+ /// Size of the button border
+ /// Color of the button border
/// The visual state of the button (normal, hot, pressed, disabled, default).
/// True if the button is the default button; otherwise, false.
/// True if the button is focused; otherwise, false.
@@ -43,6 +45,8 @@ void RenderButton(
Graphics graphics,
Rectangle bounds,
FlatStyle flatStyle,
+ int borderSize,
+ Color borderColor,
PushButtonState state,
bool isDefault,
bool focused,
@@ -59,8 +63,17 @@ void RenderButton(
/// Bounds of the button
/// State of the button (normal, hot, pressed, disabled)
/// True if button is the default button
+ /// Size of the button border
+ /// Color of the button border
/// The content bounds (area inside the button for text/image)
- Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault, Color backColor);
+ Rectangle DrawButtonBackground(
+ Graphics graphics,
+ Rectangle bounds,
+ PushButtonState state,
+ bool isDefault,
+ Color backColor,
+ int borderSize,
+ Color borderColor);
///
/// Draws focus indicator appropriate for this style.
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/PopupButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/PopupButtonDarkModeRenderer.cs
index 5970ad57fca..37053ad32b4 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/PopupButtonDarkModeRenderer.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/PopupButtonDarkModeRenderer.cs
@@ -30,7 +30,8 @@ internal class PopupButtonDarkModeRenderer : ButtonDarkModeRendererBase
///
/// Draws button background with popup styling, including subtle 3D effect.
///
- public override Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault, Color backColor)
+ public override Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state,
+ bool isDefault, Color backColor, int borderSize, Color borderColor)
{
// Use padding from ButtonDarkModeRenderer
Padding padding = PaddingCore;
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/SystemButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/SystemButtonDarkModeRenderer.cs
index 706ccf777cd..67e71d4459b 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/SystemButtonDarkModeRenderer.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/DarkMode/SystemButtonDarkModeRenderer.cs
@@ -30,7 +30,8 @@ internal class SystemButtonDarkModeRenderer : ButtonDarkModeRendererBase
///
/// Draws button background with system styling (larger rounded corners).
///
- public override Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault, Color backColor)
+ public override Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state,
+ bool isDefault, Color backColor, int borderSize, Color borderColor)
{
// Shrink for DarkBorderGap and FocusBorderThickness
Rectangle fillBounds = Rectangle.Inflate(bounds, -SystemStylePadding, -SystemStylePadding);