Skip to content

Commit 98f54ff

Browse files
LeafShi1ricardobossanRicardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box)
authored
[release/8.0] Fix 12850 error provider exception in different dpi (#12915)
<!-- Please read CONTRIBUTING.md before submitting a pull request --> Back port pr #10850 and pr #12947 to release8.0 Fixes #12850 and #12939 ## Bug Description The PR #8486 removed `new Icon(icon, 16 ,16)` from `IconRegion` constructor, that causes the released icon object to be directly referenced, so there is an exception `Cannot access a disposed object. Object name: 'Icon' `. Backport #10850 fixes the above problem, but the PR always scaled `IconRegion` to 100% DPI, so it causes issue #12939: _The icon of errorProvider is not scaled well on HDPI screen_, so backport #12947 to scale the ErrorProvider's `IconRegion` according to the current device DPI. ## Customer Impact - ErrorProvider exception when shown a second time after being dragged on another monitor with different DPI. This regression was reported by the customer. ## Regression? - Yes, it's fine in dotnet 7. This is due to #8486 ## Risk - Low - the fix only change ErrorProvider icon's display size when it run in HDPI screen. ## Testing - Manual testing with the user-provided project. - Automation regression test ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/dotnet/winforms/pull/12915) --------- Co-authored-by: Ricardo Bossan <[email protected]> Co-authored-by: Ricardo Bossan (BEYONDSOFT CONSULTING INC) (from Dev Box) <[email protected]>
1 parent 833e89d commit 98f54ff

File tree

6 files changed

+125
-33
lines changed

6 files changed

+125
-33
lines changed

src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/DpiHelper.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,17 @@ public static void ScaleBitmapLogicalToDevice(ref Bitmap logicalBitmap, int devi
353353
}
354354
}
355355

356+
/// <summary>
357+
/// Get X, Y metrics at DPI, IF icon is not already that size, create and return a new one.
358+
/// </summary>
359+
internal static Icon ScaleSmallIconToDpi(Icon icon, int dpi)
360+
{
361+
int width = PInvoke.GetCurrentSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSMICON, (uint)dpi);
362+
int height = PInvoke.GetCurrentSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSMICON, (uint)dpi);
363+
364+
return (icon.Width == width && icon.Height == height) ? icon : new(icon, width, height);
365+
}
366+
356367
/// <summary>
357368
/// Indicates whether the first (Parking)Window has been created. From that moment on,
358369
/// we will not be able nor allowed to change the Process' DpiMode.

src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.ErrorWindow.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,8 @@ protected override void WmDpiChangedBeforeParent(ref Message m)
484484
}
485485

486486
double factor = ((double)currentDpi) / _parent._deviceDpi;
487-
using Icon icon = _provider.Icon;
487+
Icon icon = _provider.Icon;
488+
_provider.CurrentDpi = currentDpi;
488489
_provider.Icon = new Icon(icon, (int)(icon.Width * factor), (int)(icon.Height * factor));
489490
}
490491
}

src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.IconRegion.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ internal class IconRegion : IHandle<HICON>
1515
private Region? _region;
1616
private readonly Icon _icon;
1717

18-
public IconRegion(Icon icon)
18+
public IconRegion(Icon icon, int currentDpi)
1919
{
20-
_icon = icon;
20+
_icon = DpiHelper.ScaleSmallIconToDpi(icon, currentDpi);
2121
}
2222

2323
/// <summary>

