From a9fd467b5d1861c600565848bbfa26fa15322f86 Mon Sep 17 00:00:00 2001 From: Magomed Date: Tue, 14 Jul 2026 14:39:45 +0300 Subject: [PATCH] Fix gradient button paint artifacts and WrapMode crash on .NET Framework. Use toolbar-themed background fill, lighter shadow, and remove invalid LinearGradientBrush.WrapMode that caused OnPaint failures. Co-authored-by: Cursor --- .../UserControls/FOrders.cs | 6 +- .../UserControls/UCPriceList.cs | 4 +- src/Elfisa.UI/Controls/ModernButton.cs | 31 ++++++++- src/Elfisa.UI/Controls/ModernButtonStyles.cs | 8 +-- .../Helpers/SkillGradientButtonPaint.cs | 68 ++++++------------- 5 files changed, 56 insertions(+), 61 deletions(-) diff --git a/src/ElectronicPharmacy/UserControls/FOrders.cs b/src/ElectronicPharmacy/UserControls/FOrders.cs index f70554b..749d90a 100644 --- a/src/ElectronicPharmacy/UserControls/FOrders.cs +++ b/src/ElectronicPharmacy/UserControls/FOrders.cs @@ -91,7 +91,7 @@ namespace Электронная_Фармация.UserControls const int row1Y = 10; const int controlHeight = 32; const int filterHeight = 33; - const int buttonHeight = 37; + const int buttonHeight = 34; const int rowGap = 14; const int row2Y = row1Y + buttonHeight + rowGap; const int commentWidth = 200; @@ -126,11 +126,11 @@ namespace Электронная_Фармация.UserControls x = pad; txtGoodFromOrders.SetBounds(x, row2Y, 220, controlHeight); x = txtGoodFromOrders.Right + gap; - btnClearGoodSearch.SetBounds(x, row2Y, 32, controlHeight + 3); + btnClearGoodSearch.SetBounds(x, row2Y, 32, controlHeight); x = btnClearGoodSearch.Right + gap; txtOrderNumberFilter.SetBounds(x, row2Y, 220, controlHeight); x = txtOrderNumberFilter.Right + gap; - btnClearOrderNumberFilter.SetBounds(x, row2Y, 32, controlHeight + 3); + btnClearOrderNumberFilter.SetBounds(x, row2Y, 32, controlHeight); x = btnClearOrderNumberFilter.Right + gap; btnAddComment.SetBounds(x, row2Y, commentWidth, buttonHeight); diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.cs index c667c75..fe2f19b 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.cs @@ -52,9 +52,9 @@ namespace Электронная_Фармация.UserControls Tag = "chrome-child"; btnDeleteGood.Height = 34; - btnClearSearchInPriceList.Height = 35; + btnClearSearchInPriceList.Height = 32; txtSearchGoodNameInPriceList.Height = 32; - btnClearSupplierFilter.Height = 35; + btnClearSupplierFilter.Height = 32; panel1.SizeChanged += (_, __) => LayoutMarkupRow(); LayoutMarkupRow(); diff --git a/src/Elfisa.UI/Controls/ModernButton.cs b/src/Elfisa.UI/Controls/ModernButton.cs index ce7cb79..b49fd01 100644 --- a/src/Elfisa.UI/Controls/ModernButton.cs +++ b/src/Elfisa.UI/Controls/ModernButton.cs @@ -94,13 +94,29 @@ namespace Elfisa.UI.Controls return; } - var parentColor = Parent?.BackColor ?? ThemeManager.Colors.ToolbarBackground; - using (var brush = new SolidBrush(parentColor)) + using (var brush = new SolidBrush(ResolveChromeBackground())) { pevent.Graphics.FillRectangle(brush, ClientRectangle); } } + private Color ResolveChromeBackground() + { + var theme = _theme ?? ThemeManager.Colors; + var parentTag = Parent?.Tag as string; + if (parentTag == "toolbar") + { + return theme.ToolbarBackground; + } + + if (parentTag == "chrome-child" || parentTag == "status-footer") + { + return theme.BackgroundSecondary; + } + + return Parent?.BackColor ?? theme.ToolbarBackground; + } + public ModernButton() { SetStyle(ControlStyles.AllPaintingInWmPaint | @@ -135,6 +151,12 @@ namespace Elfisa.UI.Controls ? new Font(theme.FontSemibold14.FontFamily, 10f, FontStyle.Bold) : new Font("Segoe UI Semibold", 10f, FontStyle.Bold); } + + if (SkillGradientButtonPaint.UsesGradient(_buttonStyle, _compactIconMode) && !_headerNavMode && !_topNavMode) + { + BackColor = ResolveChromeBackground(); + } + Invalidate(); } @@ -240,6 +262,11 @@ namespace Elfisa.UI.Controls if (usesGradient && !_topNavMode && !_sidebarMode) { + using (var bgBrush = new SolidBrush(ResolveChromeBackground())) + { + g.FillRectangle(bgBrush, bounds); + } + SkillGradientButtonPaint.Paint( g, bounds, diff --git a/src/Elfisa.UI/Controls/ModernButtonStyles.cs b/src/Elfisa.UI/Controls/ModernButtonStyles.cs index 8105e1b..e5373e1 100644 --- a/src/Elfisa.UI/Controls/ModernButtonStyles.cs +++ b/src/Elfisa.UI/Controls/ModernButtonStyles.cs @@ -60,9 +60,7 @@ namespace Elfisa.UI.Controls button.AllowTextEllipsis = false; button.Font = ActionFont; button.Padding = new Padding(16, 6, 16, 6); - var height = style == ModernButtonStyle.Primary || style == ModernButtonStyle.Danger - ? DefaultHeight + 3 - : DefaultHeight; + var height = DefaultHeight; button.Height = Math.Max(button.Height, height); FitToText(button, height); @@ -85,8 +83,8 @@ namespace Elfisa.UI.Controls button.Font = new Font("Segoe UI Semibold", 13f, FontStyle.Bold); button.IconGlyph = null; button.AllowTextEllipsis = false; - button.MinimumSize = new Size(ClearSize, ClearSize + 3); - button.Size = new Size(ClearSize, ClearSize + 3); + button.MinimumSize = new Size(ClearSize, ClearSize); + button.Size = new Size(ClearSize, ClearSize); } public static void FitToText(ModernButton button, int height = DefaultHeight) diff --git a/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs b/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs index 7565792..b5ca044 100644 --- a/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs +++ b/src/Elfisa.UI/Helpers/SkillGradientButtonPaint.cs @@ -5,42 +5,18 @@ using Elfisa.UI.Controls; namespace Elfisa.UI.Helpers { /// - /// Градиент и тень из skill winforms-modern-buttons для ModernButton. + /// Градиент из skill winforms-modern-buttons для ModernButton (без тяжёлой тени — она давала артефакты на toolbar). /// internal static class SkillGradientButtonPaint { - private static readonly Color ShadowBase = Color.FromArgb(60, 0, 0, 0); - public static bool UsesGradient(ModernButtonStyle style, bool compactIconMode) { - return style == ModernButtonStyle.Primary - || style == ModernButtonStyle.Danger - || (compactIconMode && style == ModernButtonStyle.Danger); + return style == ModernButtonStyle.Primary || style == ModernButtonStyle.Danger; } public static int GetCornerRadius(ModernButtonStyle style, bool compactIconMode) { - if (compactIconMode) - { - return 6; - } - - return style == ModernButtonStyle.Primary ? 12 : 8; - } - - public static int GetShadowDepth(ModernButtonStyle style, bool compactIconMode, bool enabled) - { - if (!enabled) - { - return 0; - } - - if (compactIconMode) - { - return 3; - } - - return style == ModernButtonStyle.Primary ? 5 : 4; + return compactIconMode ? 6 : style == ModernButtonStyle.Primary ? 10 : 8; } public static void GetGradient( @@ -71,7 +47,6 @@ namespace Elfisa.UI.Helpers return; } - // Primary preset from skill if (pressed) { top = Color.FromArgb(25, 70, 160); @@ -98,13 +73,14 @@ namespace Elfisa.UI.Helpers bool pressed, bool enabled) { - var shadowDepth = GetShadowDepth(style, compactIconMode, enabled); var radius = GetCornerRadius(style, compactIconMode); var buttonRect = new Rectangle( clientRect.X, clientRect.Y, clientRect.Width - 1, - clientRect.Height - 1 - shadowDepth); + clientRect.Height - 1); + + g.SetClip(clientRect); if (!enabled) { @@ -116,44 +92,38 @@ namespace Elfisa.UI.Helpers return; } - for (var i = shadowDepth; i > 0; i--) + // Лёгкая тень — один слой, без чёрной полосы снизу + var shadowRect = new Rectangle(buttonRect.X, buttonRect.Y + 1, buttonRect.Width, buttonRect.Height); + using (var shadowPath = GraphicsExtensions.CreateRoundedRectangle(shadowRect, radius)) + using (var shadowBrush = new SolidBrush(Color.FromArgb(28, 0, 0, 0))) { - var alpha = ShadowBase.A * i / (shadowDepth * 3); - using (var shadowBrush = new SolidBrush(Color.FromArgb(alpha, ShadowBase))) - using (var shadowPath = GraphicsExtensions.CreateRoundedRectangle( - new Rectangle( - clientRect.X + i, - buttonRect.Y + i + 1, - buttonRect.Width - i, - buttonRect.Height - 1), - radius)) - { - g.FillPath(shadowBrush, shadowPath); - } + g.FillPath(shadowBrush, shadowPath); } + var faceRect = new Rectangle(buttonRect.X, buttonRect.Y, buttonRect.Width, buttonRect.Height - 1); GetGradient(style, hover, pressed, out var top, out var bottom); - using (var path = GraphicsExtensions.CreateRoundedRectangle(buttonRect, radius)) - using (var brush = new LinearGradientBrush(buttonRect, top, bottom, LinearGradientMode.Vertical)) + using (var path = GraphicsExtensions.CreateRoundedRectangle(faceRect, radius)) + using (var brush = new LinearGradientBrush(faceRect, top, bottom, LinearGradientMode.Vertical)) { g.FillPath(brush, path); - using (var highlightPen = new Pen(Color.FromArgb(40, Color.White), 1f)) + using (var highlightPen = new Pen(Color.FromArgb(36, Color.White), 1f)) { - g.DrawArc(highlightPen, buttonRect.X, buttonRect.Y, radius * 2, radius * 2, 180, 90); + g.DrawArc(highlightPen, faceRect.X, faceRect.Y, radius * 2, radius * 2, 180, 90); } } + + g.ResetClip(); } public static Rectangle GetContentRect(Rectangle clientRect, ModernButtonStyle style, bool compactIconMode, bool enabled) { - var shadowDepth = GetShadowDepth(style, compactIconMode, enabled); return new Rectangle( clientRect.X, clientRect.Y, clientRect.Width - 1, - clientRect.Height - 1 - shadowDepth); + clientRect.Height - 1); } } }