Skip to content

Commit 0727f28

Browse files
Refactor out InitializeControl and re-utilize CreateParams.
1 parent a5d56e5 commit 0727f28

File tree

31 files changed

+174
-252
lines changed

31 files changed

+174
-252
lines changed

src/System.Windows.Forms/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,11 @@
1+
override System.Windows.Forms.DataGridView.CreateParams.get -> System.Windows.Forms.CreateParams!
2+
override System.Windows.Forms.TabPage.CreateParams.get -> System.Windows.Forms.CreateParams!
13
System.Windows.Forms.Form.FormScreenCaptureMode.get -> System.Windows.Forms.ScreenCaptureMode
24
System.Windows.Forms.Form.FormScreenCaptureMode.set -> void
35
System.Windows.Forms.ScreenCaptureMode
46
System.Windows.Forms.ScreenCaptureMode.Allow = 0 -> System.Windows.Forms.ScreenCaptureMode
57
System.Windows.Forms.ScreenCaptureMode.HideContent = 1 -> System.Windows.Forms.ScreenCaptureMode
68
System.Windows.Forms.ScreenCaptureMode.HideWindow = 2 -> System.Windows.Forms.ScreenCaptureMode
7-
override System.Windows.Forms.ButtonBase.InitializeControl(int deviceDpi) -> void
8-
override System.Windows.Forms.CheckBox.InitializeControl(int deviceDpi) -> void
9-
override System.Windows.Forms.DataGridView.InitializeControl(int deviceDpi) -> void
10-
override System.Windows.Forms.Label.InitializeControl(int deviceDpi) -> void
11-
override System.Windows.Forms.ListBox.InitializeControl(int deviceDpi) -> void
12-
override System.Windows.Forms.MonthCalendar.InitializeControl(int deviceDpi) -> void
13-
override System.Windows.Forms.PictureBox.InitializeControl(int deviceDpi) -> void
14-
override System.Windows.Forms.ProgressBar.InitializeControl(int deviceDpi) -> void
15-
override System.Windows.Forms.RadioButton.InitializeControl(int deviceDpi) -> void
16-
override System.Windows.Forms.ScrollableControl.InitializeControl(int deviceDpi) -> void
17-
override System.Windows.Forms.ScrollBar.InitializeControl(int deviceDpi) -> void
18-
override System.Windows.Forms.Splitter.InitializeControl(int deviceDpi) -> void
19-
override System.Windows.Forms.TabControl.InitializeControl(int deviceDpi) -> void
20-
override System.Windows.Forms.TabPage.InitializeControl(int deviceDpi) -> void
21-
override System.Windows.Forms.TrackBar.InitializeControl(int deviceDpi) -> void
22-
override System.Windows.Forms.TreeView.InitializeControl(int deviceDpi) -> void
23-
virtual System.Windows.Forms.Control.InitializeControl(int deviceDpi) -> void
249
[WFO5001]System.Windows.Forms.FormCornerPreference
2510
[WFO5001]System.Windows.Forms.FormCornerPreference.Default = 0 -> System.Windows.Forms.FormCornerPreference
2611
[WFO5001]System.Windows.Forms.FormCornerPreference.DoNotRound = 1 -> System.Windows.Forms.FormCornerPreference

src/System.Windows.Forms/System/Windows/Forms/Control.cs

Lines changed: 70 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,11 @@ public unsafe partial class Control :
221221
private static readonly int s_cacheTextCountProperty = PropertyStore.CreateKey();
222222
private static readonly int s_cacheTextFieldProperty = PropertyStore.CreateKey();
223223
private static readonly int s_ambientPropertiesServiceProperty = PropertyStore.CreateKey();
224-
225224
private static readonly int s_dataContextProperty = PropertyStore.CreateKey();
226225

226+
private static readonly int s_currentDpiProperty = PropertyStore.CreateKey();
227+
private static readonly int s_formerDpiProperty = PropertyStore.CreateKey();
228+
227229
private static bool s_needToLoadComCtl = true;
228230

229231
// This switch determines the default text rendering engine to use by some controls that support switching rendering engine.
@@ -267,8 +269,6 @@ public unsafe partial class Control :
267269
private short _updateCount;
268270
private LayoutEventArgs? _cachedLayoutEventArgs;
269271
private Queue<ThreadMethodEntry>? _threadCallbackList;
270-
internal int _deviceDpi;
271-
internal int _oldDeviceDpi;
272272

