Skip to content

Commit 206e231

Browse files
Introduce InitializeControl and refactor for DarkMode and InitialDPI passing.
1 parent 34128e7 commit 206e231

File tree

27 files changed

+272
-121
lines changed

27 files changed

+272
-121
lines changed

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

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
11
#nullable enable
22
[WFO5001]static System.Windows.Forms.Application.SetColorMode(System.Windows.Forms.SystemColorMode systemColorMode) -> void
33
[WFO5001]System.Windows.Forms.ControlStyles.ApplyThemingImplicitly = 524288 -> System.Windows.Forms.ControlStyles
4-
[WFO5001]System.Windows.Forms.Form.FormBorderColorChanged -> System.EventHandler?
5-
[WFO5001]System.Windows.Forms.Form.FormCaptionBackColorChanged -> System.EventHandler?
6-
[WFO5001]System.Windows.Forms.Form.FormCaptionTextColorChanged -> System.EventHandler?
7-
[WFO5001]System.Windows.Forms.Form.FormCornerPreferenceChanged -> System.EventHandler?
8-
[WFO5001]System.Windows.Forms.FormCornerPreference
9-
[WFO5001]System.Windows.Forms.FormCornerPreference.Default = 0 -> System.Windows.Forms.FormCornerPreference
10-
[WFO5001]System.Windows.Forms.FormCornerPreference.DoNotRound = 1 -> System.Windows.Forms.FormCornerPreference
11-
[WFO5001]System.Windows.Forms.FormCornerPreference.Round = 2 -> System.Windows.Forms.FormCornerPreference
12-
[WFO5001]System.Windows.Forms.FormCornerPreference.RoundSmall = 3 -> System.Windows.Forms.FormCornerPreference
134
[WFO5001]System.Windows.Forms.SystemColorMode
145
[WFO5001]System.Windows.Forms.SystemColorMode.Classic = 0 -> System.Windows.Forms.SystemColorMode
156
[WFO5001]System.Windows.Forms.SystemColorMode.Dark = 2 -> System.Windows.Forms.SystemColorMode
167
[WFO5001]System.Windows.Forms.SystemColorMode.System = 1 -> System.Windows.Forms.SystemColorMode
17-
[WFO5001]virtual System.Windows.Forms.Form.OnFormBorderColorChanged(System.EventArgs! e) -> void
18-
[WFO5001]virtual System.Windows.Forms.Form.OnFormCaptionBackColorChanged(System.EventArgs! e) -> void
19-
[WFO5001]virtual System.Windows.Forms.Form.OnFormCaptionTextColorChanged(System.EventArgs! e) -> void
20-
[WFO5001]virtual System.Windows.Forms.Form.OnFormCornerPreferenceChanged(System.EventArgs! e) -> void
218
[WFO5002]static System.Windows.Forms.TaskDialog.ShowDialogAsync(nint hwndOwner, System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterOwner) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
229
[WFO5002]static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.IWin32Window! owner, System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterOwner) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!
2310
[WFO5002]static System.Windows.Forms.TaskDialog.ShowDialogAsync(System.Windows.Forms.TaskDialogPage! page, System.Windows.Forms.TaskDialogStartupLocation startupLocation = System.Windows.Forms.TaskDialogStartupLocation.CenterScreen) -> System.Threading.Tasks.Task<System.Windows.Forms.TaskDialogButton!>!

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,33 @@ System.Windows.Forms.ScreenCaptureMode
44
System.Windows.Forms.ScreenCaptureMode.Allow = 0 -> System.Windows.Forms.ScreenCaptureMode
55
System.Windows.Forms.ScreenCaptureMode.HideContent = 1 -> System.Windows.Forms.ScreenCaptureMode
66
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
24+
[WFO5001]System.Windows.Forms.FormCornerPreference
25+
[WFO5001]System.Windows.Forms.FormCornerPreference.Default = 0 -> System.Windows.Forms.FormCornerPreference
26+
[WFO5001]System.Windows.Forms.FormCornerPreference.DoNotRound = 1 -> System.Windows.Forms.FormCornerPreference
27+
[WFO5001]System.Windows.Forms.FormCornerPreference.Round = 2 -> System.Windows.Forms.FormCornerPreference
28+
[WFO5001]System.Windows.Forms.FormCornerPreference.RoundSmall = 3 -> System.Windows.Forms.FormCornerPreference
29+
[WFO5001]System.Windows.Forms.Form.FormBorderColorChanged -> System.EventHandler?
30+
[WFO5001]System.Windows.Forms.Form.FormCaptionBackColorChanged -> System.EventHandler?
31+
[WFO5001]System.Windows.Forms.Form.FormCaptionTextColorChanged -> System.EventHandler?
32+
[WFO5001]System.Windows.Forms.Form.FormCornerPreferenceChanged -> System.EventHandler?
33+
[WFO5001]virtual System.Windows.Forms.Form.OnFormBorderColorChanged(System.EventArgs! e) -> void
34+
[WFO5001]virtual System.Windows.Forms.Form.OnFormCaptionBackColorChanged(System.EventArgs! e) -> void
35+
[WFO5001]virtual System.Windows.Forms.Form.OnFormCaptionTextColorChanged(System.EventArgs! e) -> void
36+
[WFO5001]virtual System.Windows.Forms.Form.OnFormCornerPreferenceChanged(System.EventArgs! e) -> void

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

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ internal Control(bool autoInstallSyncContext) : base()
309309

