Skip to content

Commit 760890c

Browse files
Introduce modern ControlPaint.ControlButtonRenderer (for UpDown controls, DarkMode, FileOpener etc.).
1 parent 3117c53 commit 760890c

File tree

5 files changed

+478
-45
lines changed

5 files changed

+478
-45
lines changed

src/System.Windows.Forms/System/Windows/Forms/Controls/UpDown/UpDownBase.UpDownButtons.cs

Lines changed: 99 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Drawing;
5+
using System.Drawing.Imaging;
56
using System.Windows.Forms.VisualStyles;
67

78
namespace System.Windows.Forms;
89

910
public abstract partial class UpDownBase
1011
{
1112
/// <summary>
12-
/// A control representing the pair of buttons on the end of the upDownEdit control. This class handles
13-
/// drawing the updown buttons, and detecting mouse actions on these buttons. Acceleration on the buttons is
14-
/// handled. The control sends UpDownEventArgss to the parent UpDownBase class when a button is pressed, or
13+
/// A control representing the pair of buttons on the end of the up-down edit control. This class handles
14+
/// drawing the up-down buttons, and detecting mouse actions on these buttons. Acceleration on the buttons is
15+
/// handled. The control sends UpDownEventArgs to the parent UpDownBase class when a button is pressed, or
1516
/// when the acceleration determines that another event should be generated.
1617
/// </summary>
1718
internal partial class UpDownButtons : Control
@@ -28,13 +29,18 @@ internal partial class UpDownButtons : Control
2829
private Timer? _timer; // generates UpDown events
2930
private int _timerInterval; // milliseconds between events
3031

32+
private Bitmap? _cachedBitmap;
33+
3134
private bool _doubleClickFired;
3235

36+
/// <summary>
37+
/// Initializes a new instance of the <see cref="UpDownButtons"/> class.
38+
/// </summary>
39+
/// <param name="parent">The parent <see cref="UpDownBase"/> control.</param>
3340
internal UpDownButtons(UpDownBase parent) : base()
3441
{
3542
SetStyle(ControlStyles.Opaque | ControlStyles.FixedHeight | ControlStyles.FixedWidth, true);
3643
SetStyle(ControlStyles.Selectable, false);
37-
3844
_parent = parent;
3945
}
4046

@@ -47,9 +53,10 @@ public event UpDownEventHandler? UpDown
4753
remove => _upDownEventHandler -= value;
4854
}
4955

50-
/// <remarks>
51-
/// <para>Called when the mouse button is pressed - we need to start spinning the value of the updown.</para>
52-
/// </remarks>
56+
/// <summary>
57+
/// Called when the mouse button is pressed - we need to start spinning the value of the up-down control.
58+
/// </summary>
59+
/// <param name="e">The mouse event arguments.</param>
5360
private void BeginButtonPress(MouseEventArgs e)
5461
{
5562
int half_height = Size.Height / 2;
@@ -73,16 +80,17 @@ private void BeginButtonPress(MouseEventArgs e)
7380
// Generate UpDown event
7481
OnUpDown(new UpDownEventArgs((int)_pushed));
7582

76-
// Start the timer for new updown events
83+
// Start the timer for new up-down events
7784
StartTimer();
7885
}
7986

87+
/// <inheritdoc/>
8088
protected override AccessibleObject CreateAccessibilityInstance()
8189
=> new UpDownButtonsAccessibleObject(this);
8290