273273
// For keeping track of our ui state for focus and keyboard cues. Using a member
274274
// variable here because we hit this a lot
@@ -307,8 +307,13 @@ internal Control(bool autoInstallSyncContext) : base()
307307
{
308308
Properties = new PropertyStore();
309309

310-
// Initialize Dpi to the value on the primary screen, we will have the correct value when the Handle is created.
311-
_deviceDpi = _oldDeviceDpi = ScaleHelper.InitialSystemDpi;
310+
// Initialize Dpi to the value on the primary screen, we will have the correct
311+
// value when the Handle is created. We use the static infra to ensure that the DPI is set
312+
// correctly in the PropertyStore, and that it is not set to 0.
313+
// So, this is available before any Constructor of any inheriting control runs.
314+
Properties.AddValue(
315+
s_currentDpiProperty,
316+
ScaleHelper.InitialSystemDpi);
312317

313318
_window = new ControlNativeWindow(this);
314319
RequiredScalingEnabled = true;
@@ -333,7 +338,7 @@ internal Control(bool autoInstallSyncContext) : base()
333338
// At this point, the control is not yet created, but every necessary state as well as
334339
// the base class properties are in place, because they have been initialized by the
335340
// static ctor of Control, which is called before all other instance constructors.
336-
InitializeControl(_deviceDpi);
341+
InitializeControl();
337342

338343
// We baked the "default default" margin and min size into CommonProperties
339344
// so that in the common case the PropertyStore would be empty. If, however,
@@ -419,59 +424,12 @@ public Control(Control? parent, string? text, int left, int top, int width, int
419424
}
420425

421426
/// <summary>
422-
/// Provides inheriting controls a dedicated early-initialization hook that is guaranteed to run
423-
/// <i>before</i> <see cref="CreateParams"/> is called by any base class constructor.
424-
/// This method enables derived controls to set styles, flags, or perform other setup that must
425-
/// occur prior to any base class logic that depends on such initialization.
427+
/// Giving a derived control a chance to initialize its state before the
428+
/// constructor code runs. If the control needs to access the initial or former
429+
/// DeviceDPI, it can safely access the internal Properties CurrentDpi and
430+
/// FormerDpi at any time.
426431
/// </summary>
427-
/// <remarks>
428-
/// <para>
429-
/// In WinForms, the constructor call chain for controls can result in base class constructors
430-
/// invoking methods such as <see cref="CreateParams"/> or other initialization logic
431-
/// <b>before</b> the derived class's constructor body executes. This makes it impossible for
432-
/// the derived class to perform certain setup in time using only its constructor.
433-
/// </para>
434-
/// <para>
435-
/// For example, consider the following inheritance and call chain:
436-
/// </para>
437-
/// <code>
438-
/// public class MyButton : Button
439-
/// {
440-
/// public MyButton()
441-
/// {
442-
/// // This code runs after Button's constructor, which may have already called CreateParams.
443-
/// }
444-
///
445-
/// protected override void InitializeControl(int deviceDpi)
446-
/// {
447-
/// // This code runs before any base class calls to CreateParams or similar methods.
448-
/// }
449-
/// }
450-
///
451-
/// // Call chain:
452-
/// new MyButton()
453-
/// → Control..ctor()
454-
/// → Control.InitializeControl (called in base constructor)
455-
/// → ButtonBase.InitializeControl (called before CreateParams)
456-
/// → Button.InitializeControl (called before CreateParams)
457-
/// → MyButton.InitializeControl (called before CreateParams)
458-
/// → Control.CreateParams (called in base constructor)
459-
/// → ButtonBase.CreateParams (called before CreateParams)
460-
/// → Button.CreateParams (called before CreateParams)
461-
/// → MyButton.CreateParams (called before CreateParams)
462-
/// → MyButton.CreateParams (called before CreateParams)
463-
/// → ButtonBase..ctor()
464-
/// → Button..ctor()
465-
/// → MyButton..ctor()
466-
/// </code>
467-
/// <para>
468-
/// By overriding <c>InitializeControl</c>, inheritors can ensure their initialization logic
469-
/// runs at the correct time, even when base class constructors invoke methods that require
470-
/// early setup.
471-
/// </para>
472-
/// </remarks>
473-
/// <param name="deviceDpi">The DPI value for the control's device context.</param>
474-
protected virtual void InitializeControl(int deviceDpi)
432+
private protected virtual void InitializeControl()
475433
{
476434
}
477435

@@ -480,6 +438,42 @@ protected virtual void InitializeControl(int deviceDpi)
480438
/// </summary>
481439
internal DPI_AWARENESS_CONTEXT DpiAwarenessContext => _window.DpiAwarenessContext;
482440

441+
internal int DeviceDpiInternal
442+
{
443+
get
444+
{
445+
return Properties.GetValueOrDefault(
446+
s_currentDpiProperty,
447+
ScaleHelper.InitialSystemDpi);
448+
}
449+
450+
set
451+
{
452+
if (value != DeviceDpiInternal)
453+
{
454+
Properties.AddOrRemoveValue(s_currentDpiProperty, value, ScaleHelper.InitialSystemDpi);
455+
}
456+
}
457+
}
458+
459+
internal int FormerDeviceDpi
460+
{
461+
get
462+
{
463+
return Properties.GetValueOrDefault(
464+
s_formerDpiProperty,
465+
DeviceDpiInternal);
466+
}
467+
468+
set
469+
{
470+
if (value != FormerDeviceDpi)
471+
{
472+
Properties.AddOrRemoveValue(s_formerDpiProperty, value, DeviceDpiInternal);
473+
}
474+
}
475+
}
476+
483477
/// <summary>
484478
/// The Accessibility Object for this Control
485479
/// </summary>
@@ -1666,7 +1660,7 @@ public static Font DefaultFont
16661660
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
16671661
public int DeviceDpi
16681662
// deviceDpi may change in WmDpiChangedBeforeParent in PmV2 scenarios, so we can't cache statically.
1669-
=> ScaleHelper.IsThreadPerMonitorV2Aware ? _deviceDpi : ScaleHelper.InitialSystemDpi;
1663+
=> ScaleHelper.IsThreadPerMonitorV2Aware ? DeviceDpiInternal : ScaleHelper.InitialSystemDpi;
16701664

16711665
// The color to use when drawing disabled text. Normally we use BackColor,
16721666
// but that obviously won't work if we're transparent.
@@ -5375,9 +5369,9 @@ Site is { } site
53755369
// We would need to get adornments metrics for both (old and new) Dpi in case application is in PerMonitorV2 mode and Dpi changed.
53765370
AdjustWindowRectExForControlDpi(ref adornmentsAfterDpiChange, (WINDOW_STYLE)cp.Style, bMenu: false, (WINDOW_EX_STYLE)cp.ExStyle);
53775371

5378-
if (_oldDeviceDpi != _deviceDpi && OsVersion.IsWindows10_1703OrGreater())
5372+
if (FormerDeviceDpi != DeviceDpiInternal && OsVersion.IsWindows10_1703OrGreater())
53795373
{
5380-
AdjustWindowRectExForDpi(ref adornmentsBeforeDpiChange, (WINDOW_STYLE)cp.Style, bMenu: false, (WINDOW_EX_STYLE)cp.ExStyle, _oldDeviceDpi);
5374+
AdjustWindowRectExForDpi(ref adornmentsBeforeDpiChange, (WINDOW_STYLE)cp.Style, bMenu: false, (WINDOW_EX_STYLE)cp.ExStyle, FormerDeviceDpi);
53815375
}
53825376
else
53835377
{
@@ -5530,7 +5524,7 @@ internal Control[] GetChildControlsInTabOrder(bool handleCreatedOnly)
55305524
/// <returns>The control's <see cref="Font"/></returns>
55315525
private Font GetCurrentFontAndDpi(out int fontDpi)
55325526
{
5533-
fontDpi = _deviceDpi;
5527+
fontDpi = DeviceDpiInternal;
55345528

55355529
// If application is in PerMonitorV2 mode and font is scaled when moved between monitors.
55365530
if (ScaledControlFont is not null)
@@ -6431,7 +6425,7 @@ public void ScaleBitmapLogicalToDevice(ref Bitmap logicalBitmap)
64316425

64326426
private protected void AdjustWindowRectExForControlDpi(ref RECT rect, WINDOW_STYLE style, bool bMenu, WINDOW_EX_STYLE exStyle)
64336427
{
6434-
AdjustWindowRectExForDpi(ref rect, style, bMenu, exStyle, _deviceDpi);
6428+
AdjustWindowRectExForDpi(ref rect, style, bMenu, exStyle, DeviceDpiInternal);
64356429
}
64366430

64376431
private static void AdjustWindowRectExForDpi(ref RECT rect, WINDOW_STYLE style, bool bMenu, WINDOW_EX_STYLE exStyle, int dpi)
@@ -7404,19 +7398,20 @@ void HandleHighDpi()
74047398
return;
74057399
}
74067400

7407-
int old = _deviceDpi;
7401+
int old = DeviceDpiInternal;
74087402
Font localFont = GetCurrentFontAndDpi(out int fontDpi);
7409-
_deviceDpi = (int)PInvoke.GetDpiForWindow(this);
7410-
if (old == _deviceDpi)
7403+
7404+
Properties.AddOrRemoveValue(s_currentDpiProperty, (int)PInvoke.GetDpiForWindow(this));
7405+
if (old == DeviceDpiInternal)
74117406
{
74127407
return;
74137408
}
74147409

7415-
if (fontDpi != _deviceDpi)
7410+
if (fontDpi != DeviceDpiInternal)
74167411
{
74177412
// Controls are by default font scaled.
74187413
// Dpi change requires font to be recalculated in order to get controls scaled with right dpi.
7419-
Font fontForDpi = GetScaledFont(localFont, _deviceDpi, fontDpi);
7414+
Font fontForDpi = GetScaledFont(localFont, DeviceDpiInternal, fontDpi);
74207415
ScaledControlFont = fontForDpi;
74217416

74227417
// If it is a container control that inherit Font and is scaled by parent, we simply scale Font
@@ -7428,7 +7423,7 @@ void HandleHighDpi()
74287423
}
74297424
}
74307425

7431-
RescaleConstantsForDpi(old, _deviceDpi);
7426+
RescaleConstantsForDpi(old, DeviceDpiInternal);
74327427

74337428
// If the control is top-level window and its StartPosition is not WindowsDefaultLocation, Location needs
74347429
// recalculated. For example, a Form centered as FormStartPosition.CenterParent or FormStartPosition.CenterScreen,
@@ -11516,7 +11511,7 @@ private void WmDpiChangedBeforeParent(ref Message m)
1151611511
{
1151711512
DefWndProc(ref m);
1151811513

11519-
_oldDeviceDpi = _deviceDpi;
11514+
Properties.AddOrRemoveValue(s_formerDpiProperty, DeviceDpiInternal);
1152011515

1152111516
// In order to support tests, will be querying Dpi from the message first.
1152211517
int newDeviceDpi = (short)m.WParamInternal.LOWORD;
@@ -11527,16 +11522,16 @@ private void WmDpiChangedBeforeParent(ref Message m)
1152711522
newDeviceDpi = (int)PInvoke.GetDpiForWindow(this);
1152811523
}
1152911524

11530-
if (_oldDeviceDpi == newDeviceDpi)
11525+
if (FormerDeviceDpi == newDeviceDpi)
1153111526
{
1153211527
OnDpiChangedBeforeParent(EventArgs.Empty);
1153311528
return;
1153411529
}
1153511530

1153611531
Font localFont = GetCurrentFontAndDpi(out int fontDpi);
11537-
_deviceDpi = newDeviceDpi;
11532+
Properties.AddOrRemoveValue(s_currentDpiProperty, newDeviceDpi);
1153811533

11539-
if (fontDpi == _deviceDpi)
11534+
if (fontDpi == DeviceDpiInternal)
1154011535
{
1154111536
OnDpiChangedBeforeParent(EventArgs.Empty);
1154211537
return;
@@ -11548,7 +11543,7 @@ private void WmDpiChangedBeforeParent(ref Message m)
1154811543
ContainerControl? container = this as ContainerControl;
1154911544
bool isLocalFontSet = IsFontSet();
1155011545

11551-
ScaledControlFont = GetScaledFont(localFont, _deviceDpi, fontDpi);
11546+
ScaledControlFont = GetScaledFont(localFont, DeviceDpiInternal, fontDpi);
1155211547

1155311548
if (isLocalFontSet || container is null || !IsScaledByParent(this))
1155411549
{
@@ -11567,7 +11562,7 @@ private void WmDpiChangedBeforeParent(ref Message m)
1156711562
// This flag is reset when scaling is done on Container in "OnParentFontChanged".
1156811563
container?.IsDpiChangeScalingRequired = true;
1156911564

11570-
RescaleConstantsForDpi(_oldDeviceDpi, _deviceDpi);
11565+
RescaleConstantsForDpi(FormerDeviceDpi, DeviceDpiInternal);
1157111566
}
1157211567

1157311568
OnDpiChangedBeforeParent(EventArgs.Empty);

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,6 @@ protected ButtonBase()
8282
SetFlag(FlagShowToolTip, false);
8383
}
8484

85-
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
86-
protected override void InitializeControl(int deviceDpi)
87-
{
88-
// Call the base classes, and let them setup their fields.
89-
base.InitializeControl(deviceDpi);
90-
91-
// Opt into DarkMode.
92-
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
93-
}
94-
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
95-
9685
/// <summary>
9786
/// Gets or sets a value indicating whether the ellipsis character (...) appears at the right edge of the control,
9887
/// denoting that the control text extends beyond the specified length of the control.
@@ -265,6 +254,10 @@ protected override CreateParams CreateParams
265254
{
266255
get
267256
{
257+
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
258+
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
259+
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
260+
268261
CreateParams cp = base.CreateParams;
269262
if (!OwnerDraw)
270263
{

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/CheckBox.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public CheckBox() : base()
4949
TextAlign = ContentAlignment.MiddleLeft;
5050
}
5151

52-
protected override void InitializeControl(int deviceDpi) => ScaleConstants();
52+
private protected override void InitializeControl() => ScaleConstants();
5353

5454
private bool AccObjDoDefaultAction { get; set; }
5555

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/RadioButton.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ private void ScaleConstants()
241241
_flatSystemStyleMinimumHeight = LogicalToDeviceUnits(LogicalFlatSystemStyleMinimumHeight);
242242
}
243243

244-
protected override void InitializeControl(int deviceDpi) => ScaleConstants();
244+
private protected override void InitializeControl() => ScaleConstants();
245245

246246
internal override Size GetPreferredSizeCore(Size proposedConstraints)
247247
{

src/System.Windows.Forms/System/Windows/Forms/Controls/ComboBox/ComboBox.FlatComboAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public FlatComboAdapter(ComboBox comboBox, bool smallButton)
2929
s_offsetPixels = comboBox.LogicalToDeviceUnits(OFFSET_2PIXELS);
3030

3131
_clientRect = comboBox.ClientRectangle;
32-
int dropDownButtonWidth = SystemInformation.GetHorizontalScrollBarArrowWidthForDpi(comboBox._deviceDpi);
32+
int dropDownButtonWidth = SystemInformation.GetHorizontalScrollBarArrowWidthForDpi(comboBox.DeviceDpiInternal);
3333
_outerBorder = new Rectangle(_clientRect.Location, new Size(_clientRect.Width - 1, _clientRect.Height - 1));
3434
_innerBorder = new Rectangle(_outerBorder.X + 1, _outerBorder.Y + 1, _outerBorder.Width - dropDownButtonWidth - 2, _outerBorder.Height - 2);
3535
_innerInnerBorder = new Rectangle(_innerBorder.X + 1, _innerBorder.Y + 1, _innerBorder.Width - 2, _innerBorder.Height - 2);

src/System.Windows.Forms/System/Windows/Forms/Controls/DataGridView/DataGridView.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -488,14 +488,6 @@ public DataGridView()
488488
Invalidate();
489489
}
490490

491-
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
492-
protected override void InitializeControl(int deviceDpi)
493-
{
494-
base.InitializeControl(deviceDpi);
495-
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
496-
}
497-
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
498-
499491
[Browsable(false)]
500492
[EditorBrowsable(EditorBrowsableState.Advanced)]
501493
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
@@ -1497,6 +1489,17 @@ public DataGridViewHeaderBorderStyle ColumnHeadersBorderStyle
14971489
}
14981490
}
14991491

1492+
protected override CreateParams CreateParams
1493+
{
1494+
get
1495+
{
1496+
#pragma warning disable WFO5001
1497+
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
1498+
#pragma warning restore WFO5001
1499+
return base.CreateParams;
1500+
}
1501+
}
1502+
15001503
[SRCategory(nameof(SR.CatPropertyChanged))]
15011504
[SRDescription(nameof(SR.DataGridView_ColumnHeadersBorderStyleChangedDescr))]
15021505
public event EventHandler? ColumnHeadersBorderStyleChanged

0 commit comments

Comments
 (0)