From 97d865e5a4e37009a38ff7f229429b3cd1d68b29 Mon Sep 17 00:00:00 2001 From: "Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box)" Date: Thu, 30 Jul 2026 13:48:10 -0300 Subject: [PATCH 1/2] Fix icons of PropertyGrid control are not scaled well on 100% secondary monitor Fixes #8268 ## Proposed changes `PropertyGrid`'s toolbar icons (sort-alphabetically / sort-by-category / property-pages buttons) didn't rescale correctly after a PerMonitorV2 DPI change. Root-caused to two independent, stacked defects in the toolbar rebuild path (`PropertyGrid.SetupToolbar`/`CreatePushButton`): - `SetupToolbar()` rebuilt the toolbar's `ImageList` at the new DPI but never updated `ToolStrip.ImageScalingSize` (what actually drives rendered icon size), and the `ImageList.ImageSize` assignment itself was gated on `ScaleHelper.IsScalingRequired`, which only reflects the _process's_ startup DPI rather than whether the app is PerMonitorV2-aware. Fixed by always setting `ImageScalingSize` right after the `ImageList` is rebuilt; before any button is created, since a `ToolStripItem`'s size is computed once and never revisited later; and switching to `ScaleHelper.IsScalingRequirementMet`. - `ToolStripItem`/`ToolStripButton` bake their default `Margin` and minimum button width from `ScaleHelper.InitialSystemDpi` (again the process's startup DPI) at construction time, so a button (re)created after a runtime DPI change still inherits sizing baked in for whatever DPI the process happened to start at. Fixed by having `CreatePushButton` explicitly re-apply `Margin` and `DeviceDpi` using the grid's current DPI; `DeviceDpi` routes through `ToolStripButton`'s own already-correct `DeviceDpi` setter override, which recomputes the minimum width but was never being invoked for ordinary toolbar buttons. Together these explain the issue's reported asymmetry: sizing baked in for a _higher_ DPI and later applied at a _lower_ one is very visibly wrong (oversized icons/buttons); the reverse just leaves a little extra room, so it's easy to miss. ## Customer Impact - Apps using `PerMonitorV2` render `PropertyGrid` toolbar icons at the wrong size after the app moves between monitors with different DPI, until the process restarts. Visual/readability issue only; the grid remains fully usable. ## Regression? - Yes, relative to .NET Framework; not a regression within .NET Core/5+ (reproduces on .NET 6/7/8 alike per the issue). ## Risk - All changes are scoped to `PropertyGrid`'s own toolbar-button construction/rebuild path; none affect `SystemAware`/`DpiUnaware` apps or any other control. The `DeviceDpi`/`IsScalingRequirementMet` changes invoke existing, already-correct framework code paths rather than introducing new logic. > ### Before ### After ## Test environment(s) > 11.0.100-preview.5.26302.115 --- .../Controls/PropertyGrid/PropertyGrid.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs index 161889d3b97..f5255616ae0 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs @@ -1753,12 +1753,19 @@ private PropertyGridToolStripButton CreatePushButton( EventHandler eventHandler, bool useRadioButtonRole = false) { + // ToolStripItem/ToolStripButton bake Margin and the minimum button width from + // ScaleHelper.InitialSystemDpi (the process's startup DPI) at construction time. Re-apply both + // explicitly so a button (re)created after a runtime DPI change reflects the grid's actual current + // DPI; DeviceDpi routes through ToolStripButton's own existing, correct DeviceDpi setter override. + // See https://github.com/dotnet/winforms/issues/8268. PropertyGridToolStripButton button = new(this, useRadioButtonRole) { Text = toolTipText, AutoToolTip = true, DisplayStyle = ToolStripItemDisplayStyle.Image, - ImageIndex = imageIndex + ImageIndex = imageIndex, + Margin = ScaleHelper.ScaleToDpi(new Padding(0, 1, 0, 2), DeviceDpi), + DeviceDpi = DeviceDpi }; button.Click += eventHandler; @@ -2106,7 +2113,7 @@ private void EnsureLargeButtons() ImageSize = s_largeButtonSize }; - if (ScaleHelper.IsScalingRequired) + if (ScaleHelper.IsScalingRequirementMet) { AddLargeImage(_alphaBitmap); AddLargeImage(_categoryBitmap); @@ -2140,7 +2147,7 @@ private void EnsureLargeButtons() } } - // This method should be called only inside a if (DpiHelper.IsScalingRequired) clause. + // This method should be called only inside a if (ScaleHelper.IsScalingRequirementMet) clause. private void AddLargeImage(Bitmap? originalBitmap) { if (originalBitmap is null) @@ -3874,12 +3881,19 @@ private void SetupToolbar(bool fullRebuild) { _normalButtonImages?.Dispose(); _normalButtonImages = new ImageList(); - if (ScaleHelper.IsScalingRequired) + if (ScaleHelper.IsScalingRequirementMet) { _normalButtonImages.ImageSize = s_normalButtonSize; } } + // A ToolStripItem's size is computed once, at creation, and never revisited on its own; set + // ImageScalingSize (drives ToolStripItem.PreferredImageSize) before CreatePushButton runs below. + if (!LargeButtons) + { + _toolStrip.ImageScalingSize = _normalButtonImages.ImageSize; + } + // Setup our event handlers. EventHandler tabButtonHandler = OnViewTabButtonClick; EventHandler sortButtonHandler = OnViewSortButtonClick; @@ -4013,6 +4027,9 @@ private void SetupToolbar(bool fullRebuild) _toolStrip.ImageList = LargeButtons ? _largeButtonImages : _normalButtonImages; + // Covers LargeButtons only (_largeButtonImages isn't ready earlier); a no-op otherwise. + _toolStrip.ImageScalingSize = LargeButtons ? _largeButtonImages!.ImageSize : _normalButtonImages!.ImageSize; + using (SuspendLayoutScope scope = new(_toolStrip)) { _toolStrip.Items.Clear(); From 99909b350fe2dd9381d3464f52a8c26b3f630cc1 Mon Sep 17 00:00:00 2001 From: "Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box)" Date: Mon, 3 Aug 2026 21:13:44 -0300 Subject: [PATCH 2/2] Handles feedback --- .../Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs index f5255616ae0..a45d851eb8c 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/PropertyGrid/PropertyGrid.cs @@ -3887,13 +3887,6 @@ private void SetupToolbar(bool fullRebuild) } } - // A ToolStripItem's size is computed once, at creation, and never revisited on its own; set - // ImageScalingSize (drives ToolStripItem.PreferredImageSize) before CreatePushButton runs below. - if (!LargeButtons) - { - _toolStrip.ImageScalingSize = _normalButtonImages.ImageSize; - } - // Setup our event handlers. EventHandler tabButtonHandler = OnViewTabButtonClick; EventHandler sortButtonHandler = OnViewSortButtonClick; @@ -4028,7 +4021,7 @@ private void SetupToolbar(bool fullRebuild) _toolStrip.ImageList = LargeButtons ? _largeButtonImages : _normalButtonImages; // Covers LargeButtons only (_largeButtonImages isn't ready earlier); a no-op otherwise. - _toolStrip.ImageScalingSize = LargeButtons ? _largeButtonImages!.ImageSize : _normalButtonImages!.ImageSize; + _toolStrip.ImageScalingSize = _toolStrip.ImageList!.ImageSize; using (SuspendLayoutScope scope = new(_toolStrip)) {