src/System.Windows.Forms/src/System/Windows/Forms/ErrorProvider.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ public partial class ErrorProvider : Component, IExtenderProvider, ISupportIniti
2525
{
2626
private readonly Dictionary<Control, ControlItem> _items = new();
2727
private readonly Dictionary<Control, ErrorWindow> _windows = new();
28-
private Icon _icon = DefaultIcon;
28+
private Icon? _icon;
2929
private IconRegion? _region;
3030
private int _itemIdCounter;
3131
private int _blinkRate;
32+
private int _currentDpi;
3233
private ErrorBlinkStyle _blinkStyle;
3334
private bool _showIcon = true; // used for blinking
3435
private bool _inSetErrorManager;
@@ -314,7 +315,7 @@ public object? DataSource
314315
{
315316
if (_parentControl is not null && _parentControl.BindingContext is not null && value is not null && !string.IsNullOrEmpty(_dataMember))
316317
{
317-
// Let's check if the datamember exists in the new data source
318+
// Let's check if the data member exists in the new data source
318319
try
319320
{
320321
_errorManager = _parentControl.BindingContext[value, _dataMember];
@@ -552,10 +553,8 @@ private static Icon DefaultIcon
552553
if (t_defaultIcon is null)
553554
{
554555
// Error provider uses small Icon.
555-
int width = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSMICON);
556-
int height = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSMICON);
557-
using Icon defaultIcon = new(typeof(ErrorProvider), "Error");
558-
t_defaultIcon = new Icon(defaultIcon, width, height);
556+
Icon defaultIcon = new(typeof(ErrorProvider), "Error");
557+
t_defaultIcon = DpiHelper.ScaleSmallIconToDpi(defaultIcon, DpiHelper.DeviceDpi);
559558
}
560559
}
561560

@@ -573,10 +572,7 @@ private static Icon DefaultIcon
573572
[SRDescription(nameof(SR.ErrorProviderIconDescr))]
574573
public Icon Icon
575574
{
576-
get
577-
{
578-
return _icon;
579-
}
575+
get => _icon ??= DefaultIcon;
580576
set
581577
{
582578
_icon = value.OrThrowIfNull();
@@ -589,10 +585,21 @@ public Icon Icon
589585
}
590586
}
591587

588+
/// <summary>
589+
/// Gets or sets the DPI at which the current error is displayed.
590+
/// If currentDpi is not set, it defaults to _parentControl.DeviceDpi
591+
/// or the system DPI.
592+
/// </summary>
593+
private int CurrentDpi
594+
{
595+
get => _currentDpi != 0 ? _currentDpi : _parentControl?.DeviceDpi ?? DpiHelper.DeviceDpi;
596+
set => _currentDpi = value;
597+
}
598+
592599
/// <summary>
593600
/// Create the icon region on demand.
594601
/// </summary>
595-
internal IconRegion Region => _region ??= new IconRegion(Icon);
602+
internal IconRegion Region => _region ??= new IconRegion(Icon, CurrentDpi);
596603

597604
/// <summary>
598605
/// Begin bulk member initialization - deferring binding to data source until EndInit is reached
@@ -766,7 +773,7 @@ internal ErrorWindow EnsureErrorWindow(Control parent)
766773
[SRDescription(nameof(SR.ErrorProviderIconPaddingDescr))]
767774
public int GetIconPadding(Control control) => EnsureControlItem(control).IconPadding;
768775

769-
private void ResetIcon() => Icon = DefaultIcon;
776+
private void ResetIcon() => _icon = null;
770777

771778
[EditorBrowsable(EditorBrowsableState.Advanced)]
772779
protected virtual void OnRightToLeftChanged(EventArgs e)
@@ -811,5 +818,5 @@ public void SetIconPadding(Control control, int padding)
811818
EnsureControlItem(control).IconPadding = padding;
812819
}
813820

814-
private bool ShouldSerializeIcon() => Icon != DefaultIcon;
821+
private bool ShouldSerializeIcon() => _icon is not null && _icon != DefaultIcon;
815822
}

src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/ErrorProviderTest.Designer.cs

Lines changed: 79 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/ErrorProviderTest.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ private void submitButton_Click(object sender, EventArgs e)
1414
{
1515
if (textBox1.TextLength < 5 || textBox1.TextLength > 10)
1616
{
17-
errorProvider1.SetError(textBox1, "The length of the testbox is invalid!");
17+
errorProvider1.SetError(textBox1, "The length of the textbox is invalid!");
1818
}
1919
else
2020
{
2121
errorProvider1.Clear();
2222
MessageBox.Show("All right!", "OK", MessageBoxButtons.OK, MessageBoxIcon.Information);
2323
}
24+
25+
if (textBox2.TextLength < 5 || textBox2.TextLength > 20)
26+
{
27+
errorProvider2.SetError(textBox2, "The length of the textbox is invalid!");
28+
}
29+
else
30+
{
31+
errorProvider2.Clear();
32+
MessageBox.Show("All right!", "OK", MessageBoxButtons.OK, MessageBoxIcon.Information);
33+
}
2434
}
2535
}

0 commit comments

Comments
 (0)