From d3ef73b0df0261c753fbcac2225ded25847bdb83 Mon Sep 17 00:00:00 2001 From: Peter Wagner Date: Wed, 25 Mar 2026 15:50:54 +0000 Subject: [PATCH 1/5] * Small square rendered next to Close button on KryptonForm when using a custom theme # PR Description - #3183 ## Summary Fix a rendering artifact (a small square) appearing next to the `KryptonForm` Close (X) button when using a custom XML-based Krypton theme/palette. ## Root Cause When importing a custom palette from XML, palette values that are omitted by the theme XML could remain at placeholder/default C# values instead of being initialized from the palette's `BasePalette`. This could leave caption/button chrome in an invalid/partial state, producing a small stray visual element. ## Changes In `KryptonCustomPaletteBase.ImportFromXmlDocument`, the palette is now populated from `BasePalette` before applying the XML overrides: - Call `PopulateFromBaseOperation(null)` before importing the `Properties` / `Images` elements from the XML. ## Why this works The initial populate step ensures missing metrics/draw settings (including caption/button related values used by the form window buttons) are correctly inherited from the base palette. The subsequent XML import can then override only what the theme explicitly defines. ## Files Modified - `Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs` ## Testing - Repro: apply a custom theme (e.g. `Microsoft365Pink`) to a `KryptonForm` and verify the small square near the Close button is gone. - Sanity: verify built-in palettes are unaffected. ## Notes - A full `dotnet build` was not clean in this environment due to pre-existing MSBuild/resource configuration errors unrelated to this palette change. --- Documents/Changelog/Changelog.md | 1 + .../Controls Toolkit/KryptonCustomPaletteBase.cs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Documents/Changelog/Changelog.md b/Documents/Changelog/Changelog.md index 19be033be..50e25c649 100644 --- a/Documents/Changelog/Changelog.md +++ b/Documents/Changelog/Changelog.md @@ -45,6 +45,7 @@ ## 2026-11-xx - Build 2611 (V110 Nightly) - November 2026 +* Resolved [#3183](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3183), Small square rendered next to Close button on KryptonForm when using a custom theme * Resolved [#3249](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3249), `KryptonForm` shows an extra border on the secondary monitor when maximized * Resolved [#3013](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3013), Maximized form's size exceeds the screen's working area * Resolved [#3164](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3164), Font property values are being serialized depending of the current culture in exported XML theme file diff --git a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs index 62d458075..35056d2a0 100644 --- a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs +++ b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs @@ -3124,6 +3124,14 @@ private void ImportFromXmlDocument([DisallowNull] XmlDocument doc) SetPaletteName(root.GetAttribute("Name")); } + // Populate missing palette values from the base palette first. + // + // Some theme XML files intentionally omit certain button/header metrics and + // drawing settings. Without an initial populate step, those omitted values can + // remain at placeholder defaults and cause small rendering artifacts in the + // KryptonForm caption chrome (e.g. near the system Close button). + PopulateFromBaseOperation(null); + // Grab the properties and images elements var props = root.SelectSingleNode(nameof(Properties)) as XmlElement; var images = root.SelectSingleNode(nameof(Images)) as XmlElement; From ac991222d1fc676b0089ef4208438b0deb026ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20A=2E=20Avil=C3=A9s?= <102550248+mcpbcs@users.noreply.github.com> Date: Thu, 26 Mar 2026 08:55:00 -0700 Subject: [PATCH 2/5] Added Test Form Bug3183SmallSquareRenderedNextToClose --- ...SmallSquareRenderedNextToClose.Designer.cs | 75 +++++++++++ .../Bug3183SmallSquareRenderedNextToClose.cs | 39 ++++++ ...Bug3183SmallSquareRenderedNextToClose.resx | 123 ++++++++++++++++++ .../TestForm/Properties/Resources.Designer.cs | 21 +++ .../TestForm/Properties/Resources.resx | 3 + .../TestForm/StartScreen.cs | 1 + 6 files changed, 262 insertions(+) create mode 100644 Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.Designer.cs create mode 100644 Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs create mode 100644 Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.resx diff --git a/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.Designer.cs b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.Designer.cs new file mode 100644 index 000000000..20d53e04c --- /dev/null +++ b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.Designer.cs @@ -0,0 +1,75 @@ +namespace TestForm +{ + partial class Bug3183SmallSquareRenderedNextToClose + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.kryptonButton1 = new Krypton.Toolkit.KryptonButton(); + this.kryptonManager1 = new Krypton.Toolkit.KryptonManager(this.components); + this.SuspendLayout(); + // + // kryptonButton1 + // + this.kryptonButton1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.kryptonButton1.Location = new System.Drawing.Point(465, 45); + this.kryptonButton1.Name = "kryptonButton1"; + this.kryptonButton1.Size = new System.Drawing.Size(290, 90); + this.kryptonButton1.TabIndex = 2; + this.kryptonButton1.Values.DropDownArrowColor = System.Drawing.Color.Empty; + this.kryptonButton1.Values.Text = "Apply Pink Theme"; + this.kryptonButton1.Click += new System.EventHandler(this.kryptonButton1_Click); + // + // kryptonManager1 + // + this.kryptonManager1.ToolkitStrings.MessageBoxStrings.LessDetails = "L&ess Details..."; + this.kryptonManager1.ToolkitStrings.MessageBoxStrings.MoreDetails = "&More Details..."; + this.kryptonManager1.ToolkitStrings.PrintPreviewDialogStrings.PaginationPartOneText = "of"; + this.kryptonManager1.ToolkitStrings.PrintPreviewDialogStrings.PaginationPartTwoText = null; + this.kryptonManager1.ToolkitStrings.PrintPreviewDialogStrings.ZoomInButtonText = "Zoom &Out"; + this.kryptonManager1.ToolkitStrings.PrintPreviewDialogStrings.ZoomOutButtonText = null; + // + // Bug3183SmallSquareRenderedNextToClose + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.kryptonButton1); + this.Name = "Bug3183SmallSquareRenderedNextToClose"; + this.Text = "Bug3183SmallSquareRenderedNextToClose"; + this.WindowState = System.Windows.Forms.FormWindowState.Maximized; + this.Load += new System.EventHandler(this.Bug3183SmallSquareRenderedNextToClose_Load); + this.ResumeLayout(false); + + } + + #endregion + + private KryptonButton kryptonButton1; + private KryptonManager kryptonManager1; + } +} \ No newline at end of file diff --git a/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs new file mode 100644 index 000000000..668d2ebd8 --- /dev/null +++ b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +using TestForm.Properties; + +namespace TestForm +{ + public partial class Bug3183SmallSquareRenderedNextToClose : KryptonForm + { + public Bug3183SmallSquareRenderedNextToClose() + { + InitializeComponent(); + } + + private void Bug3183SmallSquareRenderedNextToClose_Load(object sender, EventArgs e) + { + + } + + private void kryptonButton1_Click(object sender, EventArgs e) + { + byte[] contentFile = Encoding.UTF8.GetBytes(Properties.Resources.Microsoft365_Super_Pink); + + KryptonCustomPaletteBase customPaletteBase = new KryptonCustomPaletteBase(); + customPaletteBase.ImportWithUpgrade(new MemoryStream(contentFile)); + + this.kryptonManager1.GlobalCustomPalette = customPaletteBase; + this.kryptonManager1.GlobalPaletteMode = PaletteMode.Custom; + } + } +} diff --git a/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.resx b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.resx new file mode 100644 index 000000000..d0bfb46cc --- /dev/null +++ b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/Source/Krypton Components/TestForm/Properties/Resources.Designer.cs b/Source/Krypton Components/TestForm/Properties/Resources.Designer.cs index df61b3c79..ab6056d33 100644 --- a/Source/Krypton Components/TestForm/Properties/Resources.Designer.cs +++ b/Source/Krypton Components/TestForm/Properties/Resources.Designer.cs @@ -1760,6 +1760,27 @@ internal static System.Drawing.Bitmap mh { } } + /// + /// Looks up a localized string similar to <?xml version='1.0' encoding='utf-8'?> + ///<KryptonPalette Version="21" Generated="Monday, 09 March 2026, @15:54"> + /// <Properties> + /// <UseThemeFormChromeBorderWidth Type="InheritBool" Value="Inherit" /> + /// <ButtonSpecs> + /// <Common> + /// <Image Type="Image" Value="" /> + /// <ImageStates> + /// <ImageCheckedNormal Type="Image" Value="" /> + /// <ImageCheckedPressed Type="Image" Value="" /> + /// <ImageCheckedTracking Type="Image" Value="" /> + /// <ImageNormal Type="Image" Value="" /> + /// [rest of string was truncated]";. + /// + internal static string Microsoft365_Super_Pink { + get { + return ResourceManager.GetString("Microsoft365_Super_Pink", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Source/Krypton Components/TestForm/Properties/Resources.resx b/Source/Krypton Components/TestForm/Properties/Resources.resx index 284067406..2056775a5 100644 --- a/Source/Krypton Components/TestForm/Properties/Resources.resx +++ b/Source/Krypton Components/TestForm/Properties/Resources.resx @@ -946,4 +946,7 @@ ..\Resources\delete.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Microsoft365 Super Pink.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + \ No newline at end of file diff --git a/Source/Krypton Components/TestForm/StartScreen.cs b/Source/Krypton Components/TestForm/StartScreen.cs index 9974dffc3..3c731f12a 100644 --- a/Source/Krypton Components/TestForm/StartScreen.cs +++ b/Source/Krypton Components/TestForm/StartScreen.cs @@ -148,6 +148,7 @@ private void AddButtons() CreateButton("Tabbed MDI Demo (Issue #1746)", "KryptonTabbedMdiManager: MDI child windows displayed as tab pages instead of overlapping windows.", typeof(TabbedMdiDemo)); CreateButton("Ribbon MDI Demo (Issue #2921)", "Comprehensive demo for Issue #2921: Ribbon + MDI. Verifies no double ribbon tabs when opening/closing maximized MDI children; close/minimize/maximize and QAT click areas aligned with visuals.", typeof(RibbonMdiDemo)); CreateButton("Ribbon QATLocation=Hidden does not hide QAT when FormBorderStyle=None (Issue #3203)", string.Empty, typeof(Bug3203QATLocationHiddenFormTest)); + CreateButton("Small square rendered next to Close button on KryptonForm when using a custom theme (Issue #3183)", string.Empty, typeof(Bug3183SmallSquareRenderedNextToClose)); } private void OnFormClosing(object? sender, FormClosingEventArgs e) From d8dea15e5e14d59502f95c09cfc832f293ef9db7 Mon Sep 17 00:00:00 2001 From: Peter Wagner Date: Sat, 28 Mar 2026 09:16:17 +0000 Subject: [PATCH 3/5] * Documentation --- .../KryptonCustomPaletteBase.cs | 16 +++-- .../Bug3183SmallSquareRenderedNextToClose.cs | 71 +++++++++++++------ 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs index cdac0c3a9..a2517f97c 100644 --- a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs +++ b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs @@ -3124,12 +3124,18 @@ private void ImportFromXmlDocument([DisallowNull] XmlDocument doc) SetPaletteName(root.GetAttribute("Name")); } - // Populate missing palette values from the base palette first. + // Import order: establish a full baseline from BasePalette, then apply the XML theme on top. // - // Some theme XML files intentionally omit certain button/header metrics and - // drawing settings. Without an initial populate step, those omitted values can - // remain at placeholder defaults and cause small rendering artifacts in the - // KryptonForm caption chrome (e.g. near the system Close button). + // PopulateFromBaseOperation resets the palette hierarchy and copies values from the current + // BasePalette (via each storage type's PopulateFromBase). That ensures every property that the + // theme file does NOT serialize still reflects inheritance from the base theme instead of + // leftover C# defaults or stale state from a previous load—avoiding gaps in metrics, button + // specs, header chrome, etc. (e.g. stray rendering next to caption buttons). + // + // The steps below (ImportImagesFromElement / ImportObjectFromElement) then overwrite only what + // the XML actually defines. Theme wins for anything present in the file; anything omitted keeps + // the values we just populated from BasePalette. Doing populate after import would fight the + // file and could reset explicit theme settings, so populate must run first. PopulateFromBaseOperation(null); // Grab the properties and images elements diff --git a/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs index 668d2ebd8..8b3d1909c 100644 --- a/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs +++ b/Source/Krypton Components/TestForm/Bug3183SmallSquareRenderedNextToClose.cs @@ -1,4 +1,13 @@ -using System; +#region BSD License +/* + * + * New BSD 3-Clause License (https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/LICENSE) + * Modifications by Peter Wagner (aka Wagnerp) & Simon Coghlan (aka Smurf-IV), et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; @@ -13,27 +22,49 @@ namespace TestForm { - public partial class Bug3183SmallSquareRenderedNextToClose : KryptonForm - { - public Bug3183SmallSquareRenderedNextToClose() - { - InitializeComponent(); - } - - private void Bug3183SmallSquareRenderedNextToClose_Load(object sender, EventArgs e) - { + /// + /// Manual repro harness for GitHub issue #3183: a small square or wrong caption glyph next to the + /// Close (and related) buttons when a custom XML palette is applied. + /// + /// + /// + /// The embedded resource Microsoft365_Super_Pink matches the theme attached to the issue. + /// Run the form, then click Apply Pink Theme and inspect the top-right caption buttons + /// (especially when maximized vs normal). + /// + /// + /// The form starts maximized to mirror the original reporter’s sample and to exercise maximize / + /// restore button-spec state. + /// + /// + public partial class Bug3183SmallSquareRenderedNextToClose : KryptonForm + { + public Bug3183SmallSquareRenderedNextToClose() + { + InitializeComponent(); + } - } + /// + /// No startup theme load: compare default chrome first, then apply the custom palette from the button. + /// + private void Bug3183SmallSquareRenderedNextToClose_Load(object sender, EventArgs e) + { + } - private void kryptonButton1_Click(object sender, EventArgs e) - { - byte[] contentFile = Encoding.UTF8.GetBytes(Properties.Resources.Microsoft365_Super_Pink); + /// + /// Loads the embedded pink theme and switches the global palette to Custom so this form’s caption + /// uses the same path as real apps (). + /// + private void kryptonButton1_Click(object sender, EventArgs e) + { + // Theme XML is stored as a string resource so the repro has no dependency on disk paths. + byte[] contentFile = Encoding.UTF8.GetBytes(Properties.Resources.Microsoft365_Super_Pink); - KryptonCustomPaletteBase customPaletteBase = new KryptonCustomPaletteBase(); - customPaletteBase.ImportWithUpgrade(new MemoryStream(contentFile)); + KryptonCustomPaletteBase customPaletteBase = new KryptonCustomPaletteBase(); + customPaletteBase.ImportWithUpgrade(new MemoryStream(contentFile)); - this.kryptonManager1.GlobalCustomPalette = customPaletteBase; - this.kryptonManager1.GlobalPaletteMode = PaletteMode.Custom; - } - } + this.kryptonManager1.GlobalCustomPalette = customPaletteBase; + this.kryptonManager1.GlobalPaletteMode = PaletteMode.Custom; + } + } } From c83bf826c2ccb9d49ae09a862d6083c40d8c1272 Mon Sep 17 00:00:00 2001 From: Peter Wagner Date: Wed, 22 Apr 2026 18:43:29 +0100 Subject: [PATCH 4/5] Update KryptonCustomPaletteBase.cs --- .../KryptonCustomPaletteBase.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs index bad037263..040caba6e 100644 --- a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs +++ b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCustomPaletteBase.cs @@ -3552,11 +3552,29 @@ private void ExportObjectToElement(XmlDocument doc, // Should we test if the property value is the default? if (ignoreDefaults) { + // First prefer the component-model pattern used across the palette hierarchy. + // If a ShouldSerialize() method exists and returns false, treat + // the property as default and do not export it. + var shouldSerializeMethod = t.GetMethod($"ShouldSerialize{prop.Name}", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + binder: null, + types: Type.EmptyTypes, + modifiers: null); + + if (shouldSerializeMethod != null && + shouldSerializeMethod.ReturnType == typeof(bool)) + { + if (!(bool)shouldSerializeMethod.Invoke(obj, null)!) + { + ignore = true; + } + } + var defaultAttribs = prop.GetCustomAttributes(typeof(DefaultValueAttribute), false); // Does this property have a default value attribute? // Use the first one found (KryptonDefaultColor is a DefaultValueAttribute subclass) - if (defaultAttribs.Length >= 1) + if (!ignore && defaultAttribs.Length >= 1) { // Cast to correct type var defaultAttrib = defaultAttribs[0] as DefaultValueAttribute; From fde6c4489a5e6aa724e65aec803f37c5e74bcf00 Mon Sep 17 00:00:00 2001 From: Peter Wagner Date: Wed, 22 Apr 2026 19:03:02 +0100 Subject: [PATCH 5/5] Update StartScreen.cs --- Source/Krypton Components/TestForm/StartScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Krypton Components/TestForm/StartScreen.cs b/Source/Krypton Components/TestForm/StartScreen.cs index aa5e6abe6..5e99d1687 100644 --- a/Source/Krypton Components/TestForm/StartScreen.cs +++ b/Source/Krypton Components/TestForm/StartScreen.cs @@ -150,6 +150,7 @@ private void AddButtons() CreateButton("Tabbed MDI Demo (Issue #1746)", "KryptonTabbedMdiManager: MDI child windows displayed as tab pages instead of overlapping windows."); CreateButton("Ribbon MDI Demo (Issue #2921)", "Comprehensive demo for Issue #2921: Ribbon + MDI. Verifies no double ribbon tabs when opening/closing maximized MDI children; close/minimize/maximize and QAT click areas aligned with visuals."); CreateButton("Ribbon QATLocation=Hidden does not hide QAT when FormBorderStyle=None (Issue #3203)", string.Empty); + CreateButton("Small Square Rendered Next to Close Button (Issue #3183)", string.Empty); } private void OnFormClosing(object? sender, FormClosingEventArgs e)