@@ -337,6 +337,44 @@ protected internal bool IsDefault
337
337
/// <summary>
338
338
/// Gets or sets the flat style appearance of the button control.
339
339
/// </summary>
340
+ /// <remarks>
341
+ /// <para>
342
+ /// The <see cref="FlatStyle"/> property determines how the button is rendered. The following values are supported:
343
+ /// </para>
344
+ /// <list type="bullet">
345
+ /// <item>
346
+ /// <term><see cref="FlatStyle.Standard"/></term>
347
+ /// <description>
348
+ /// The default style. The button is not wrapping the system button. It is rendered using the StandardButton adapter.
349
+ /// VisualStyleRenderer from the OS is used for certain parts, which may have issues in high-resolution scenarios.
350
+ /// Dark mode works to some extent, but improvements are needed.
351
+ /// </description>
352
+ /// </item>
353
+ /// <item>
354
+ /// <term><see cref="FlatStyle.Popup"/></term>
355
+ /// <description>
356
+ /// The button is fully owner-drawn. No rendering is delegated to the OS, not even VisualStyleRenderer.
357
+ /// This style works well in dark mode and is fully controlled by the application.
358
+ /// 3D effects are expected but may not be rendered; consider revisiting for meaningful styling.
359
+ /// </description>
360
+ /// </item>
361
+ /// <item>
362
+ /// <term><see cref="FlatStyle.Flat"/></term>
363
+ /// <description>
364
+ /// The button is fully owner-drawn, with no OS calls or VisualStyleRenderer usage.
365
+ /// This fits modern design language and works well in dark mode.
366
+ /// </description>
367
+ /// </item>
368
+ /// <item>
369
+ /// <term><see cref="FlatStyle.System"/></term>
370
+ /// <description>
371
+ /// The button wraps the system button and is not owner-drawn.
372
+ /// No <c>OnPaint</c>, <c>OnPaintBackground</c>, or adapter is involved.
373
+ /// In dark mode, this style is used as a fallback for Standard-style buttons.
374
+ /// </description>
375
+ /// </item>
376
+ /// </list>
377
+ /// </remarks>
340
378
[ SRCategory ( nameof ( SR . CatAppearance ) ) ]
341
379
[ DefaultValue ( FlatStyle . Standard ) ]
342
380
[ Localizable ( true ) ]
@@ -356,6 +394,7 @@ public FlatStyle FlatStyle
356
394
_flatStyle = value ;
357
395
LayoutTransaction . DoLayoutIf ( AutoSize , ParentInternal , this , PropertyNames . FlatStyle ) ;
358
396
Invalidate ( ) ;
397
+
359
398
UpdateOwnerDraw ( ) ;
360
399
}
361
400
}
@@ -409,12 +448,18 @@ public Image? Image
409
448
StopAnimate ( ) ;
410
449
411
450
_image = value ;
451
+
412
452
if ( _image is not null )
413
453
{
414
454
ImageIndex = ImageList . Indexer . DefaultIndex ;
415
455
ImageList = null ;
416
456
}
417
457
458
+ // If we have an Image, for some flat styles we need to change the rendering approach from
459
+ // being a wrapper around the Win32 control to being owner-drawn. The Win32 control does not
460
+ // support images in the same flexible way as we need it.
461
+ UpdateOwnerDraw ( ) ;
462
+
418
463
LayoutTransaction . DoLayoutIf ( AutoSize , ParentInternal , this , PropertyNames . Image ) ;
419
464
Animate ( ) ;
420
465
Invalidate ( ) ;
@@ -620,7 +665,14 @@ internal virtual Rectangle OverChangeRectangle
620
665
}
621
666
}
622
667
623
- private protected virtual bool OwnerDraw => FlatStyle != FlatStyle . System ;
668
+ /// <summary>
669
+ /// OwnerDraw ultimately determines, if we're wrapping the respective Win32 control
670
+ /// (Button, CheckBox, RadioButton - OwnerDraw == false) or not. When we're not OwnerDraw,
671
+ /// both Light- and DarkMode are (and can be) rendered by the System, but then there is
672
+ /// no image rendering, and no OnPaint. This is the original behavior of the Win32 controls.
673
+ /// </summary>
674
+ private protected virtual bool OwnerDraw =>
675
+ FlatStyle != FlatStyle . System ;
624
676
625
677
bool ? ICommandBindingTargetProvider . PreviousEnabledStatus { get ; set ; }
626
678
@@ -964,40 +1016,35 @@ internal override Size GetPreferredSizeCore(Size proposedConstraints)
964
1016
return LayoutUtils . UnionSizes ( preferredSize + Padding . Size , MinimumSize ) ;
965
1017
}
966
1018
967
- #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.
1019
+ /// <summary>
1020
+ /// Returns an adapter for Rendering one of the FlatStyles. Note, that we always render
1021
+ /// buttons ourselves, except when the User explicitly requests FlatStyle.System rendering!
1022
+ /// </summary>
968
1023
internal ButtonBaseAdapter Adapter
969
1024
{
970
1025
get
971
1026
{
972
1027
if ( _adapter is null
973
1028
|| FlatStyle != _cachedAdapterType )
974
1029
{
975
- if ( Application . IsDarkModeEnabled && this is Button )
1030
+ switch ( FlatStyle )
976
1031
{
977
- _adapter = CreateDarkModeAdapter ( ) ;
1032
+ case FlatStyle . Standard :
1033
+ _adapter = CreateStandardAdapter ( ) ;
1034
+ break ;
1035
+ case FlatStyle . Popup :
1036
+ _adapter = CreatePopupAdapter ( ) ;
1037
+ break ;
1038
+ case FlatStyle . Flat :
1039
+ _adapter = CreateFlatAdapter ( ) ;
1040
+ break ;
1041
+ default :
1042
+ Debug . Fail ( $ "Unsupported FlatStyle: \" { FlatStyle } \" ") ;
1043
+ break ;
978
1044
}
979
- else
980
- {
981
- switch ( FlatStyle )
982
- {
983
- case FlatStyle . Standard :
984
- _adapter = CreateStandardAdapter ( ) ;
985
- break ;
986
- case FlatStyle . Popup :
987
- _adapter = CreatePopupAdapter ( ) ;
988
- break ;
989
- case FlatStyle . Flat :
990
- _adapter = CreateFlatAdapter ( ) ;
991
- break ;
992
- default :
993
- Debug . Fail ( $ "Unsupported FlatStyle: \" { FlatStyle } \" ") ;
994
- break ;
995
- }
996
1045
997
- _cachedAdapterType = FlatStyle ;
998
- }
1046
+ _cachedAdapterType = FlatStyle ;
999
1047
}
1000
- #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.
1001
1048
1002
1049
return _adapter ;
1003
1050
}
@@ -1021,14 +1068,6 @@ internal virtual ButtonBaseAdapter CreateStandardAdapter()
1021
1068
return null ;
1022
1069
}
1023
1070
1024
- internal virtual ButtonBaseAdapter CreateDarkModeAdapter ( )
1025
- {
1026
- // When a button-derived class does not have a dedicated DarkMode adapter implementation,
1027
- // we're falling back to the standard adapter, to not _force_ the derived class to implement
1028
- // a dark mode adapter.
1029
- return CreateStandardAdapter ( ) ;
1030
- }
1031
-
1032
1071
internal virtual StringFormat CreateStringFormat ( )
1033
1072
{
1034
1073
if ( Adapter is null )
@@ -1255,7 +1294,11 @@ private void SetFlag(int flag, bool value)
1255
1294
1256
1295
private bool ShouldSerializeImage ( ) => _image is not null ;
1257
1296
1258
- private void UpdateOwnerDraw ( )
1297
+ // Indicates whether this control uses owner drawing, enabling UserPaint and determining
1298
+ // if we wrap the native Win32 control (OwnerDraw == false) or render it ourselves.
1299
+ // Also needed to detect a Dark Mode opt-out for FlatStyle.Standard when system painting
1300
+ // cannot be forced.
1301
+ private protected void UpdateOwnerDraw ( )
1259
1302
{
1260
1303
if ( OwnerDraw != GetStyle ( ControlStyles . UserPaint ) )
1261
1304
{
0 commit comments