83-
/// <remarks>
84-
/// <para>Called when the mouse button is released - we need to stop spinning the value of the updown.</para>
85-
/// </remarks>
91+
/// <summary>
92+
/// Called when the mouse button is released - we need to stop spinning the value of the up-down control.
93+
/// </summary>
8694
private void EndButtonPress()
8795
{
8896
_pushed = ButtonID.None;
@@ -100,9 +108,10 @@ private void EndButtonPress()
100108

101109
/// <summary>
102110
/// Handles detecting mouse hits on the buttons. This method detects
103-
/// which button was hit (up or down), fires a updown event, captures
104-
/// the mouse, and starts a timer for repeated updown events.
111+
/// which button was hit (up or down), fires an up-down event, captures
112+
/// the mouse, and starts a timer for repeated up-down events.
105113
/// </summary>
114+
/// <param name="e">The mouse event arguments.</param>
106115
protected override void OnMouseDown(MouseEventArgs e)
107116
{
108117
// Begin spinning the value
@@ -128,9 +137,10 @@ protected override void OnMouseDown(MouseEventArgs e)
128137
_parent.OnMouseDown(_parent.TranslateMouseEvent(this, e));
129138
}
130139

140+
/// <inheritdoc/>
131141
protected override void OnMouseMove(MouseEventArgs e)
132142
{
133-
// If the mouse is captured by the buttons (i.e. an updown button
143+
// If the mouse is captured by the buttons (i.e. an up-down button
134144
// was pushed, and the mouse button has not yet been released),
135145
// determine the new state of the buttons depending on where
136146
// the mouse pointer has moved.
@@ -149,7 +159,7 @@ protected override void OnMouseMove(MouseEventArgs e)
149159
// Test if the mouse has moved outside the button area
150160
if (rect.Contains(e.X, e.Y))
151161
{
152-
// Inside button, repush the button if necessary
162+
// Inside button, re-push the button if necessary
153163
if (_pushed != _captured)
154164
{
155165
// Restart the timer
@@ -163,11 +173,11 @@ protected override void OnMouseMove(MouseEventArgs e)
163173
{
164174
// Outside button
165175
//
166-
// Retain the capture, but pop the button up whilst the mouse
176+
// Retain the capture, but pop the button up while the mouse
167177
// remains outside the button and the mouse button remains pressed.
168178
if (_pushed != ButtonID.None)
169179
{
170-
// Stop the timer for updown events
180+
// Stop the timer for up-down events
171181
StopTimer();
172182

173183
_pushed = ButtonID.None;
@@ -204,6 +214,7 @@ protected override void OnMouseMove(MouseEventArgs e)
204214
/// <summary>
205215
/// Handles detecting when the mouse button is released.
206216
/// </summary>
217+
/// <param name="e">The mouse event arguments.</param>
207218
protected override void OnMouseUp(MouseEventArgs e)
208219
{
209220
if (!_parent.ValidationCancelled && e.Button == MouseButtons.Left)
@@ -239,6 +250,7 @@ protected override void OnMouseUp(MouseEventArgs e)
239250
_parent.OnMouseUp(me);
240251
}
241252

253+
/// <inheritdoc/>
242254
protected override void OnMouseLeave(EventArgs e)
243255
{
244256
_mouseOver = ButtonID.None;
@@ -250,12 +262,40 @@ protected override void OnMouseLeave(EventArgs e)
250262
/// <summary>
251263
/// Handles painting the buttons on the control.
252264
/// </summary>
265+
/// <param name="e">The paint event arguments.</param>
266+
#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.
253267
protected override void OnPaint(PaintEventArgs e)
254268
{
255269
int half_height = ClientSize.Height / 2;
256270

257271
// Draw the up and down buttons
258-
if (Application.RenderWithVisualStyles)
272+
if (Application.IsDarkModeEnabled || !Application.RenderWithVisualStyles)
273+
{
274+
Graphics cachedGraphics = EnsureCachedBitmap(
275+
_parent._defaultButtonsWidth,
276+
ClientSize.Height);
277+
278+
ControlPaint.DrawScrollButton(
279+
cachedGraphics,
280+
new Rectangle(0, 0, _parent._defaultButtonsWidth, half_height),
281+
ScrollButton.Up,
282+
_pushed == ButtonID.Up
283+
? ButtonState.Pushed
284+
: (Enabled ? ButtonState.Normal : ButtonState.Inactive));
285+
286+
ControlPaint.DrawScrollButton(
287+
cachedGraphics,
288+
new Rectangle(0, half_height, _parent._defaultButtonsWidth, half_height),
289+
ScrollButton.Down,
290+
_pushed == ButtonID.Down
291+
? ButtonState.Pushed
292+
: (Enabled ? ButtonState.Normal : ButtonState.Inactive));
293+
294+
e.GraphicsInternal.DrawImageUnscaled(
295+
_cachedBitmap,
296+
new Point(0, 0));
297+
}
298+
else
259299
{
260300
VisualStyleRenderer vsr = new(_mouseOver == ButtonID.Up
261301
? VisualStyleElement.Spin.Up.Hot
@@ -296,20 +336,7 @@ protected override void OnPaint(PaintEventArgs e)
296336
new Rectangle(0, half_height, _parent._defaultButtonsWidth, half_height),
297337
HWNDInternal);
298338
}
299-
else
300-
{
301-
ControlPaint.DrawScrollButton(
302-
e.GraphicsInternal,
303-
new Rectangle(0, 0, _parent._defaultButtonsWidth, half_height),
304-
ScrollButton.Up,
305-
_pushed == ButtonID.Up ? ButtonState.Pushed : (Enabled ? ButtonState.Normal : ButtonState.Inactive));
306-
307-
ControlPaint.DrawScrollButton(
308-
e.GraphicsInternal,
309-
new Rectangle(0, half_height, _parent._defaultButtonsWidth, half_height),
310-
ScrollButton.Down,
311-
_pushed == ButtonID.Down ? ButtonState.Pushed : (Enabled ? ButtonState.Normal : ButtonState.Inactive));
312-
}
339+
#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.
313340

314341
if (half_height != (ClientSize.Height + 1) / 2)
315342
{
@@ -329,12 +356,45 @@ protected override void OnPaint(PaintEventArgs e)
329356
base.OnPaint(e);
330357
}
331358

359+
/// <summary>
360+
/// Ensures that the Bitmap has the correct size and returns the Graphics object from that Bitmap.
361+
/// </summary>
362+
/// <param name="width"></param>
363+
/// <param name="height"></param>
364+
/// <returns></returns>
365+
[MemberNotNull(nameof(_cachedBitmap))]
366+
private Graphics EnsureCachedBitmap(int width, int height)
367+
{
368+
if (_cachedBitmap is null || _cachedBitmap.Size != new Size(width, height))
369+
{
370+
_cachedBitmap?.Dispose();
371+
_cachedBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
372+
}
373+
374+
return Graphics.FromImage(_cachedBitmap);
375+
}
376+
377+
protected override void Dispose(bool disposing)
378+
{
379+
base.Dispose(disposing);
380+
if (disposing)
381+
{
382+
_cachedBitmap?.Dispose();
383+
_cachedBitmap = null;
384+
_timer?.Dispose();
385+
_timer = null;
386+
_upDownEventHandler = null;
387+
}
388+
}
389+
332390
/// <summary>
333391
/// Occurs when the UpDown buttons are pressed and when the acceleration timer tick event is raised.
334392
/// </summary>
393+
/// <param name="upevent">The up-down event arguments.</param>
335394
protected virtual void OnUpDown(UpDownEventArgs upevent)
336395
=> _upDownEventHandler?.Invoke(this, upevent);
337396

397+
/// <inheritdoc/>
338398
internal override void ReleaseUiaProvider(HWND handle)
339399
{
340400
if (IsAccessibilityObjectCreated
@@ -348,7 +408,7 @@ internal override void ReleaseUiaProvider(HWND handle)
348408
}
349409

350410
/// <summary>
351-
/// Starts the timer for generating updown events
411+
/// Starts the timer for generating up-down events.
352412
/// </summary>
353413
protected void StartTimer()
354414
{
@@ -369,7 +429,7 @@ protected void StartTimer()
369429
}
370430

371431
/// <summary>
372-
/// Stops the timer for generating updown events
432+
/// Stops the timer for generating up-down events.
373433
/// </summary>
374434
protected void StopTimer()
375435
{
@@ -383,11 +443,14 @@ protected void StopTimer()
383443
_parent.OnStopTimer();
384444
}
385445

446+
/// <inheritdoc/>
386447
internal override bool SupportsUiaProviders => true;
387448

388449
/// <summary>
389-
/// Generates updown events when the timer calls this function.
450+
/// Generates up-down events when the timer calls this function.
390451
/// </summary>
452+
/// <param name="source">The source of the event.</param>
453+
/// <param name="args">The event arguments.</param>
391454
private void TimerHandler(object? source, EventArgs args)
392455
{
393456
// Make sure we've got mouse capture
@@ -397,7 +460,7 @@ private void TimerHandler(object? source, EventArgs args)
397460
return;
398461
}
399462

400-
// onUpDown method calls customer's ValueCHanged event handler which might enter the message loop and
463+
// OnUpDown method calls customer's ValueChanged event handler which might enter the message loop and
401464
// process the mouse button up event, which results in timer being disposed
402465
OnUpDown(new UpDownEventArgs((int)_pushed));
403466

src/System.Windows.Forms/System/Windows/Forms/Controls/UpDown/UpDownBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,12 @@ public override ContextMenuStrip? ContextMenuStrip
211211
protected override AccessibleObject CreateAccessibilityInstance()
212212
=> new UpDownBaseAccessibleObject(this);
213213

214+
#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.
214215
protected override CreateParams CreateParams
215216
{
216217
get
217218
{
218-
#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.
219219
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
220-
#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.
221220

222221
CreateParams cp = base.CreateParams;
223222

@@ -238,6 +237,7 @@ protected override CreateParams CreateParams
238237
return cp;
239238
}
240239
}
240+
#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.
241241

242242
/// <summary>
243243
/// Deriving classes can override this to configure a default size for their control.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Windows.Forms;
5+
6+
public static unsafe partial class ControlPaint
7+
{
8+
[Flags]
9+
internal enum ModernControlButton
10+
{
11+
Empty = 0x0,
12+
Up = 0x1,
13+
Down = 0x2,
14+
UpDown = 0x3,
15+
Right = 0x4,
16+
Left = 0x8,
17+
LeftRight = 0xC,
18+
Ellipse = 0x10,
19+
SingleBorder = 0x10000,
20+
RoundedBorder = 0x20000
21+
}
22+
}

0 commit comments

Comments
 (0)