Skip to content
1 change: 1 addition & 0 deletions src/System.Windows.Forms/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
override System.Windows.Forms.Button.BackgroundImage.set -> void
override System.Windows.Forms.ButtonBase.OnBackColorChanged(System.EventArgs! e) -> void
override System.Windows.Forms.ButtonBase.OnForeColorChanged(System.EventArgs! e) -> void
static System.Windows.Forms.Application.SetColorMode(System.Windows.Forms.SystemColorMode systemColorMode) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@ public virtual DialogResult DialogResult
}
}

public override Image? BackgroundImage
{
set
{
base.BackgroundImage = value;

if (Application.IsDarkModeEnabled)
{
// BackgroundImage changes may affect rendering logic,
// so we manually update the OwnerDraw flag to ensure correct visual behavior.
UpdateOwnerDraw();
}
}
}

/// <summary>
/// Defines, whether the control is owner-drawn. Based on this,
/// the UserPaint flags get set, which in turn makes it later
Expand All @@ -161,7 +176,6 @@ private protected override bool OwnerDraw
get
{
if (Application.IsDarkModeEnabled

// The SystemRenderer cannot render images. So, we flip to our
// own DarkMode renderer, if we need to render images, except if...
&& Image is null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,15 +549,13 @@ internal void PaintImage(PaintEventArgs e, LayoutData layout)
{
if (Application.IsDarkModeEnabled && Control.DarkModeRequestState is true && Control.BackgroundImage is not null)
{
Rectangle bounds = Control.ClientRectangle;
bounds.Inflate(-ButtonBorderSize, -ButtonBorderSize);
ControlPaint.DrawBackgroundImage(
e.GraphicsInternal,
Control.BackgroundImage,
Color.Transparent,
Control.BackgroundImageLayout,
Control.ClientRectangle,
bounds,
Control.ClientRectangle,
Control.DisplayRectangle.Location,
Control.RightToLeft);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public void RenderButton(
// Paint image and field using the provided delegates
paintImage(contentBounds);

DrawButtonBorder(graphics, bounds, state, isDefault);

paintField();

if (focused && showFocusCues)
Expand All @@ -76,6 +78,8 @@ public void RenderButton(
}
}

public abstract void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault);

public abstract Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault, Color backColor);

public abstract void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ 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);

Expand All @@ -28,10 +26,6 @@ public override Rectangle DrawButtonBackground(
// fill background
using var back = backColor.GetCachedSolidBrushScope();
graphics.FillRectangle(back, bounds);

// draw border identical to Win32
DrawButtonBorder(graphics, bounds, state, isDefault);

// return inner content area (border + 1 px system padding)
return Rectangle.Inflate(bounds, -3, -3);
}
Expand Down Expand Up @@ -77,7 +71,7 @@ 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, PushButtonState state, bool isDefault)
{
g.SmoothingMode = SmoothingMode.AntiAlias;

Expand All @@ -98,12 +92,9 @@ private static void DrawSingleBorder(Graphics g, Rectangle rect, 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);
g.DrawRectangle(pen, rect);
}

private static Color GetBorderColor(PushButtonState state) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ public override Rectangle DrawButtonBackground(Graphics graphics, Rectangle boun
using var brush = backColor.GetCachedSolidBrushScope();
graphics.FillPath(brush, path);

// Draw 3D effect borders
DrawButtonBorder(graphics, paddedBounds, state, isDefault);

// Return content bounds (area inside the button for text/image)
return contentBounds;
}
Expand Down Expand Up @@ -120,7 +117,7 @@ public override Color GetBackgroundColor(PushButtonState state, bool isDefault)
/// <summary>
/// Draws the 3D effect border for the button.
/// </summary>
private static void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault)
public override void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault)
{
// Save original smoothing mode to restore later
SmoothingMode originalMode = graphics.SmoothingMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ public override Color GetBackgroundColor(PushButtonState state, bool isDefault)
_ => DefaultColors.StandardBackColor
};

public override void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) =>
DrawButtonBorder(graphics, bounds, state, isDefault, false);

/// <summary>
/// Draws the button border based on the current state, using anti-aliasing and an additional inner border.
/// </summary>
Expand Down