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);