-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathToggleButton.cs
More file actions
360 lines (321 loc) · 13.6 KB
/
Copy pathToggleButton.cs
File metadata and controls
360 lines (321 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
//*********************************************************************************************************************
// File Name: ToggleButton.cs
// Description: Implementation of the Toggle Button control
//
// Copyright (C) 2023 Mike Pullen.
// Licensed under the MIT License. See the LICENSE file in the repository root.
//
// Revision History:
//====================================================================================================================
// 2023/12/02 - Mike Pullen - Original implementation.
// 2026/08/31 - Mike Pullen - Corrected spelling of the DrawingStyles.Hollow enumerator.
// 2026/08/31 - Mike Pullen - Released under the MIT License.
// 2026/08/31 - Mike Pullen - Disposed of the GDI+ drawing resources, opted in to resize repainting and
// double buffering, and added designer metadata to the properties.
// 2026/09/08 - Mike Pullen - Took the surface behind the switch from the control's own BackColor rather than
// clearing with the parent's, which needed a parent and blacked out the corners
// when the parent was transparent.
// 2026/09/08 - Mike Pullen - Drew the background out to the control's right edge and inset the toggle by the
// same amount on every side, so no notch is left beside the rounded end and the
// toggle no longer looks to be breaking out of the background.
//*********************************************************************************************************************
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace CommonControls
{
/// <summary>
/// Toggle Button control
/// </summary>
public class ToggleButton: CheckBox
{
#region Type Definitions
/// <summary>
/// Drawing styles for the control
/// </summary>
public enum DrawingStyles
{
/// <summary>
/// The background of the control is filled
/// </summary>
Solid,
/// <summary>
/// Only the outline of the background is drawn
/// </summary>
Hollow,
}
#endregion
#region Contructors and Destructor
/// <summary>
/// Default constructor
/// </summary>
public ToggleButton()
{
// Repaint the whole surface on resize, and draw off screen, so resizing leaves no artifacts and does not flicker.
// SupportsTransparentBackColor allows a consumer to set BackColor to Color.Transparent and have whatever the
// control is sitting on show through the corners the rounded switch does not reach.
this.SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint | ControlStyles.SupportsTransparentBackColor, true);
// Minimum size required to ensure it is drawn correctly
this.MinimumSize = new Size(50, 25);
}
#endregion
#region Event Handlers
/// <summary>
/// Override for the paint event
/// </summary>
/// <param name="paintEvent">IN - The paint event arguments</param>
protected override void OnPaint(PaintEventArgs paintEvent)
{
// Paint the surface the switch sits on. ButtonBase paints its own background from OnPaint rather than from
// OnPaintBackground, and this override replaces that, so nothing else clears the surface: left alone, the
// previous frame stays underneath and every repaint blends the new switch's antialiased edge over the old
// one, which builds a fringe of the previous colour around the switch.
if (Color.Transparent == this.BackColor)
{
// Let the framework draw whatever is behind the control through it
base.OnPaintBackground(paintEvent);
}
// Otherwise the consumer has said what the switch is sitting on
else
{
// Fill with that colour. This is the control's own BackColor rather than its parent's: taking the
// parent's ignored what the consumer had asked for, required a parent to exist at paint time, and
// turned the corners black whenever the parent was transparent, because clearing with a transparent
// colour writes it into the buffer rather than letting anything through.
paintEvent.Graphics.Clear(this.BackColor);
}
// Set the smoothing mode
paintEvent.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set the colors based on the state of the toggle
Color backgroundColor;
Color toggleColor;
// If the control is disabled
if (false == this.Enabled)
{
// Use the disabled colors
backgroundColor = m_DisabledBackground;
toggleColor = m_DisabledToggle;
}
// Otherwise, if the toggle is ON
else if (this.Checked)
{
// Use the ON colors
backgroundColor = m_OnBackground;
toggleColor = m_OnToggle;
}
// Otherwise, the toggle is OFF
else
{
// Use the OFF colors
backgroundColor = m_OffBackground;
toggleColor = m_OffToggle;
}
// Get the rectangle for the toggle
Rectangle toggleRectangle = GetToggleRectangle();
// Create the drawing resources, disposing of them once the control has been drawn
using (GraphicsPath backgroundPath = GetBackgroundPath())
using (SolidBrush backgroundBrush = new SolidBrush(backgroundColor))
using (SolidBrush toggleBrush = new SolidBrush(toggleColor))
{
// Draw the background based on the selected style
if (DrawingStyles.Solid == m_Style)
{
// Fill the background
paintEvent.Graphics.FillPath(backgroundBrush, backgroundPath);
}
else
{
// Only draw the outline for the background
using (Pen backgroundPen = new Pen(backgroundBrush, 1))
{
paintEvent.Graphics.DrawPath(backgroundPen, backgroundPath);
}
}
// Draw the toggle
paintEvent.Graphics.FillEllipse(toggleBrush, toggleRectangle);
}
}
#endregion
#region Methods
/// <summary>
/// Creates the path for the control
/// </summary>
/// <returns>The rounded path used to draw the background</returns>
private GraphicsPath GetBackgroundPath()
{
// The rounded ends are as wide as the control is tall, so the background fills the control and
// meets its right edge. Ending short of it leaves a sliver of the control uncovered, which shows
// as a notch beside the rounded end rather than as nothing at all.
int iEndDiameter = this.Height - 1;
// Create the left arc
Point LeftArcStart = new Point(0, 0);
Size ArcSize = new Size(iEndDiameter, iEndDiameter);
Rectangle LeftArc = new Rectangle(LeftArcStart, ArcSize);
// Create the right arc, its far edge on the last pixel of the control
Point RightArcStart = new Point(this.Width - iEndDiameter - 1, 0);
Rectangle RightArc = new Rectangle(RightArcStart, ArcSize);
// Create the path for the toggle
GraphicsPath TogglePath = new GraphicsPath();
TogglePath.StartFigure();
TogglePath.AddArc(LeftArc, 90, 180);
TogglePath.AddArc(RightArc, 270, 180);
TogglePath.CloseFigure();
return TogglePath;
}
/// <summary>
/// Gets the rectangle for the toggle
/// </summary>
/// <returns>The bounding rectangle of the toggle for the current state</returns>
private Rectangle GetToggleRectangle()
{
// The toggle sits inside the background by the same amount on every side. Deriving both the size
// and the position from that one inset keeps the gap even all the way round; setting them
// separately left the gap at the end the toggle had travelled to narrower than the others, and
// an uneven gap of a pixel or two reads as the toggle breaking out of the background.
int iBackgroundHeight = this.Height - 1;
int iToggleDiameter = iBackgroundHeight - (2 * m_iTOGGLE_INSET);
Size toggleSize = new Size(iToggleDiameter, iToggleDiameter);
// Set the position based on the toggle state
Point toggleStart = new Point(m_iTOGGLE_INSET, m_iTOGGLE_INSET);
// If the toggle is on
if (this.Checked)
{
// Set the position to the right, the same inset in from the background's right edge
toggleStart.X = (this.Width - 1) - m_iTOGGLE_INSET - iToggleDiameter;
}
// Otherwise, the toggle is off and stays where it was placed, on the left
// Create and return the rectangle
Rectangle toggleRectangle = new Rectangle(toggleStart, toggleSize);
return toggleRectangle;
}
#endregion
#region Properties
/// <summary>
/// Background color when the toggle is in the OFF state
/// </summary>
[Category("Appearance")]
[Description("Background color when the toggle is in the OFF state")]
[DefaultValue(typeof(Color), "Black")]
public Color OffBackground
{
get => m_OffBackground;
set
{
m_OffBackground = value;
this.Invalidate();
}
}
/// <summary>
/// Toggle color when the toggle is in the OFF state
/// </summary>
[Category("Appearance")]
[Description("Toggle color when the toggle is in the OFF state")]
[DefaultValue(typeof(Color), "White")]
public Color OffToggle
{
get => m_OffToggle;
set
{
m_OffToggle = value;
this.Invalidate();
}
}
/// <summary>
/// Background color when the toggle is in the ON state
/// </summary>
[Category("Appearance")]
[Description("Background color when the toggle is in the ON state")]
[DefaultValue(typeof(Color), "Black")]
public Color OnBackground
{
get => m_OnBackground;
set
{
m_OnBackground = value;
this.Invalidate();
}
}
/// <summary>
/// Toggle color when the toggle is in the ON state
/// </summary>
[Category("Appearance")]
[Description("Toggle color when the toggle is in the ON state")]
[DefaultValue(typeof(Color), "White")]
public Color OnToggle
{
get => m_OnToggle;
set
{
m_OnToggle = value;
this.Invalidate();
}
}
/// <summary>
/// Background color when the control is disabled
/// </summary>
[Category("Appearance")]
[Description("Background color when the control is disabled")]
[DefaultValue(typeof(Color), "Gray")]
public Color DisabledBackground
{
get => m_DisabledBackground;
set
{
m_DisabledBackground = value;
this.Invalidate();
}
}
/// <summary>
/// Toggle color when the control is disabled
/// </summary>
[Category("Appearance")]
[Description("Toggle color when the control is disabled")]
[DefaultValue(typeof(Color), "LightGray")]
public Color DisabledToggle
{
get => m_DisabledToggle;
set
{
m_DisabledToggle = value;
this.Invalidate();
}
}
/// <summary>
/// Drawing style for the control
/// </summary>
[Category("Appearance")]
[Description("Drawing style for the control")]
[DefaultValue(DrawingStyles.Solid)]
public DrawingStyles Style
{
get => m_Style;
set
{
m_Style = value;
this.Invalidate();
}
}
/// <summary>
/// Override for the Text property to remove set
/// </summary>
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override string Text { get => base.Text; }
#endregion
#region Data Members
// Button colors
private Color m_OffBackground = Color.Black;
private Color m_OffToggle = Color.White;
private Color m_OnBackground = Color.Black;
private Color m_OnToggle = Color.White;
private Color m_DisabledBackground = Color.Gray;
private Color m_DisabledToggle = Color.LightGray;
// Drawing style
private DrawingStyles m_Style = DrawingStyles.Solid;
// How far the toggle sits inside the background, on every side
private const int m_iTOGGLE_INSET = 3;
#endregion
}
}