310310
// Initialize Dpi to the value on the primary screen, we will have the correct value when the Handle is created.
311311
_deviceDpi = _oldDeviceDpi = ScaleHelper.InitialSystemDpi;
312+
312313
_window = new ControlNativeWindow(this);
313314
RequiredScalingEnabled = true;
314315
RequiredScaling = BoundsSpecified.All;
@@ -326,16 +327,21 @@ internal Control(bool autoInstallSyncContext) : base()
326327
| ControlStyles.Selectable,
327328
true);
328329

330+
// Allows inheriting controls to initialize their control-specific state, as well as
331+
// perform DPI-depending initialization of fields before the constructor code of the
332+
// respective (inheriting) control has a chance to run.
333+
// At this point, the control is not yet created, but every necessary state as well as
334+
// the base class properties are in place, because they have been initialized by the
335+
// static ctor of Control, which is called before all other instance constructors.
336+
InitializeControl(_deviceDpi);
337+
329338
// We baked the "default default" margin and min size into CommonProperties
330339
// so that in the common case the PropertyStore would be empty. If, however,
331340
// someone overrides these Default* methods, we need to write the default
332341
// value into the PropertyStore in the ctor.
333342

334343
// Changing the order of property accesses here can break existing code as these are all virtual properties.
335344
// Try to keep observable state for Control unchanged in this constructor to avoid nasty subtle bugs.
336-
337-
InitializeConstantsForInitialDpi(_deviceDpi);
338-
339345
if (DefaultMargin != CommonProperties.DefaultMargin)
340346
{
341347
Margin = DefaultMargin;
@@ -389,7 +395,8 @@ public Control(string? text) : this(null, text)
389395
/// <summary>
390396
/// Initializes a new instance of the <see cref="Control"/> class.
391397
/// </summary>
392-
public Control(string? text, int left, int top, int width, int height) : this(null, text, left, top, width, height)
398+
public Control(string? text, int left, int top, int width, int height)
399+
: this(null, text, left, top, width, height)
393400
{
394401
}
395402

@@ -411,6 +418,63 @@ public Control(Control? parent, string? text, int left, int top, int width, int
411418
Size = new Size(width, height);
412419
}
413420

421+
/// <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.
426+
/// </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)
475+
{
476+
}
477+
414478
/// <summary>
415479
/// Gets control Dpi awareness context value.
416480
/// </summary>
@@ -8035,15 +8099,6 @@ protected virtual void OnValidated(EventArgs e)
80358099
((EventHandler?)Events[s_validatedEvent])?.Invoke(this, e);
80368100
}
80378101

8038-
/// <summary>
8039-
/// This is called in the <see cref="Control"/> constructor before calculating the initial <see cref="Size"/>.
8040-
/// This gives a chance to initialize fields that will be used in calls to sizing related virtuals such as
8041-
/// <see cref="DefaultSize"/>, etc. The real size cannot be calculated until the handle is created as Windows
8042-
/// can have their own DPI setting. When the handle is created, <see cref="RescaleConstantsForDpi(int, int)"/>
8043-
/// is called.
8044-
/// </summary>
8045-
private protected virtual void InitializeConstantsForInitialDpi(int initialDpi) { }
8046-
80478102
/// <summary>
80488103
/// Invoked when the control handle is created and right before the top level parent control receives a
80498104
/// WM_DPICHANGED message.

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,38 @@ public virtual DialogResult DialogResult
146146
}
147147
}
148148

