This is an alternative for the standard numeric UpDown control for Windows Forms.
Introduction
This custom control is just a single file, CustomUpDown.cs (see code at the bottom of the article) which can easily be added to your project.
The source code including a demo project and a DumpImageToCode
utility is available on GitHub at https://github.com/RickZeeland/WinForms_NumUpDown.
Tested with .NET 6 but should also work in .NET 4.8.
Based on Control
, which is the most powerful way to create a Control
, but not the easiest.
More information about different types of custom controls can be found at this link.
Background
The main reason for writing this custom control was layout problems experienced with the standard numeric UpDown
control after converting complex forms from .NET 4.8 to .NET 6.
I also disliked the tiny buttons of the standard numeric UpDown
control.
Using the Code
This control is just a single file that can easily be added to your Winforms project.
It functions mostly the same as the standard numeric UpDown
control and also supports the ValueChanged
event, example code from the demo Form1
:
private void customUpDown1_ValueChanged(object sender, EventArgs e)
{
var fontsize = (float)this.customUpDown1.Value;
this.Font = new Font(this.Font.FontFamily, fontsize);
}
It also features:
- Optional label
- Repeat buttons
- Arrow or +- style
- Color properties for label, textbox and buttons
Note that the repeat button functionality is implemented without using a Timer
.
Source Code
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace WinForms_NumUpDown
{
public class CustomUpDown : Control
{
public event EventHandler ValueChanged;
[Category("CustomUpDown"),
Description("Gets or sets the default value in textBox"),
RefreshProperties(RefreshProperties.Repaint)]
public decimal Value
{
get
{
return valueLocal;
}
set
{
if (DecimalPlaces == 0)
{
valueLocal = value;
}
else if (DecimalPlaces > 0 && DecimalPlaces < 7)
{
valueLocal = Math.Round(value, DecimalPlaces);
}
Invalidate();
}
}
[Category("CustomUpDown"),
Description("Gets or sets the decimal places (max 6)")]
public int DecimalPlaces { get; set; } = 0;
[Category("CustomUpDown"), Description("Gets or sets the increment value")]
public decimal Increment { get; set; } = 1;
[Category("CustomUpDown"),
Description("Gets or sets the Maximum value (max 25 significant digits)")]
public decimal Maximum { get; set; } = 100;
[Category("CustomUpDown"), Description("Gets or sets the Minimum value")]
public decimal Minimum { get; set; } = 0;
[Category("CustomUpDown"),
Description("Gets or sets the button repeat delay in milliseconds")]
public int RepeatDelayMs { get; set; } = 100;
[Category("CustomUpDown"),
Description("Gets or sets the button fore color"),
RefreshProperties(RefreshProperties.Repaint)]
public Color ButtonForeColor
{
get { return buttonForeColor; }
set
{
buttonForeColor = value;
Invalidate();
}
}
[Category("CustomUpDown"),
Description("Gets or sets the button back color"),
RefreshProperties(RefreshProperties.Repaint)]
public Color ButtonBackColor
{
get { return buttonBackColor; }
set
{
buttonBackColor = value;
Invalidate();
}
}
[Category("CustomUpDown"),
Description("Gets or sets the textbox back color (default White),
use ForeColor for the text color"),
RefreshProperties(RefreshProperties.Repaint)]
public Color TextBackColor
{
get { return textBackColor; }
set
{
textBackColor = value;
Invalidate();
}
}
public enum ButtonDisplay { Arrows, PlusMinus, PlusMinus2, PlusMinus3 };
[Category("CustomUpDown"),
Description("Gets or sets the Button display style"),
RefreshProperties(RefreshProperties.Repaint)]
public ButtonDisplay ButtonStyle
{
get { return buttonstyle; }
set
{
buttonstyle = value;
if (buttonstyle == ButtonDisplay.PlusMinus)
{
buttonUp.Text = "+";
buttonDown.Text = "-";
buttonUp.BackgroundImage = emptyIcon;
buttonDown.BackgroundImage = emptyIcon;
}
else if (buttonstyle == ButtonDisplay.PlusMinus2)
{
if (plus2Icon == null)
{
plus2Icon = GetImageFromHexString(plus2IconCode);
}
if (minus2Icon == null)
{
minus2Icon = GetImageFromHexString(minus2IconCode);
}
buttonUp.Text = "";
buttonDown.Text = "";
buttonUp.BackgroundImage = plus2Icon;
buttonUp.BackgroundImageLayout = ImageLayout.Zoom;
buttonDown.BackgroundImage = minus2Icon;
buttonDown.BackgroundImageLayout = ImageLayout.Zoom;
}
else if (buttonstyle == ButtonDisplay.PlusMinus3)
{
if (plus3Icon == null)
{
plus3Icon = GetImageFromHexString(plus3IconCode);
}
if (minus3Icon == null)
{
minus3Icon = GetImageFromHexString(minus3IconCode);
}
buttonUp.Text = "";
buttonDown.Text = "";
buttonUp.BackgroundImage = plus3Icon;
buttonUp.BackgroundImageLayout = ImageLayout.Zoom;
buttonDown.BackgroundImage = minus3Icon;
buttonDown.BackgroundImageLayout = ImageLayout.Zoom;
}
else
{
buttonUp.Text = "▲";
buttonDown.Text = "▼";
buttonUp.BackgroundImage = emptyIcon;
buttonDown.BackgroundImage = emptyIcon;
}
Invalidate();
}
}
[Category("CustomUpDown"),
Description("Gets or sets the optional label text
(with ForeColor as the color)"),
RefreshProperties(RefreshProperties.Repaint)]
public new string Text
{
get { return base.Text; }
set
{
if (!value.StartsWith("customUpDown"))
{
label1.Text = value;
}
base.Text = value;
Invalidate();
}
}
private ButtonDisplay buttonstyle;
private Label label1;
private bool label1Init;
private TextBox textBox;
private Button buttonUp;
private Button buttonDown;
private Color buttonForeColor;
private Color buttonBackColor;
private Color textBackColor;
private decimal valueLocal;
private Image emptyIcon;
private string plus2IconCode = "89 50 4E 47 0D 0A 1A 0A 00 00 00
0D 49 48 44 52 00 00 00 1A 00 00 00 1A 08 06 00 00 00 A9 4A 4C CE
00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 04 67 41 4D 41 00
00 B1 8F 0B FC 61 05 00 00 00 09 70 48 59 73 00 00 0E C3 00 00 0E
C3 01 C7 6F A8 64 00 00 00 58 49 44 41 54 48 4B ED 93 51 0A 00 21
08 44 BD FF C5 6A F7 52 E5 C2 06 11 1A 31 62 5F F3 E0 FD 88 3A 5F
23 E4 16 AF DA 7E EB 37 C8 62 84 0C D3 60 10 0C 83 5C E6 9E 44 DD
F6 CC 3A 88 E8 62 2D 47 74 79 54 EB 00 B1 A8 30 EB B3 34 18 04 C3
20 98 B9 67 A1 9E 90 43 44 3A 27 6F 8D BD 18 59 7F D5 00 00 00 00
49 45 4E 44 AE 42 60 82";
private Image plus2Icon;
private string minus2IconCode = "89 50 4E 47 0D 0A 1A 0A 00 00 00
0D 49 48 44 52 00 00 00 1A 00 00 00 1A 08 06 00 00 00 A9 4A 4C CE
00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 04 67 41 4D 41 00
00 B1 8F 0B FC 61 05 00 00 00 09 70 48 59 73 00 00 0E C3 00 00 0E
C3 01 C7 6F A8 64 00 00 00 38 49 44 41 54 48 4B ED CB BB 0D 00 20
0C 03 D1 EC BF 18 9F A5 C0 03 38 0D 41 82 E2 9E 74 9D 1D 00 F0 8F
A9 D6 A5 BA 4A B9 43 A5 94 1B 57 4A 0D E5 0E 27 35 05 00 EF 45 6C
09 E6 4E D7 AD BA 2E B0 00 00 00 00 49 45 4E 44 AE 42 60 82";
private Image minus2Icon;
private string plus3IconCode = "89 50 4E 47 0D 0A 1A 0A 00 00 00 0D
49 48 44 52 00 00 00 28 00 00 00 28 08 06 00 00 00 8C FE B8 6D 00 00
00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 04 67 41 4D 41 00 00 B1 8F
0B FC 61 05 00 00 00 09 70 48 59 73 00 00 0E C3 00 00 0E C3 01 C7 6F
A8 64 00 00 03 4B 49 44 41 54 58 47 ED 58 4D 6B DB 40 10 9D 9A D0 53
29 A5 84 D0 43 7E 41 E9 A9 07 53 7A 31 04 1F 7A F2 A1 C4 21 B6 64 FB
50 28 A5 90 D6 92 56 3E D6 35 39 04 D3 3F DD 79 33 3B D6 07 AA 22 3B
32 A8 E0 07 83 E4 DD 99 37 E3 9D D9 0F 2D 9D 70 C2 7F 86 9E 7F D2 98
BE BF 98 50 D4 0F 28 0A E7 E4 DC 8C E2 FB 39 25 5B 88 BE 3B 37 97 BE
A8 0F 5D 6F 06 EC 38 DA 04 48 85 38 A4 E4 22 A0 E5 0D 07 B0 59 50 FA
A7 89 40 37 A0 E4 06 B6 E0 60 EC F8 DA 80 10 0D 68 7D 16 50 3C 9A 53
BA CD 1C A7 DB 90 62 17 52 74 3B A5 E5 90 DF 3F 42 F0 CE 23 C9 01 C5
3C 8A 45 7D 70 BC A5 F1 73 61 6E 21 48 21 98 50 7C 0E 67 E6 68 46 8E
53 18 8F 16 E4 DE 88 56 0D A0 83 A0 60 93 05 1A 3B 70 7A 95 83 83 14
C3 80 EE 2E F3 E9 44 5D 71 DB 4B D1 F0 58 D3 BA 87 11 1E D0 C0 CB FA
0C 6D BE 5B 00 1B D4 6B C6 83 B4 AF 2E 7D F7 DE 41 8A 41 48 DF 2E 66
CF 92 DF 4A 88 F4 44 1F A4 57 01 9D 26 C4 05 3D 70 58 DA C1 5D AA CB
E6 18 73 9D F0 6C 8C 35 B8 84 6B 2D 79 87 76 3F 32 05 B2 09 7D 3D 67
9D EB BC A0 CD 77 1B 7A 36 AA E0 02 A7 04 C9 3E E0 4B 34 1A 42 48 50
37 20 80 D8 C8 21 7D 78 66 50 87 E8 D7 3F 92 F2 32 A3 A3 93 8D 76 31
D5 C6 61 36 AA 1B 8F A4 B3 E9 28 A2 B0 D9 D1 03 8C B9 B8 A7 DA 5A 74
E4 51 0E F0 C1 EC B2 00 AB 9C 2A D7 8C A2 A9 D9 35 99 70 3B 20 45 6A
E8 36 D7 F4 E5 B5 6F AE C0 FE 23 98 07 B8 6D 02 C2 A7 6F AE 07 66 DB
82 92 5F EA E4 B1 A1 7F 52 80 DE 56 4B 09 13 A6 BC 3A 54 02 DB 97 18
50 C2 4E EE 4A CB 80 38 63 C1 13 4B 8B D6 13 16 E7 72 80 68 43 9F EA
A8 7E 66 2B 90 27 7C C0 97 DA 47 7D E9 A9 03 6A 4E 03 74 B1 6F AA 81
3A 3B 34 C5 06 F8 F2 3E 7D BD D7 00 AB 3C 94 79 29 B8 D5 16 75 80 65
03 A3 02 C7 26 FA 7B F9 3E FB 53 89 88 39 43 5F 95 4D B6 8B 28 37 B6
4A D8 70 3D 3A 69 AE 83 15 2D 3B 18 E2 37 76 05 3C D9 F1 67 25 D1 11
3A 44 CC 16 5C 45 EE E5 50 FB DD 06 BF 6B C1 C6 15 35 94 9F D9 B2 94
48 2A 4D 6C D4 F2 82 B6 B2 1E 8B 2C 41 36 63 CB 35 0C DF F8 5D 8B CE
07 D8 F9 14 FF 7B 92 C8 91 AB 62 92 A4 35 93 24 6D 7F 92 64 CE 3A BB
CC 74 7C A1 C6 76 C3 CA DD DD EA 80 6C C6 1E F7 B0 B0 A0 9F AF 6C 52
DA CC 6E 84 E2 71 2B EA DC 71 CB 3B ED F0 81 15 D8 F7 C8 AF DF CA 7A
DC C7 7B EE AB CD 90 3B F2 FF 78 D2 91 1F F0 44 49 EB 1F 4D 73 5A F5
77 C1 31 37 7C F8 AE 26 5C 05 88 01 3E 0D AD 90 21 1C 64 27 3E 3B 0D
62 88 74 D9 0E 03 E1 05 F5 5E 6B 74 9F 0F F7 38 F7 E1 EE 5A F9 70 37
08 01 AE 2B E0 C8 26 81 3A D2 AB 0F 4E 19 EE 5D AE 16 52 F8 D8 CE 92
2B B4 61 87 28 EB 83 A3 CD AB 0F 03 88 84 0C 35 83 8B A0 7C DA 1F 13
4D E7 F1 2E 8F F2 D8 91 E2 4A 0D 5B 13 AE 42 78 14 57 48 3B 8A 1F 82
77 B4 71 60 72 FD F6 89 82 7C CD 1E 25 B0 13 4E 38 0E 88 FE 02 F6 44
9F 27 3E C7 F6 74 00 00 00 00 49 45 4E 44 AE 42 60 82";
private Image plus3Icon;
private string minus3IconCode = "89 50 4E 47 0D 0A 1A 0A 00 00 00 0D
49 48 44 52 00 00 00 28 00 00 00 28 08 06 00 00 00 8C FE B8 6D 00 00
00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 04 67 41 4D 41 00 00 B1 8F
0B FC 61 05 00 00 00 09 70 48 59 73 00 00 0E C3 00 00 0E C3 01 C7 6F
A8 64 00 00 03 03 49 44 41 54 58 47 ED 98 4D 6B DB 40 10 86 A7 21 F4
54 4A 29 25 F4 90 5F 50 72 36 25 97 40 C8 A1 27 1F 4A 1C 92 F8 EB 50
28 A5 90 D6 92 D6 3E D6 35 39 04 D3 3F DD 79 66 77 2D CB 49 14 C5 96
A9 0A 7E 61 90 B4 9A 7D 67 76 77 66 47 5A D9 61 87 FF 0C 7B E1 2A 1D
F9 FE EA 4A 92 56 57 92 DE 40 9C EB 4B 7A 3B 90 6C 8E F8 7B E7 06 F6
2E 69 A1 1B BA 81 05 47 9D 80 D4 88 7B 92 1D 74 65 74 A1 0E CC 86 32
FE 53 45 D0 ED 4A 76 41 5F 38 14 0B BE 3A 60 44 27 32 DD EF 4A DA 1E
C8 78 9E 1B 1E CF 7B 92 BA 9E 24 97 D7 32 3A D3 FB 63 84 7B 9D 49 75
28 D5 59 2C EA C3 F1 41 3A 2F 8D B9 06 27 8D E0 4A D2 77 18 8B 86 FA
E2 74 09 D3 F6 50 DC 7B D3 2A 01 3A 38 45 9F DC D1 D4 C1 19 54 D6 76
D2 3A 76 E5 E6 70 79 39 89 2B 6D 7B 6D 1A 39 54 77 BA 22 45 C3 F4 21
5E 73 1E 96 7D 72 18 5E 17 74 AB C0 3A F4 E4 DB 41 FF 45 F6 DB 13 B2
3C C9 47 7B EB 71 CF 89 47 50 D0 83 23 2E 3B DC 2B 71 59 1D 1D 8D 13
CD C6 D4 3B 97 69 AC 65 47 B4 4F 1F 98 9D 8A D8 0B 7D 49 B4 23 38 CD
49 B5 81 2D D3 A8 08 23 21 6E 20 40 E2 CC 9D C8 C9 3E D7 4D 10 39 E0
CC F9 D3 B6 BD AC 3A 70 02 5B 97 E1 CE 8F D0 5D FB 56 3F FA 7A E0 B9
FA 92 5C 63 C3 DB 7A 3A E1 16 D0 E9 3F F7 1D DD EC 5C BE BC 0D CD B5
03 EE 98 80 D8 0C CD E5 20 DB 86 92 FD 5A 6B EA 9F 87 42 28 91 30 0F
EC 0E F7 41 F9 B2 0E 92 A9 83 37 2B DB 40 4C 10 AE EB 48 EC 6B B0 2B
36 B0 E5 67 31 69 D9 9B 32 10 73 DE 41 97 86 A6 AD 03 5B C1 66 88 F7
12 B0 CB A3 AC 5B C1 A5 6F F1 23 BE 92 AF 54 93 63 B2 6F 13 81 23 AF
22 9E 9B 52 E9 67 D0 39 6B 2E 43 0C 5A 1D CD 19 CF D4 60 AE BA 0C 9F
3D 49 5E 5B 9F 2B 8B 0D 5A B9 8A DC A3 33 FF DE CD 78 2E 85 76 36 12
46 CA 73 DC B7 F2 CC 1E DF 61 68 4D B1 AD 2B 66 6C E4 C6 56 70 7C CE
73 29 1A EF 60 E3 97 F8 F1 24 B1 4F AE 7F 9F 24 A4 BA 1F 65 63 B7 99
86 6F D4 94 1B 55 6E 6E A9 03 79 C6 6E F7 63 61 28 3F DF C4 A4 8C 99
5D 09 C5 CF AD A4 71 9F 5B 85 A9 47 C8 3E DA E2 BE B5 09 22 07 9C 39
FF 1A A1 B4 DD 4F FE 1F 1B 7D F2 83 40 94 D5 FE D3 34 90 49 6B E1 9C
72 63 23 BC AA C2 55 80 75 E0 D7 30 06 32 A2 4E 36 E2 B7 33 C2 3A B2
F3 C7 0A 83 E8 86 7A EB 63 F4 39 3F EE E9 D2 8F BB AB E5 C7 3D C2 08
38 AE C0 D0 72 2D E6 5E 4B 97 D3 25 E3 DC E5 74 68 81 4F 39 CB 4E 69
A3 42 AC EA C3 51 E7 D1 47 04 44 46 46 CC 70 10 B4 BC EC 4F 89 5F CE
ED 1D 1E 2D 63 41 CA 91 1A A5 89 A3 10 9D C5 09 CB 4E F0 23 DC D3 A6
8E D9 F1 DB 27 E9 2E C7 EC 56 1C DB 61 87 ED 40 E4 2F CA D3 30 3D C3
2A 06 23 00 00 00 00 49 45 4E 44 AE 42 60 82";
private Image minus3Icon;
protected override void OnTextChanged(EventArgs e)
{
if (!string.IsNullOrEmpty(Text))
{
if (Text.StartsWith("customUpDown"))
{
Text = string.Empty;
label1Init = true;
}
else if (label1Init)
{
int buttonWidth = (int)(textBox.Height * 0.8);
Width = (int)(Text.Length * Font.Size +
textBox.Width + 2 * buttonWidth);
label1Init = false;
}
}
base.OnTextChanged(e);
}
public CustomUpDown()
{
buttonForeColor = Color.Gray;
textBackColor = Color.White;
label1 = new Label();
Controls.Add(label1);
textBox = new TextBox();
Value = Minimum;
textBox.KeyPress += TextBox_KeyPress;
Controls.Add(textBox);
buttonUp = new Button();
AddButtonUp();
buttonDown = new Button();
AddbuttonDown();
buttonstyle = ButtonDisplay.Arrows;
buttonUp.Text = "▲";
buttonDown.Text = "▼";
}
private void TextBox_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)Keys.Enter)
{
AddValue(0);
e.Handled = false;
ValueChanged?.Invoke(this, e);
}
else if (char.IsDigit(e.KeyChar) || e.KeyChar == (char)Keys.Back)
{
e.Handled = false;
}
else if (e.KeyChar == '.' || e.KeyChar == ',')
{
e.Handled = !(DecimalPlaces > 0);
}
else
{
e.Handled = true;
}
}
private void AddButtonUp()
{
buttonUp.Name = "ButtonUp";
buttonUp.FlatStyle = FlatStyle.Flat;
buttonUp.FlatAppearance.BorderSize = 0;
buttonUp.TextAlign = ContentAlignment.TopCenter;
buttonUp.MouseDown += ButtonUpClick;
Controls.Add(buttonUp);
}
private void AddbuttonDown()
{
buttonDown.Name = "buttonDown";
buttonDown.FlatStyle = FlatStyle.Flat;
buttonDown.FlatAppearance.BorderSize = 0;
buttonDown.TextAlign = ContentAlignment.TopCenter;
buttonDown.MouseDown += ButtonDownClick;
Controls.Add(buttonDown);
}
protected override void OnPaint(PaintEventArgs e)
{
textBox.Text = Value.ToString();
textBox.BackColor = TextBackColor;
textBox.ForeColor = ForeColor;
int buttonHeight = textBox.Height;
int buttonWidth = (int)(textBox.Height * 0.8);
if (string.IsNullOrEmpty(label1.Text))
{
label1.Visible = false;
textBox.Width = Width - 2 * buttonWidth;
textBox.Location = new Point(0, 0);
}
else
{
label1.Visible = true;
label1.BackColor = BackColor;
textBox.Width = (int)(Math.Max(3, textBox.Text.Length) * Font.Size);
label1.Width = Width - textBox.Width - 2 * buttonWidth;
label1.Location = new Point(0, 2);
textBox.Location = new Point(label1.Width, 0);
}
buttonUp.ForeColor = ButtonForeColor;
buttonDown.ForeColor = ButtonForeColor;
buttonUp.BackColor = ButtonBackColor;
buttonDown.BackColor = ButtonBackColor;
buttonUp.Size = new Size(buttonWidth, buttonHeight);
buttonDown.Size = buttonUp.Size;
buttonUp.Location = new Point(Width - 2 * buttonWidth, 0);
buttonDown.Location = new Point(Width - buttonWidth, 0);
base.OnPaint(e);
}
private void ButtonUpClick(object sender, MouseEventArgs e)
{
int delayMs = 300;
while ((MouseButtons & MouseButtons.Left) != 0)
{
if (AddValue(Increment))
{
Application.DoEvents();
Thread.Sleep(delayMs);
delayMs = RepeatDelayMs;
}
Application.DoEvents();
}
ValueChanged?.Invoke(this, e);
}
private void ButtonDownClick(object sender, MouseEventArgs e)
{
int delayMs = 300;
while ((MouseButtons & MouseButtons.Left) != 0)
{
if (AddValue(-Increment))
{
Application.DoEvents();
Thread.Sleep(delayMs);
delayMs = RepeatDelayMs;
}
Application.DoEvents();
}
ValueChanged?.Invoke(this, e);
}
private bool AddValue(decimal value)
{
if (string.IsNullOrEmpty(textBox.Text))
{
Value = Minimum;
textBox.Text = Value.ToString();
return false;
}
else if (textBox.TextLength > 25)
{
Debug.Print("Too many significant digits for decimal");
Value = Maximum;
textBox.Text = Value.ToString();
return false;
}
if (decimal.TryParse(textBox.Text, out var result))
{
Value = Math.Round(result + value, DecimalPlaces);
if (Value < Minimum)
{
Value = Minimum;
}
else if (Value > Maximum)
{
Value = Maximum;
}
else
{
textBox.Text = Value.ToString();
return true;
}
}
textBox.Text = Value.ToString();
return false;
}
private Image GetImageFromHexString(string hexString)
{
var hexArr = hexString.Split(' ');
byte[] arr2 = new byte[hexArr.Length];
for (int i = 0; i < hexArr.Length; i++)
{
arr2[i] = Convert.ToByte(hexArr[i], 16);
}
using (var ms2 = new MemoryStream(arr2))
{
return Image.FromStream(ms2);
}
}
}
}
History
- 24th January, 2023: Initial version