Introduction
If you develop applications for Windows, you surely came across NumericUpDown control. It is handy in many scenarios, but also somehow limited. There were several attempts to improve it. Few of them have been documented on CodeProject pages. Especially Extended NumericUpDown Control article by Claudio Nicora drew my attention. Claudio enhanced standard NumericUpdown control with extra events and properties and that is what made it more appealing. This is one way of enhancing NumericUpDown control.
I decided to go into a somewhat different direction. Why instead of enhancing existing elements of NumericUpDown control, provide ability to replace edit element of it with any (editable or not) control? This would open an entire new world to how this control could be used.
With flexibility to provide your own editing component to NumericUpDown, you can show numbers using formatting of your choice or you can display your own graphical controls, elements pictures. There are really no limits to what can go there.
Of course, it's not NumericUpDown anymore. I had to drop 'Numeric' prefix, and decided to call it UniversalUpDown control.
NumericUpDown Anatomy
NumericUpDown control is a composite control. When you look at it, you see two distinct areas. TextBox like area where numbers are displayed and edited, and squared UpDown buttons. And that is how NumericUpDown is composed. It has two internal controls: UpDownEdit and UpDownButtons.
Making UniversalUpDown Control
Knowing how NumericUpdown is structured, our task is easy.
We need to:
- Build
UniversalUpDown control as UserControl that inherits from NumericUpDown
- Provide
public property (I named it UserEditControl) where user can provide as a parameter any control that will replace UpDownEdit.
The entire framework is drafted below:
public partial class UniversalUpDown : NumericUpDown
{
Control m_UserEditControl = null;
public Control UserEditControl
{
set
{
m_UserEditControl = value;
}
get
{
return m_UserEditControl;
}
}
}
Of course, the control defined as above doesn't do anything more than standard NumericUpDown. We need to write missing code.
First goes constructor. Besides what every constructor does, it has to do a little housekeeping: find reference to UpDownEdit and save it in local variable. It will be handy in UserEditControl property setter where we will be replacing it with user provided control.
Something like this:
Control m_UpDownEdit = null;
public UniversalUpDown()
{
InitializeComponent();
foreach (Control control in this.Controls)
{
Type type = control.GetType();
if (type.Name=="UpDownEdit")
m_UpDownEdit= control;
}
}
When I looked at the internal structure of NumericUpDown, I always saw that UpDownEdit has index zero in Controls collection, so in constructor, I could use line:
m_UpDownEdit = this.Controls[0];
I decided to use loop instead because this appeared to me a safer approach to be 100% sure that value stored in m_UpDownEdit is truly UpDownEdit instance not UpdownButtons.
The next task is to complete coding of UserEditControl property. Please read comments inside the code section below. I hope they explain everything.
public Control UserEditControl
{
set
{
if (value == null)
{
if (m_UserEditControl != null)
{
this.Controls.Remove(m_UserEditControl);
m_UserEditControl = null;
m_UpDownEdit.Visible = true;
}
return;
}
value.Dock = m_UpDownEdit.Dock;
value.Location = m_UpDownEdit.Location;
value.Size = m_UpDownEdit.Size;
if (m_UserEditControl==null)
m_UpDownEdit.Visible = false;
else
Controls.Remove(m_UserEditControl);
Controls.Add(value);
m_UserEditControl = value;
base.OnValueChanged(EventArgs.Empty);
}
get
{
return m_UserEditControl;
}
}
And that's all. The code for the entire UniversalUpDown control is listed below:
public partial class UniversalUpDown : NumericUpDown
{
Control m_UserEditControl = null;
Control m_UpDownEdit = null;
public UniversalUpDownControl()
{
InitializeComponent();
foreach (Control control in this.Controls)
{
Type type = control.GetType();
if (type.Name=="UpDownEdit")
m_UpDownEdit= control;
}
}
public Control UserEditControl
{
set
{
if (value == null)
{
if (m_UserEditControl != null)
{
this.Controls.Remove(m_UserEditControl);
m_UserEditControl = null;
m_UpDownEdit.Visible = true;
}
return;
}
value.Dock = m_UpDownEdit.Dock;
value.Location = m_UpDownEdit.Location;
value.Size = m_UpDownEdit.Size;
if (m_UserEditControl==null)
m_UpDownEdit.Visible = false;
else
Controls.Remove(m_UserEditControl);
Controls.Add(value);
m_UserEditControl = value;
base.OnValueChanged(EventArgs.Empty);
}
get
{
return m_UserEditControl;
}
}
}
Using the Control
Simplicity comes at a little extra cost. Since you are providing your own edit control, you need to intercept ValueChaged event in order to change appearance of UserEditControl.
If UserEditControl is editable, you also have to provide the means to set control's value property after value of UserEditControl change.
The following example shows how to use editable textbox as UserEditEdit control:
(...)
universalUpDown1.UserEditControl = textBox1;
universalUpDown1.Minimum = 0;
universalUpDown1.Maximum = 255;
universalUpDown1.UserEditControl = textBox1;
(...)
private void universalUpDown1_ValueChanged(object sender, EventArgs e)
{
textBox1.Text = String.Format("{0:X2}", (int)universalUpDown1.Value);
}
private void textBox1_Validating(object sender, CancelEventArgs e)
{
int valueAsInteger=0;
try
{
valueAsInteger = Convert.ToInt32(textBox1.Text, 16);
}
catch (Exception ex)
{
MessageBox.Show("String is not valid representation of hexadecimal number.");
e.Cancel = true;
return;
}
if ((valueAsInteger < 0) || (valueAsInteger > 255))
{
MessageBox.Show(string.Format("Value {0:X2} is not within valid range.", valueAsInteger));
e.Cancel = true;
return;
}
universalUpDown1.Value = valueAsInteger;
}
Demo Program
The attached code contains the source code of the control discussed in this tip and demo program shows few examples of UniversalUpDown in action.
History
- 07-Sep-2013 - Initial version