149+
#pragma warning disable WFO5001
150+
/// <summary>
151+
/// Defines, whether the control is owner-drawn. Based on this,
152+
/// the UserPaint flags get set, which in turn makes it later
153+
/// a Win32 controls, which we wrap (OwnerDraw == false) or if we
154+
/// draw ourselves. If the user wants to opt out of DarkMode, we cannot
155+
/// force System-Painting for FlatStyle.Standard, so we need to know here
156+
/// and now.
157+
/// </summary>
158+
private protected override bool OwnerDraw
159+
{
160+
get
161+
{
162+
if (Application.IsDarkModeEnabled
163+
// The SystemRenderer cannot render images. So, we flip to our
164+
// own DarkMode renderer, if we need to render images, except if
165+
&& GetStyle(ControlStyles.ApplyThemingImplicitly)
166+
// the user wants to opt out of implicit DarkMode rendering.
167+
&& Image is null
168+
// And this only counts for FlatStyle.Standard. For the rest,
169+
// we're using specific renderers, which check themselves, if
170+
// they need to apply Light- or DarkMode.
171+
&& FlatStyle == FlatStyle.Standard)
172+
{
173+
return false;
174+
}
175+
176+
return base.OwnerDraw;
177+
}
178+
}
179+
#pragma warning restore WFO5001
180+
149181
internal override bool SupportsUiaProviders => true;
150182

151183
/// <summary>

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,21 @@ protected ButtonBase()
7878

7979
SetStyle(ControlStyles.UserMouse | ControlStyles.UserPaint, OwnerDraw);
8080

81-
#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.
82-
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
83-
#pragma warning restore WFO5001
84-
8581
SetFlag(FlagUseMnemonic, true);
8682
SetFlag(FlagShowToolTip, false);
8783
}
8884

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+
8996
/// <summary>
9097
/// Gets or sets a value indicating whether the ellipsis character (...) appears at the right edge of the control,
9198
/// denoting that the control text extends beyond the specified length of the control.
@@ -617,7 +624,7 @@ internal virtual Rectangle OverChangeRectangle
617624
}
618625
}
619626

620-
internal bool OwnerDraw => FlatStyle != FlatStyle.System;
627+
private protected virtual bool OwnerDraw => FlatStyle != FlatStyle.System;
621628

622629
bool? ICommandBindingTargetProvider.PreviousEnabledStatus { get; set; }
623630

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public CheckBox() : base()
4949
TextAlign = ContentAlignment.MiddleLeft;
5050
}
5151

52+
protected override void InitializeControl(int deviceDpi) => ScaleConstants();
53+
5254
private bool AccObjDoDefaultAction { get; set; }
5355

5456
/// <summary>
@@ -256,8 +258,6 @@ protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNe
256258
ScaleConstants();
257259
}
258260

259-
private protected override void InitializeConstantsForInitialDpi(int initialDpi) => ScaleConstants();
260-
261261
private void ScaleConstants()
262262
{
263263
const int LogicalFlatSystemStylePaddingWidth = 25;

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,6 @@ protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNe
233233
ScaleConstants();
234234
}
235235

236-
private protected override void InitializeConstantsForInitialDpi(int initialDpi)
237-
=> ScaleConstants();
238-
239236
private void ScaleConstants()
240237
{
241238
const int LogicalFlatSystemStylePaddingWidth = 24;
@@ -244,6 +241,8 @@ private void ScaleConstants()
244241
_flatSystemStyleMinimumHeight = LogicalToDeviceUnits(LogicalFlatSystemStyleMinimumHeight);
245242
}
246243

244+
protected override void InitializeControl(int deviceDpi) => ScaleConstants();
245+
247246
internal override Size GetPreferredSizeCore(Size proposedConstraints)
248247
{
249248
if (FlatStyle != FlatStyle.System)

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,10 +387,6 @@ public DataGridView()
387387

388388
SetStyle(ControlStyles.SupportsTransparentBackColor, false);
389389

390-
#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.
391-
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
392-
#pragma warning restore WFO5001
393-
394390
// this class overrides GetPreferredSizeCore, let Control automatically cache the result
395391
SetExtendedState(ExtendedStates.UserPreferredSizeCache, true);
396392

@@ -492,6 +488,14 @@ public DataGridView()
492488
Invalidate();
493489
}
494490

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+
495499
[Browsable(false)]
496500
[EditorBrowsable(EditorBrowsableState.Advanced)]
497501
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]

src/System.Windows.Forms/System/Windows/Forms/Controls/Labels/Label.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@ public Label() : base()
7474

7575
SetStyle(ControlStyles.ResizeRedraw, true);
7676

77-
#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.
78-
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
79-
#pragma warning restore WFO5001
80-
8177
CommonProperties.SetSelfAutoSizeInDefaultLayout(this, true);
8278

8379
_labelState[s_stateFlatStyle] = (int)FlatStyle.Standard;
@@ -89,6 +85,14 @@ public Label() : base()
8985
_requestedWidth = Width;
9086
}
9187

88+
#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.
89+
protected override void InitializeControl(int deviceDpi)
90+
{
91+
base.InitializeControl(deviceDpi);
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+
9296
/// <summary>
9397
/// Indicates whether the control is automatically resized to fit its contents.
9498
/// </summary>

0 commit comments

Comments
 (0)