I am attempting to embed a custom NumericUpDown that includes a leading/trailing string/char (e.g. "%" or "$") in the NumericUpDown textbox inside of a C# DataGridView. In other words, I want to have NumericUpDown controls that have '%' or '$' symbols INSIDE a DataGridView.
I am currently using the custom NumericUpDown posted here by 'doomgg' alongside this example of using the default NumericUpDown inside a DataGridView.
I modified the DataGridViewNumericUpDownColumn class slightly to have fields for leading/trailing symbols and changed the DataGridViewNumericUpDownEditingControl to inherit from my CustomNumericUpDown class, but it is not updating/displaying the symbols in my Form. The NumericUpDown elements behave as though they are standard. Any help would be appreciated
Here is my modified code that is not working properly:
CustomNumericUpDown.cs:
public class CustomNumericUpDown : NumericUpDown
{
private string _leadingSymbol = "";
public string LeadingSymbol
{
get { return _leadingSymbol; }
set
{
_leadingSymbol = value;
this.UpdateEditText();
}
}
private string _trailingSymbol = "";
public string TrailingSymbol
{
get { return _trailingSymbol; }
set
{
_trailingSymbol = value;
this.UpdateEditText();
}
}
protected override void UpdateEditText()
{
if(UserEdit)
{
ParseEditText();
}
ChangingText = true;
base.Text = _leadingSymbol + GetNumberFromText(this.Value) + _trailingSymbol;
}
private string GetNumberFromText(decimal num)
{
string num_text;
if (Hexadecimal)
{
num_text = ((Int64)num).ToString("X", CultureInfo.InvariantCulture);
} else
{
num_text = num.ToString((ThousandsSeparator ? "N" : "F") + DecimalPlaces.ToString(CultureInfo.CurrentCulture), CultureInfo.CurrentCulture);
}
return num_text;
}
protected override void ValidateEditText()
{
ParseEditText();
UpdateEditText();
}
protected new void ParseEditText()
{
try
{
string text = base.Text;
if(!string.IsNullOrEmpty(_leadingSymbol))
{
if(text.StartsWith(_leadingSymbol))
{
text = text.Substring(_leadingSymbol.Length);
}
}
if(!string.IsNullOrEmpty(_trailingSymbol))
{
if (text.EndsWith(_trailingSymbol))
{
text = text.Substring(0, text.Length - _trailingSymbol.Length);
}
}
if(!string.IsNullOrEmpty(text) && !(text.Length == 1 && text =="-"))
{
if(Hexadecimal)
{
base.Value = Constrain(Convert.ToDecimal(Convert.ToInt32(text, 16)));
} else
{
base.Value = Constrain(decimal.Parse(text, CultureInfo.CurrentCulture));
}
}
}
catch
{
}
finally
{
UserEdit = false;
}
}
private decimal Constrain(decimal value)
{
if (value < base.Minimum)
value = base.Minimum;
if (value > base.Maximum)
value = base.Maximum;
return value;
}
}
DataGridViewNumericUpDownCell.cs:
public class DataGridViewNumericUpDownCell : DataGridViewTextBoxCell
{
[System.Runtime.InteropServices.DllImport("USER32.DLL", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern short VkKeyScan(char key);
private static readonly DataGridViewContentAlignment anyRight = DataGridViewContentAlignment.TopRight |
DataGridViewContentAlignment.MiddleRight |
DataGridViewContentAlignment.BottomRight;
private static readonly DataGridViewContentAlignment anyCenter = DataGridViewContentAlignment.TopCenter |
DataGridViewContentAlignment.MiddleCenter |
DataGridViewContentAlignment.BottomCenter;
private const int DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapWidth = 100;
private const int DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapHeight = 22;
internal const int DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces = 0;
internal const Decimal DATAGRIDVIEWNUMERICUPDOWNCELL_defaultIncrement = Decimal.One;
internal const Decimal DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMaximum = (Decimal)100.0;
internal const Decimal DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMinimum = Decimal.Zero;
internal const bool DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator = false;
internal const string DATAGRIDVIEWNUMERICUPDOWNCELL_defaultLeadingSymbol = "";
internal const string DATAGRIDVIEWNUMERICUPDOWNCELL_defaultTrailingSymbol = "";
private static Type defaultEditType = typeof(DataGridViewNumericUpDownEditingControl);
private static Type defaultValueType = typeof(System.Decimal);
[ThreadStatic]
private static Bitmap renderingBitmap;
[ThreadStatic]
private static NumericUpDown paintingNumericUpDown;
private int decimalPlaces;
private Decimal increment;
private Decimal minimum;
private Decimal maximum;
private bool thousandsSeparator;
private string trailingSymbol;
private string leadingSymbol;
public DataGridViewNumericUpDownCell()
{
if (renderingBitmap == null)
{
renderingBitmap = new Bitmap(DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapWidth, DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapHeight);
}
if (paintingNumericUpDown == null)
{
paintingNumericUpDown = new NumericUpDown();
paintingNumericUpDown.BorderStyle = BorderStyle.None;
paintingNumericUpDown.Maximum = Decimal.MaxValue / 10;
paintingNumericUpDown.Minimum = Decimal.MinValue / 10;
}
this.decimalPlaces = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces;
this.increment = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultIncrement;
this.minimum = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMinimum;
this.maximum = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMaximum;
this.thousandsSeparator = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator;
this.leadingSymbol = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultLeadingSymbol;
this.trailingSymbol = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultTrailingSymbol;
}
[
DefaultValue(DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces)
]
public int DecimalPlaces
{
get
{
return this.decimalPlaces;
}
set
{
if (value < 0 || value > 99)
{
throw new ArgumentOutOfRangeException("The DecimalPlaces property cannot be smaller than 0 or larger than 99.");
}
if (this.decimalPlaces != value)
{
SetDecimalPlaces(this.RowIndex, value);
OnCommonChange();
}
}
}
private DataGridViewNumericUpDownEditingControl EditingNumericUpDown
{
get
{
return this.DataGridView.EditingControl as DataGridViewNumericUpDownEditingControl;
}
}
public override Type EditType
{
get
{
return defaultEditType;
}
}
public Decimal Increment
{
get
{
return this.increment;
}
set
{
if (value < (Decimal)0.0)
{
throw new ArgumentOutOfRangeException("The Increment property cannot be smaller than 0.");
}
SetIncrement(this.RowIndex, value);
}
}
public Decimal Maximum
{
get
{
return this.maximum;
}
set
{
if (this.maximum != value)
{
SetMaximum(this.RowIndex, value);
OnCommonChange();
}
}
}
public Decimal Minimum
{
get
{
return this.minimum;
}
set
{
if (this.minimum != value)
{
SetMinimum(this.RowIndex, value);
OnCommonChange();
}
}
}
public string TrailingSymbol
{
get
{
return this.trailingSymbol;
}
set
{
if (this.trailingSymbol != value)
{
SetTrailingSymbol(this.RowIndex, value);
OnCommonChange();
}
}
}
public string LeadingSymbol
{
get
{
return this.leadingSymbol;
}
set
{
if (this.leadingSymbol != value)
{
SetLeadingSymbol(this.RowIndex, value);
OnCommonChange();
}
}
}
[
DefaultValue(DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator)
]
public bool ThousandsSeparator
{
get
{
return this.thousandsSeparator;
}
set
{
if (this.thousandsSeparator != value)
{
SetThousandsSeparator(this.RowIndex, value);
OnCommonChange();
}
}
}
public override Type ValueType
{
get
{
Type valueType = base.ValueType;
if (valueType != null)
{
return valueType;
}
return defaultValueType;
}
}
public override object Clone()
{
DataGridViewNumericUpDownCell dataGridViewCell = base.Clone() as DataGridViewNumericUpDownCell;
if (dataGridViewCell != null)
{
dataGridViewCell.DecimalPlaces = this.DecimalPlaces;
dataGridViewCell.Increment = this.Increment;
dataGridViewCell.Maximum = this.Maximum;
dataGridViewCell.Minimum = this.Minimum;
dataGridViewCell.ThousandsSeparator = this.ThousandsSeparator;
}
return dataGridViewCell;
}
private Decimal Constrain(Decimal value)
{
Debug.Assert(this.minimum <= this.maximum);
if (value < this.minimum)
{
value = this.minimum;
}
if (value > this.maximum)
{
value = this.maximum;
}
return value;
}
[
EditorBrowsable(EditorBrowsableState.Advanced)
]
public override void DetachEditingControl()
{
DataGridView dataGridView = this.DataGridView;
if (dataGridView == null || dataGridView.EditingControl == null)
{
throw new InvalidOperationException("Cell is detached or its grid has no editing control.");
}
NumericUpDown numericUpDown = dataGridView.EditingControl as NumericUpDown;
if (numericUpDown != null)
{
TextBox textBox = numericUpDown.Controls[1] as TextBox;
if (textBox != null)
{
textBox.ClearUndo();
}
}
base.DetachEditingControl();
}
private Rectangle GetAdjustedEditingControlBounds(Rectangle editingControlBounds, DataGridViewCellStyle cellStyle)
{
editingControlBounds.X += 1;
editingControlBounds.Width = Math.Max(0, editingControlBounds.Width - 2);
int preferredHeight = cellStyle.Font.Height + 3;
if (preferredHeight < editingControlBounds.Height)
{
switch (cellStyle.Alignment)
{
case DataGridViewContentAlignment.MiddleLeft:
case DataGridViewContentAlignment.MiddleCenter:
case DataGridViewContentAlignment.MiddleRight:
editingControlBounds.Y += (editingControlBounds.Height - preferredHeight) / 2;
break;
case DataGridViewContentAlignment.BottomLeft:
case DataGridViewContentAlignment.BottomCenter:
case DataGridViewContentAlignment.BottomRight:
editingControlBounds.Y += editingControlBounds.Height - preferredHeight;
break;
}
}
return editingControlBounds;
}
protected override Rectangle GetErrorIconBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
{
const int ButtonsWidth = 16;
Rectangle errorIconBounds = base.GetErrorIconBounds(graphics, cellStyle, rowIndex);
if (this.DataGridView.RightToLeft == RightToLeft.Yes)
{
errorIconBounds.X = errorIconBounds.Left + ButtonsWidth;
}
else
{
errorIconBounds.X = errorIconBounds.Left - ButtonsWidth;
}
return errorIconBounds;
}
protected override object GetFormattedValue(object value,
int rowIndex,
ref DataGridViewCellStyle cellStyle,
TypeConverter valueTypeConverter,
TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
{
object formattedValue = base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
string formattedNumber = formattedValue as string;
if (!string.IsNullOrEmpty(formattedNumber) && value != null)
{
Decimal unformattedDecimal = System.Convert.ToDecimal(value);
Decimal formattedDecimal = System.Convert.ToDecimal(formattedNumber);
if (unformattedDecimal == formattedDecimal)
{
return formattedDecimal.ToString((this.ThousandsSeparator ? "N" : "F") + this.DecimalPlaces.ToString());
}
}
return formattedValue;
}
protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Size constraintSize)
{
if (this.DataGridView == null)
{
return new Size(-1, -1);
}
Size preferredSize = base.GetPreferredSize(graphics, cellStyle, rowIndex, constraintSize);
if (constraintSize.Width == 0)
{
const int ButtonsWidth = 16;
const int ButtonMargin = 8;
preferredSize.Width += ButtonsWidth + ButtonMargin;
}
return preferredSize;
}
public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
NumericUpDown numericUpDown = this.DataGridView.EditingControl as NumericUpDown;
if (numericUpDown != null)
{
numericUpDown.BorderStyle = BorderStyle.None;
numericUpDown.DecimalPlaces = this.DecimalPlaces;
numericUpDown.Increment = this.Increment;
numericUpDown.Maximum = this.Maximum;
numericUpDown.Minimum = this.Minimum;
numericUpDown.ThousandsSeparator = this.ThousandsSeparator;
string initialFormattedValueStr = initialFormattedValue as string;
if (initialFormattedValueStr == null)
{
numericUpDown.Text = string.Empty;
}
else
{
numericUpDown.Text = initialFormattedValueStr;
}
}
}
public override bool KeyEntersEditMode(KeyEventArgs e)
{
NumberFormatInfo numberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
Keys negativeSignKey = Keys.None;
string negativeSignStr = numberFormatInfo.NegativeSign;
if (!string.IsNullOrEmpty(negativeSignStr) && negativeSignStr.Length == 1)
{
negativeSignKey = (Keys)(VkKeyScan(negativeSignStr[0]));
}
if ((char.IsDigit((char)e.KeyCode) ||
(e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9) ||
negativeSignKey == e.KeyCode ||
Keys.Subtract == e.KeyCode) &&
!e.Shift && !e.Alt && !e.Control)
{
return true;
}
return false;
}
private void OnCommonChange()
{
if (this.DataGridView != null && !this.DataGridView.IsDisposed && !this.DataGridView.Disposing)
{
if (this.RowIndex == -1)
{
this.DataGridView.InvalidateColumn(this.ColumnIndex);
}
else
{
this.DataGridView.UpdateCellValue(this.ColumnIndex, this.RowIndex);
}
}
}
private bool OwnsEditingNumericUpDown(int rowIndex)
{
if (rowIndex == -1 || this.DataGridView == null)
{
return false;
}
DataGridViewNumericUpDownEditingControl numericUpDownEditingControl = this.DataGridView.EditingControl as DataGridViewNumericUpDownEditingControl;
return numericUpDownEditingControl != null && rowIndex == ((IDataGridViewEditingControl)numericUpDownEditingControl).EditingControlRowIndex;
}
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState,
object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{
if (this.DataGridView == null)
{
return;
}
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle,
paintParts & ~(DataGridViewPaintParts.ErrorIcon | DataGridViewPaintParts.ContentForeground));
Point ptCurrentCell = this.DataGridView.CurrentCellAddress;
bool cellCurrent = ptCurrentCell.X == this.ColumnIndex && ptCurrentCell.Y == rowIndex;
bool cellEdited = cellCurrent && this.DataGridView.EditingControl != null;
if (!cellEdited)
{
if (PartPainted(paintParts, DataGridViewPaintParts.ContentForeground))
{
Rectangle borderWidths = BorderWidths(advancedBorderStyle);
Rectangle valBounds = cellBounds;
valBounds.Offset(borderWidths.X, borderWidths.Y);
valBounds.Width -= borderWidths.Right;
valBounds.Height -= borderWidths.Bottom;
if (cellStyle.Padding != Padding.Empty)
{
if (this.DataGridView.RightToLeft == RightToLeft.Yes)
{
valBounds.Offset(cellStyle.Padding.Right, cellStyle.Padding.Top);
}
else
{
valBounds.Offset(cellStyle.Padding.Left, cellStyle.Padding.Top);
}
valBounds.Width -= cellStyle.Padding.Horizontal;
valBounds.Height -= cellStyle.Padding.Vertical;
}
valBounds = GetAdjustedEditingControlBounds(valBounds, cellStyle);
bool cellSelected = (cellState & DataGridViewElementStates.Selected) != 0;
if (renderingBitmap.Width < valBounds.Width ||
renderingBitmap.Height < valBounds.Height)
{
renderingBitmap.Dispose();
renderingBitmap = new Bitmap(valBounds.Width, valBounds.Height);
}
if (paintingNumericUpDown.Parent == null || !paintingNumericUpDown.Parent.Visible)
{
paintingNumericUpDown.Parent = this.DataGridView;
}
paintingNumericUpDown.TextAlign = DataGridViewNumericUpDownCell.TranslateAlignment(cellStyle.Alignment);
paintingNumericUpDown.DecimalPlaces = this.DecimalPlaces;
paintingNumericUpDown.ThousandsSeparator = this.ThousandsSeparator;
paintingNumericUpDown.Font = cellStyle.Font;
paintingNumericUpDown.Width = valBounds.Width;
paintingNumericUpDown.Height = valBounds.Height;
paintingNumericUpDown.RightToLeft = this.DataGridView.RightToLeft;
paintingNumericUpDown.Location = new Point(0, -paintingNumericUpDown.Height - 100);
paintingNumericUpDown.Text = formattedValue as string;
Color backColor;
if (PartPainted(paintParts, DataGridViewPaintParts.SelectionBackground) && cellSelected)
{
backColor = cellStyle.SelectionBackColor;
}
else
{
backColor = cellStyle.BackColor;
}
if (PartPainted(paintParts, DataGridViewPaintParts.Background))
{
if (backColor.A < 255)
{
backColor = Color.FromArgb(255, backColor);
}
paintingNumericUpDown.BackColor = backColor;
}
Rectangle srcRect = new Rectangle(0, 0, valBounds.Width, valBounds.Height);
if (srcRect.Width > 0 && srcRect.Height > 0)
{
paintingNumericUpDown.DrawToBitmap(renderingBitmap, srcRect);
graphics.DrawImage(renderingBitmap, new Rectangle(valBounds.Location, valBounds.Size),
srcRect, GraphicsUnit.Pixel);
}
}
if (PartPainted(paintParts, DataGridViewPaintParts.ErrorIcon))
{
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText,
cellStyle, advancedBorderStyle, DataGridViewPaintParts.ErrorIcon);
}
}
}
private static bool PartPainted(DataGridViewPaintParts paintParts, DataGridViewPaintParts paintPart)
{
return (paintParts & paintPart) != 0;
}
public override void PositionEditingControl(bool setLocation,
bool setSize,
Rectangle cellBounds,
Rectangle cellClip,
DataGridViewCellStyle cellStyle,
bool singleVerticalBorderAdded,
bool singleHorizontalBorderAdded,
bool isFirstDisplayedColumn,
bool isFirstDisplayedRow)
{
Rectangle editingControlBounds = PositionEditingPanel(cellBounds,
cellClip,
cellStyle,
singleVerticalBorderAdded,
singleHorizontalBorderAdded,
isFirstDisplayedColumn,
isFirstDisplayedRow);
editingControlBounds = GetAdjustedEditingControlBounds(editingControlBounds, cellStyle);
this.DataGridView.EditingControl.Location = new Point(editingControlBounds.X, editingControlBounds.Y);
this.DataGridView.EditingControl.Size = new Size(editingControlBounds.Width, editingControlBounds.Height);
}
internal void SetDecimalPlaces(int rowIndex, int value)
{
Debug.Assert(value >= 0 && value <= 99);
this.decimalPlaces = value;
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.DecimalPlaces = value;
}
}
internal void SetIncrement(int rowIndex, Decimal value)
{
Debug.Assert(value >= (Decimal)0.0);
this.increment = value;
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.Increment = value;
}
}
internal void SetMaximum(int rowIndex, Decimal value)
{
this.maximum = value;
if (this.minimum > this.maximum)
{
this.minimum = this.maximum;
}
object cellValue = GetValue(rowIndex);
if (cellValue != null)
{
Decimal currentValue = System.Convert.ToDecimal(cellValue);
Decimal constrainedValue = Constrain(currentValue);
if (constrainedValue != currentValue)
{
SetValue(rowIndex, constrainedValue);
}
}
Debug.Assert(this.maximum == value);
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.Maximum = value;
}
}
internal void SetMinimum(int rowIndex, Decimal value)
{
this.minimum = value;
if (this.minimum > this.maximum)
{
this.maximum = value;
}
object cellValue = GetValue(rowIndex);
if (cellValue != null)
{
Decimal currentValue = System.Convert.ToDecimal(cellValue);
Decimal constrainedValue = Constrain(currentValue);
if (constrainedValue != currentValue)
{
SetValue(rowIndex, constrainedValue);
}
}
Debug.Assert(this.minimum == value);
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.Minimum = value;
}
}
internal void SetLeadingSymbol(int rowIndex, string value)
{
this.leadingSymbol = value;
object cellValue = GetValue(rowIndex);
if (cellValue != null)
{
string currentValue = System.Convert.ToString(cellValue);
Debug.Assert(this.leadingSymbol == value);
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.LeadingSymbol = value;
}
}
}
internal void SetTrailingSymbol(int rowIndex, string value)
{
this.trailingSymbol = value;
object cellValue = GetValue(rowIndex);
if (cellValue != null)
{
string currentValue = System.Convert.ToString(cellValue);
Debug.Assert(this.trailingSymbol == value);
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.TrailingSymbol = value;
}
}
}
internal void SetThousandsSeparator(int rowIndex, bool value)
{
this.thousandsSeparator = value;
if (OwnsEditingNumericUpDown(rowIndex))
{
this.EditingNumericUpDown.ThousandsSeparator = value;
}
}
public override string ToString()
{
return "DataGridViewNumericUpDownCell { ColumnIndex=" + ColumnIndex.ToString(CultureInfo.CurrentCulture) + ", RowIndex=" + RowIndex.ToString(CultureInfo.CurrentCulture) + " }";
}
internal static HorizontalAlignment TranslateAlignment(DataGridViewContentAlignment align)
{
if ((align & anyRight) != 0)
{
return HorizontalAlignment.Right;
}
else if ((align & anyCenter) != 0)
{
return HorizontalAlignment.Center;
}
else
{
return HorizontalAlignment.Left;
}
}
}
The only difference between my DataGridViewNumericUpDownEditingControl and the linked one is that I changed which class it inherited from to the custom one provided above. If you would like the code to the unmodified DataGridViewNumericUpDownColumn or DataGridViewNumericUpDownEditingControl classes they are provided in the link I put above.
Thanks for any help in advance
I was finally able to get this to work. It turns out that in the "Paint" method for the cell, I was using the paintingNumericUpDown object to paint the final version, but wasn't updating its trailing/leading symbol values.
This is how you do that:
Also, as pointed out by Jimi, I was using a NumericUpDown in the constructor for the Cell (and all throughout the class). I went ahead replaced all of the instances of 'NumericUpDown' with 'CustomNumericUpDown' in the DataGridViewNumericUpDownCell as well.
EDIT: for anyone who wants the working files, here is the link to the GitHub repo with the files (I made it public).