diff --git a/Misc/DataGridViewNumericUpDownCell.cs b/Misc/DataGridViewNumericUpDownCell.cs
new file mode 100644
index 0000000..c6e2f91
--- /dev/null
+++ b/Misc/DataGridViewNumericUpDownCell.cs
@@ -0,0 +1,775 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace GradeCalc
+{
+ public class DataGridViewNumericUpDownCell : DataGridViewTextBoxCell
+ {
+ // Used in KeyEntersEditMode function
+ [DllImport("USER32.DLL", CharSet = CharSet.Auto)]
+ private static extern short VkKeyScan(char key);
+
+ // Used in TranslateAlignment function
+ private static readonly DataGridViewContentAlignment anyRight = DataGridViewContentAlignment.TopRight |
+ DataGridViewContentAlignment.MiddleRight |
+ DataGridViewContentAlignment.BottomRight;
+
+ private static readonly DataGridViewContentAlignment anyCenter = DataGridViewContentAlignment.TopCenter |
+ DataGridViewContentAlignment.MiddleCenter |
+ DataGridViewContentAlignment.BottomCenter;
+
+ // Default dimensions of the static rendering bitmap used for the painting of the non-edited cells
+ private const int DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapWidth = 100;
+
+ private const int DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapHeight = 22;
+
+ // Default value of the DecimalPlaces property
+ internal const int DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces = 0;
+
+ // Default value of the Increment property
+ internal const decimal DATAGRIDVIEWNUMERICUPDOWNCELL_defaultIncrement = decimal.One;
+
+ // Default value of the Maximum property
+ internal const decimal DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMaximum = (decimal)100.0;
+
+ // Default value of the Minimum property
+ internal const decimal DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMinimum = decimal.Zero;
+
+ // Default value of the ThousandsSeparator property
+ internal const bool DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator = false;
+
+ // Type of this cell's editing control
+ private static Type defaultEditType = typeof(DataGridViewNumericUpDownEditingControl);
+
+ // Type of this cell's value. The formatted value type is string, the same as the base class DataGridViewTextBoxCell
+ private static Type defaultValueType = typeof(decimal);
+
+ // The bitmap used to paint the non-edited cells via a call to NumericUpDown.DrawToBitmap
+ [ThreadStatic]
+ private static Bitmap renderingBitmap;
+
+ // The NumericUpDown control used to paint the non-edited cells via a call to NumericUpDown.DrawToBitmap
+ [ThreadStatic]
+ private static NumericUpDown paintingNumericUpDown;
+
+ private int decimalPlaces; // Caches the value of the DecimalPlaces property
+ private decimal increment; // Caches the value of the Increment property
+ private decimal minimum; // Caches the value of the Minimum property
+ private decimal maximum; // Caches the value of the Maximum property
+ private bool thousandsSeparator; // Caches the value of the ThousandsSeparator property
+
+ ///
+ /// Constructor for the DataGridViewNumericUpDownCell cell type
+ ///
+ public DataGridViewNumericUpDownCell()
+ {
+ // Create a thread specific bitmap used for the painting of the non-edited cells
+ if (renderingBitmap == null)
+ {
+ renderingBitmap = new Bitmap(DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapWidth, DATAGRIDVIEWNUMERICUPDOWNCELL_defaultRenderingBitmapHeight);
+ }
+
+ // Create a thread specific NumericUpDown control used for the painting of the non-edited cells
+ if (paintingNumericUpDown == null)
+ {
+ paintingNumericUpDown = new NumericUpDown();
+ // Some properties only need to be set once for the lifetime of the control:
+ paintingNumericUpDown.BorderStyle = BorderStyle.None;
+ paintingNumericUpDown.Maximum = decimal.MaxValue / 10;
+ paintingNumericUpDown.Minimum = decimal.MinValue / 10;
+ }
+
+ // Set the default values of the properties:
+ decimalPlaces = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces;
+ increment = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultIncrement;
+ minimum = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMinimum;
+ maximum = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMaximum;
+ thousandsSeparator = DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator;
+ }
+
+ ///
+ /// The DecimalPlaces property replicates the one from the NumericUpDown control
+ ///
+ [
+ DefaultValue(DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces)
+ ]
+ public int DecimalPlaces
+ {
+ get {
+ return decimalPlaces;
+ }
+
+ set {
+ if (value < 0 || value > 99)
+ {
+ throw new ArgumentOutOfRangeException("The DecimalPlaces property cannot be smaller than 0 or larger than 99.");
+ }
+ if (decimalPlaces != value)
+ {
+ SetDecimalPlaces(RowIndex, value);
+ OnCommonChange(); // Assure that the cell or column gets repainted and autosized if needed
+ }
+ }
+ }
+
+ ///
+ /// Returns the current DataGridView EditingControl as a DataGridViewNumericUpDownEditingControl control
+ ///
+ private DataGridViewNumericUpDownEditingControl EditingNumericUpDown
+ {
+ get {
+ return DataGridView.EditingControl as DataGridViewNumericUpDownEditingControl;
+ }
+ }
+
+ ///
+ /// Define the type of the cell's editing control
+ ///
+ public override Type EditType
+ {
+ get {
+ return defaultEditType; // the type is DataGridViewNumericUpDownEditingControl
+ }
+ }
+
+ ///
+ /// The Increment property replicates the one from the NumericUpDown control
+ ///
+ public decimal Increment
+ {
+ get {
+ return increment;
+ }
+
+ set {
+ if (value < (decimal)0.0)
+ {
+ throw new ArgumentOutOfRangeException("The Increment property cannot be smaller than 0.");
+ }
+ SetIncrement(RowIndex, value);
+ // No call to OnCommonChange is needed since the increment value does not affect the rendering of the cell.
+ }
+ }
+
+ ///
+ /// The Maximum property replicates the one from the NumericUpDown control
+ ///
+ public decimal Maximum
+ {
+ get {
+ return maximum;
+ }
+
+ set {
+ if (maximum != value)
+ {
+ SetMaximum(RowIndex, value);
+ OnCommonChange();
+ }
+ }
+ }
+
+ ///
+ /// The Minimum property replicates the one from the NumericUpDown control
+ ///
+ public decimal Minimum
+ {
+ get {
+ return minimum;
+ }
+
+ set {
+ if (minimum != value)
+ {
+ SetMinimum(RowIndex, value);
+ OnCommonChange();
+ }
+ }
+ }
+
+ ///
+ /// The ThousandsSeparator property replicates the one from the NumericUpDown control
+ ///
+ [
+ DefaultValue(DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator)
+ ]
+ public bool ThousandsSeparator
+ {
+ get {
+ return thousandsSeparator;
+ }
+
+ set {
+ if (thousandsSeparator != value)
+ {
+ SetThousandsSeparator(RowIndex, value);
+ OnCommonChange();
+ }
+ }
+ }
+
+ ///
+ /// Returns the type of the cell's Value property
+ ///
+ public override Type ValueType
+ {
+ get {
+ Type valueType = base.ValueType;
+ if (valueType != null)
+ {
+ return valueType;
+ }
+ return defaultValueType;
+ }
+ }
+
+ ///
+ /// Clones a DataGridViewNumericUpDownCell cell, copies all the custom properties.
+ ///
+ public override object Clone()
+ {
+ DataGridViewNumericUpDownCell dataGridViewCell = base.Clone() as DataGridViewNumericUpDownCell;
+ if (dataGridViewCell != null)
+ {
+ dataGridViewCell.DecimalPlaces = DecimalPlaces;
+ dataGridViewCell.Increment = Increment;
+ dataGridViewCell.Maximum = Maximum;
+ dataGridViewCell.Minimum = Minimum;
+ dataGridViewCell.ThousandsSeparator = ThousandsSeparator;
+ }
+ return dataGridViewCell;
+ }
+
+ ///
+ /// Returns the provided value constrained to be within the min and max.
+ ///
+ private decimal Constrain(decimal value)
+ {
+ Debug.Assert(minimum <= maximum);
+ if (value < minimum)
+ {
+ value = minimum;
+ }
+ if (value > maximum)
+ {
+ value = maximum;
+ }
+ return value;
+ }
+
+ ///
+ /// DetachEditingControl gets called by the DataGridView control when the editing session is ending
+ ///
+ [
+ EditorBrowsable(EditorBrowsableState.Advanced)
+ ]
+ public override void DetachEditingControl()
+ {
+ DataGridView dataGridView = 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)
+ {
+ // Editing controls get recycled. Indeed, when a DataGridViewNumericUpDownCell cell gets edited
+ // after another DataGridViewNumericUpDownCell cell, the same editing control gets reused for
+ // performance reasons (to avoid an unnecessary control destruction and creation).
+ // Here the undo buffer of the TextBox inside the NumericUpDown control gets cleared to avoid
+ // interferences between the editing sessions.
+ TextBox textBox = numericUpDown.Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ textBox.ClearUndo();
+ }
+ }
+
+ base.DetachEditingControl();
+ }
+
+ ///
+ /// Adjusts the location and size of the editing control given the alignment characteristics of the cell
+ ///
+ private Rectangle GetAdjustedEditingControlBounds(Rectangle editingControlBounds, DataGridViewCellStyle cellStyle)
+ {
+ // Add a 1 pixel padding on the left and right of the editing control
+ editingControlBounds.X += 1;
+ editingControlBounds.Width = Math.Max(0, editingControlBounds.Width - 2);
+
+ // Adjust the vertical location of the editing control:
+ 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;
+ }
+
+ ///
+ /// Customized implementation of the GetErrorIconBounds function in order to draw the potential
+ /// error icon next to the up/down buttons and not on top of them.
+ ///
+ protected override Rectangle GetErrorIconBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
+ {
+ const int ButtonsWidth = 16;
+
+ Rectangle errorIconBounds = base.GetErrorIconBounds(graphics, cellStyle, rowIndex);
+ if (DataGridView.RightToLeft == RightToLeft.Yes)
+ {
+ errorIconBounds.X = errorIconBounds.Left + ButtonsWidth;
+ }
+ else
+ {
+ errorIconBounds.X = errorIconBounds.Left - ButtonsWidth;
+ }
+ return errorIconBounds;
+ }
+
+ ///
+ /// Customized implementation of the GetFormattedValue function in order to include the decimal and thousand separator
+ /// characters in the formatted representation of the cell value.
+ ///
+ protected override object GetFormattedValue(object value,
+ int rowIndex,
+ ref DataGridViewCellStyle cellStyle,
+ TypeConverter valueTypeConverter,
+ TypeConverter formattedValueTypeConverter,
+ DataGridViewDataErrorContexts context)
+ {
+ // By default, the base implementation converts the Decimal 1234.5 into the string "1234.5"
+ object formattedValue = base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
+ string formattedNumber = formattedValue as string;
+ if (!string.IsNullOrEmpty(formattedNumber) && value != null)
+ {
+ decimal unformattedDecimal = Convert.ToDecimal(value);
+ decimal formattedDecimal = Convert.ToDecimal(formattedNumber);
+ if (unformattedDecimal == formattedDecimal)
+ {
+ // The base implementation of GetFormattedValue (which triggers the CellFormatting event) did nothing else than
+ // the typical 1234.5 to "1234.5" conversion. But depending on the values of ThousandsSeparator and DecimalPlaces,
+ // this may not be the actual string displayed. The real formatted value may be "1,234.500"
+ return formattedDecimal.ToString((ThousandsSeparator ? "N" : "F") + DecimalPlaces.ToString());
+ }
+ }
+ return formattedValue;
+ }
+
+ ///
+ /// Custom implementation of the GetPreferredSize function. This implementation uses the preferred size of the base
+ /// DataGridViewTextBoxCell cell and adds room for the up/down buttons.
+ ///
+ protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Size constraintSize)
+ {
+ if (DataGridView == null)
+ {
+ return new Size(-1, -1);
+ }
+
+ Size preferredSize = base.GetPreferredSize(graphics, cellStyle, rowIndex, constraintSize);
+ if (constraintSize.Width == 0)
+ {
+ const int ButtonsWidth = 16; // Account for the width of the up/down buttons.
+ const int ButtonMargin = 8; // Account for some blank pixels between the text and buttons.
+ preferredSize.Width += ButtonsWidth + ButtonMargin;
+ }
+ return preferredSize;
+ }
+
+ ///
+ /// Custom implementation of the InitializeEditingControl function. This function is called by the DataGridView control
+ /// at the beginning of an editing session. It makes sure that the properties of the NumericUpDown editing control are
+ /// set according to the cell properties.
+ ///
+ public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
+ {
+ base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
+ NumericUpDown numericUpDown = DataGridView.EditingControl as NumericUpDown;
+ if (numericUpDown != null)
+ {
+ numericUpDown.BorderStyle = BorderStyle.None;
+ numericUpDown.DecimalPlaces = DecimalPlaces;
+ numericUpDown.Increment = Increment;
+ numericUpDown.Maximum = Maximum;
+ numericUpDown.Minimum = Minimum;
+ numericUpDown.ThousandsSeparator = ThousandsSeparator;
+ string initialFormattedValueStr = initialFormattedValue as string;
+ if (initialFormattedValueStr == null)
+ {
+ numericUpDown.Text = string.Empty;
+ }
+ else
+ {
+ numericUpDown.Text = initialFormattedValueStr;
+ }
+ }
+ }
+
+ ///
+ /// Custom implementation of the KeyEntersEditMode function. This function is called by the DataGridView control
+ /// to decide whether a keystroke must start an editing session or not. In this case, a new session is started when
+ /// a digit or negative sign key is hit.
+ ///
+ public override bool KeyEntersEditMode(KeyEventArgs e)
+ {
+ NumberFormatInfo numberFormatInfo = 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;
+ }
+
+ ///
+ /// Called when a cell characteristic that affects its rendering and/or preferred size has changed.
+ /// This implementation only takes care of repainting the cells. The DataGridView's autosizing methods
+ /// also need to be called in cases where some grid elements autosize.
+ ///
+ private void OnCommonChange()
+ {
+ if (DataGridView != null && !DataGridView.IsDisposed && !DataGridView.Disposing)
+ {
+ if (RowIndex == -1)
+ {
+ // Invalidate and autosize column
+ DataGridView.InvalidateColumn(ColumnIndex);
+
+ // TODO: Add code to autosize the cell's column, the rows, the column headers
+ // and the row headers depending on their autosize settings.
+ // The DataGridView control does not expose a public method that takes care of this.
+ }
+ else
+ {
+ // The DataGridView control exposes a public method called UpdateCellValue
+ // that invalidates the cell so that it gets repainted and also triggers all
+ // the necessary autosizing: the cell's column and/or row, the column headers
+ // and the row headers are autosized depending on their autosize settings.
+ DataGridView.UpdateCellValue(ColumnIndex, RowIndex);
+ }
+ }
+ }
+
+ ///
+ /// Determines whether this cell, at the given row index, shows the grid's editing control or not.
+ /// The row index needs to be provided as a parameter because this cell may be shared among multiple rows.
+ ///
+ private bool OwnsEditingNumericUpDown(int rowIndex)
+ {
+ if (rowIndex == -1 || DataGridView == null)
+ {
+ return false;
+ }
+ DataGridViewNumericUpDownEditingControl numericUpDownEditingControl = DataGridView.EditingControl as DataGridViewNumericUpDownEditingControl;
+ return numericUpDownEditingControl != null && rowIndex == ((IDataGridViewEditingControl)numericUpDownEditingControl).EditingControlRowIndex;
+ }
+
+ ///
+ /// Custom paints the cell. The base implementation of the DataGridViewTextBoxCell type is called first,
+ /// dropping the icon error and content foreground parts. Those two parts are painted by this custom implementation.
+ /// In this sample, the non-edited NumericUpDown control is painted by using a call to Control.DrawToBitmap. This is
+ /// an easy solution for painting controls but it's not necessarily the most performant. An alternative would be to paint
+ /// the NumericUpDown control piece by piece (text and up/down buttons).
+ ///
+ 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 (DataGridView == null)
+ {
+ return;
+ }
+
+ // First paint the borders and background of the cell.
+ base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle,
+ paintParts & ~(DataGridViewPaintParts.ErrorIcon | DataGridViewPaintParts.ContentForeground));
+
+ Point ptCurrentCell = DataGridView.CurrentCellAddress;
+ bool cellCurrent = ptCurrentCell.X == ColumnIndex && ptCurrentCell.Y == rowIndex;
+ bool cellEdited = cellCurrent && DataGridView.EditingControl != null;
+
+ // If the cell is in editing mode, there is nothing else to paint
+ if (!cellEdited)
+ {
+ if (PartPainted(paintParts, DataGridViewPaintParts.ContentForeground))
+ {
+ // Paint a NumericUpDown control
+ // Take the borders into account
+ Rectangle borderWidths = BorderWidths(advancedBorderStyle);
+ Rectangle valBounds = cellBounds;
+ valBounds.Offset(borderWidths.X, borderWidths.Y);
+ valBounds.Width -= borderWidths.Right;
+ valBounds.Height -= borderWidths.Bottom;
+ // Also take the padding into account
+ if (cellStyle.Padding != Padding.Empty)
+ {
+ if (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;
+ }
+ // Determine the NumericUpDown control location
+ valBounds = GetAdjustedEditingControlBounds(valBounds, cellStyle);
+
+ bool cellSelected = (cellState & DataGridViewElementStates.Selected) != 0;
+
+ if (renderingBitmap.Width < valBounds.Width ||
+ renderingBitmap.Height < valBounds.Height)
+ {
+ // The static bitmap is too small, a bigger one needs to be allocated.
+ renderingBitmap.Dispose();
+ renderingBitmap = new Bitmap(valBounds.Width, valBounds.Height);
+ }
+ // Make sure the NumericUpDown control is parented to a visible control
+ if (paintingNumericUpDown.Parent == null || !paintingNumericUpDown.Parent.Visible)
+ {
+ paintingNumericUpDown.Parent = DataGridView;
+ }
+ // Set all the relevant properties
+ paintingNumericUpDown.TextAlign = TranslateAlignment(cellStyle.Alignment);
+ paintingNumericUpDown.DecimalPlaces = DecimalPlaces;
+ paintingNumericUpDown.ThousandsSeparator = ThousandsSeparator;
+ paintingNumericUpDown.Font = cellStyle.Font;
+ paintingNumericUpDown.Width = valBounds.Width;
+ paintingNumericUpDown.Height = valBounds.Height;
+ paintingNumericUpDown.RightToLeft = 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)
+ {
+ // The NumericUpDown control does not support transparent back colors
+ backColor = Color.FromArgb(255, backColor);
+ }
+ paintingNumericUpDown.BackColor = backColor;
+ }
+ // Finally paint the NumericUpDown control
+ 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))
+ {
+ // Paint the potential error icon on top of the NumericUpDown control
+ base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText,
+ cellStyle, advancedBorderStyle, DataGridViewPaintParts.ErrorIcon);
+ }
+ }
+ }
+
+ ///
+ /// Little utility function called by the Paint function to see if a particular part needs to be painted.
+ ///
+ private static bool PartPainted(DataGridViewPaintParts paintParts, DataGridViewPaintParts paintPart)
+ {
+ return (paintParts & paintPart) != 0;
+ }
+
+ ///
+ /// Custom implementation of the PositionEditingControl method called by the DataGridView control when it
+ /// needs to relocate and/or resize the editing control.
+ ///
+ 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);
+ DataGridView.EditingControl.Location = new Point(editingControlBounds.X, editingControlBounds.Y);
+ DataGridView.EditingControl.Size = new Size(editingControlBounds.Width, editingControlBounds.Height);
+ }
+
+ ///
+ /// Utility function that sets a new value for the DecimalPlaces property of the cell. This function is used by
+ /// the cell and column DecimalPlaces property. The column uses this method instead of the DecimalPlaces
+ /// property for performance reasons. This way the column can invalidate the entire column at once instead of
+ /// invalidating each cell of the column individually. A row index needs to be provided as a parameter because
+ /// this cell may be shared among multiple rows.
+ ///
+ internal void SetDecimalPlaces(int rowIndex, int value)
+ {
+ Debug.Assert(value >= 0 && value <= 99);
+ decimalPlaces = value;
+ if (OwnsEditingNumericUpDown(rowIndex))
+ {
+ EditingNumericUpDown.DecimalPlaces = value;
+ }
+ }
+
+ /// Utility function that sets a new value for the Increment property of the cell. This function is used by
+ /// the cell and column Increment property. A row index needs to be provided as a parameter because
+ /// this cell may be shared among multiple rows.
+ internal void SetIncrement(int rowIndex, decimal value)
+ {
+ Debug.Assert(value >= (decimal)0.0);
+ increment = value;
+ if (OwnsEditingNumericUpDown(rowIndex))
+ {
+ EditingNumericUpDown.Increment = value;
+ }
+ }
+
+ /// Utility function that sets a new value for the Maximum property of the cell. This function is used by
+ /// the cell and column Maximum property. The column uses this method instead of the Maximum
+ /// property for performance reasons. This way the column can invalidate the entire column at once instead of
+ /// invalidating each cell of the column individually. A row index needs to be provided as a parameter because
+ /// this cell may be shared among multiple rows.
+ internal void SetMaximum(int rowIndex, decimal value)
+ {
+ maximum = value;
+ if (minimum > maximum)
+ {
+ minimum = maximum;
+ }
+ object cellValue = GetValue(rowIndex);
+ if (cellValue != null)
+ {
+ decimal currentValue = Convert.ToDecimal(cellValue);
+ decimal constrainedValue = Constrain(currentValue);
+ if (constrainedValue != currentValue)
+ {
+ SetValue(rowIndex, constrainedValue);
+ }
+ }
+ Debug.Assert(maximum == value);
+ if (OwnsEditingNumericUpDown(rowIndex))
+ {
+ EditingNumericUpDown.Maximum = value;
+ }
+ }
+
+ /// Utility function that sets a new value for the Minimum property of the cell. This function is used by
+ /// the cell and column Minimum property. The column uses this method instead of the Minimum
+ /// property for performance reasons. This way the column can invalidate the entire column at once instead of
+ /// invalidating each cell of the column individually. A row index needs to be provided as a parameter because
+ /// this cell may be shared among multiple rows.
+ internal void SetMinimum(int rowIndex, decimal value)
+ {
+ minimum = value;
+ if (minimum > maximum)
+ {
+ maximum = value;
+ }
+ object cellValue = GetValue(rowIndex);
+ if (cellValue != null)
+ {
+ decimal currentValue = Convert.ToDecimal(cellValue);
+ decimal constrainedValue = Constrain(currentValue);
+ if (constrainedValue != currentValue)
+ {
+ SetValue(rowIndex, constrainedValue);
+ }
+ }
+ Debug.Assert(minimum == value);
+ if (OwnsEditingNumericUpDown(rowIndex))
+ {
+ EditingNumericUpDown.Minimum = value;
+ }
+ }
+
+ /// Utility function that sets a new value for the ThousandsSeparator property of the cell. This function is used by
+ /// the cell and column ThousandsSeparator property. The column uses this method instead of the ThousandsSeparator
+ /// property for performance reasons. This way the column can invalidate the entire column at once instead of
+ /// invalidating each cell of the column individually. A row index needs to be provided as a parameter because
+ /// this cell may be shared among multiple rows.
+ internal void SetThousandsSeparator(int rowIndex, bool value)
+ {
+ thousandsSeparator = value;
+ if (OwnsEditingNumericUpDown(rowIndex))
+ {
+ EditingNumericUpDown.ThousandsSeparator = value;
+ }
+ }
+
+ ///
+ /// Returns a standard textual representation of the cell.
+ ///
+ public override string ToString()
+ {
+ return "DataGridViewNumericUpDownCell { ColumnIndex=" + ColumnIndex.ToString(CultureInfo.CurrentCulture) + ", RowIndex=" + RowIndex.ToString(CultureInfo.CurrentCulture) + " }";
+ }
+
+ ///
+ /// Little utility function used by both the cell and column types to translate a DataGridViewContentAlignment value into
+ /// a HorizontalAlignment value.
+ ///
+ 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Misc/DataGridViewNumericUpDownColumn.cs b/Misc/DataGridViewNumericUpDownColumn.cs
new file mode 100644
index 0000000..28cf13c
--- /dev/null
+++ b/Misc/DataGridViewNumericUpDownColumn.cs
@@ -0,0 +1,304 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Text;
+using System.Windows.Forms;
+
+namespace GradeCalc
+{
+ ///
+ /// Custom column type dedicated to the DataGridViewNumericUpDownCell cell type.
+ ///
+ public class DataGridViewNumericUpDownColumn : DataGridViewColumn
+ {
+ ///
+ /// Constructor for the DataGridViewNumericUpDownColumn class.
+ ///
+ public DataGridViewNumericUpDownColumn() : base(new DataGridViewNumericUpDownCell())
+ {
+ }
+
+ ///
+ /// Represents the implicit cell that gets cloned when adding rows to the grid.
+ ///
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
+ ]
+ public override DataGridViewCell CellTemplate
+ {
+ get {
+ return base.CellTemplate;
+ }
+ set {
+ DataGridViewNumericUpDownCell dataGridViewNumericUpDownCell = value as DataGridViewNumericUpDownCell;
+ if (value != null && dataGridViewNumericUpDownCell == null)
+ {
+ throw new InvalidCastException("Value provided for CellTemplate must be of type DataGridViewNumericUpDownElements.DataGridViewNumericUpDownCell or derive from it.");
+ }
+ base.CellTemplate = value;
+ }
+ }
+
+ ///
+ /// Replicates the DecimalPlaces property of the DataGridViewNumericUpDownCell cell type.
+ ///
+ [
+ Category("Appearance"),
+ DefaultValue(DataGridViewNumericUpDownCell.DATAGRIDVIEWNUMERICUPDOWNCELL_defaultDecimalPlaces),
+ Description("Indicates the number of decimal places to display.")
+ ]
+ public int DecimalPlaces
+ {
+ get {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ return NumericUpDownCellTemplate.DecimalPlaces;
+ }
+ set {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ // Update the template cell so that subsequent cloned cells use the new value.
+ NumericUpDownCellTemplate.DecimalPlaces = value;
+ if (DataGridView != null)
+ {
+ // Update all the existing DataGridViewNumericUpDownCell cells in the column accordingly.
+ DataGridViewRowCollection dataGridViewRows = DataGridView.Rows;
+ int rowCount = dataGridViewRows.Count;
+ for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
+ {
+ // Be careful not to unshare rows unnecessarily.
+ // This could have severe performance repercussions.
+ DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex);
+ DataGridViewNumericUpDownCell dataGridViewCell = dataGridViewRow.Cells[Index] as DataGridViewNumericUpDownCell;
+ if (dataGridViewCell != null)
+ {
+ // Call the internal SetDecimalPlaces method instead of the property to avoid invalidation
+ // of each cell. The whole column is invalidated later in a single operation for better performance.
+ dataGridViewCell.SetDecimalPlaces(rowIndex, value);
+ }
+ }
+ DataGridView.InvalidateColumn(Index);
+ // TODO: Call the grid's autosizing methods to autosize the column, rows, column headers / row headers as needed.
+ }
+ }
+ }
+
+ ///
+ /// Replicates the Increment property of the DataGridViewNumericUpDownCell cell type.
+ ///
+ [
+ Category("Data"),
+ Description("Indicates the amount to increment or decrement on each button click.")
+ ]
+ public decimal Increment
+ {
+ get {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ return NumericUpDownCellTemplate.Increment;
+ }
+ set {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ NumericUpDownCellTemplate.Increment = value;
+ if (DataGridView != null)
+ {
+ DataGridViewRowCollection dataGridViewRows = DataGridView.Rows;
+ int rowCount = dataGridViewRows.Count;
+ for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
+ {
+ DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex);
+ DataGridViewNumericUpDownCell dataGridViewCell = dataGridViewRow.Cells[Index] as DataGridViewNumericUpDownCell;
+ if (dataGridViewCell != null)
+ {
+ dataGridViewCell.SetIncrement(rowIndex, value);
+ }
+ }
+ }
+ }
+ }
+
+ /// Indicates whether the Increment property should be persisted.
+ private bool ShouldSerializeIncrement()
+ {
+ return !Increment.Equals(DataGridViewNumericUpDownCell.DATAGRIDVIEWNUMERICUPDOWNCELL_defaultIncrement);
+ }
+
+ ///
+ /// Replicates the Maximum property of the DataGridViewNumericUpDownCell cell type.
+ ///
+ [
+ Category("Data"),
+ Description("Indicates the maximum value for the numeric up-down cells."),
+ RefreshProperties(RefreshProperties.All)
+ ]
+ public decimal Maximum
+ {
+ get {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ return NumericUpDownCellTemplate.Maximum;
+ }
+ set {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ NumericUpDownCellTemplate.Maximum = value;
+ if (DataGridView != null)
+ {
+ DataGridViewRowCollection dataGridViewRows = DataGridView.Rows;
+ int rowCount = dataGridViewRows.Count;
+ for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
+ {
+ DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex);
+ DataGridViewNumericUpDownCell dataGridViewCell = dataGridViewRow.Cells[Index] as DataGridViewNumericUpDownCell;
+ if (dataGridViewCell != null)
+ {
+ dataGridViewCell.SetMaximum(rowIndex, value);
+ }
+ }
+ DataGridView.InvalidateColumn(Index);
+ // TODO: This column and/or grid rows may need to be autosized depending on their
+ // autosize settings. Call the autosizing methods to autosize the column, rows,
+ // column headers / row headers as needed.
+ }
+ }
+ }
+
+ /// Indicates whether the Maximum property should be persisted.
+ private bool ShouldSerializeMaximum()
+ {
+ return !Maximum.Equals(DataGridViewNumericUpDownCell.DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMaximum);
+ }
+
+ ///
+ /// Replicates the Minimum property of the DataGridViewNumericUpDownCell cell type.
+ ///
+ [
+ Category("Data"),
+ Description("Indicates the minimum value for the numeric up-down cells."),
+ RefreshProperties(RefreshProperties.All)
+ ]
+ public decimal Minimum
+ {
+ get {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ return NumericUpDownCellTemplate.Minimum;
+ }
+ set {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ NumericUpDownCellTemplate.Minimum = value;
+ if (DataGridView != null)
+ {
+ DataGridViewRowCollection dataGridViewRows = DataGridView.Rows;
+ int rowCount = dataGridViewRows.Count;
+ for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
+ {
+ DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex);
+ DataGridViewNumericUpDownCell dataGridViewCell = dataGridViewRow.Cells[Index] as DataGridViewNumericUpDownCell;
+ if (dataGridViewCell != null)
+ {
+ dataGridViewCell.SetMinimum(rowIndex, value);
+ }
+ }
+ DataGridView.InvalidateColumn(Index);
+ // TODO: This column and/or grid rows may need to be autosized depending on their
+ // autosize settings. Call the autosizing methods to autosize the column, rows,
+ // column headers / row headers as needed.
+ }
+ }
+ }
+
+ /// Indicates whether the Maximum property should be persisted.
+ private bool ShouldSerializeMinimum()
+ {
+ return !Minimum.Equals(DataGridViewNumericUpDownCell.DATAGRIDVIEWNUMERICUPDOWNCELL_defaultMinimum);
+ }
+
+ ///
+ /// Replicates the ThousandsSeparator property of the DataGridViewNumericUpDownCell cell type.
+ ///
+ [
+ Category("Data"),
+ DefaultValue(DataGridViewNumericUpDownCell.DATAGRIDVIEWNUMERICUPDOWNCELL_defaultThousandsSeparator),
+ Description("Indicates whether the thousands separator will be inserted between every three decimal digits.")
+ ]
+ public bool ThousandsSeparator
+ {
+ get {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ return NumericUpDownCellTemplate.ThousandsSeparator;
+ }
+ set {
+ if (NumericUpDownCellTemplate == null)
+ {
+ throw new InvalidOperationException("Operation cannot be completed because this DataGridViewColumn does not have a CellTemplate.");
+ }
+ NumericUpDownCellTemplate.ThousandsSeparator = value;
+ if (DataGridView != null)
+ {
+ DataGridViewRowCollection dataGridViewRows = DataGridView.Rows;
+ int rowCount = dataGridViewRows.Count;
+ for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
+ {
+ DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex);
+ DataGridViewNumericUpDownCell dataGridViewCell = dataGridViewRow.Cells[Index] as DataGridViewNumericUpDownCell;
+ if (dataGridViewCell != null)
+ {
+ dataGridViewCell.SetThousandsSeparator(rowIndex, value);
+ }
+ }
+ DataGridView.InvalidateColumn(Index);
+ // TODO: This column and/or grid rows may need to be autosized depending on their
+ // autosize settings. Call the autosizing methods to autosize the column, rows,
+ // column headers / row headers as needed.
+ }
+ }
+ }
+
+ ///
+ /// Small utility function that returns the template cell as a DataGridViewNumericUpDownCell
+ ///
+ private DataGridViewNumericUpDownCell NumericUpDownCellTemplate
+ {
+ get {
+ return (DataGridViewNumericUpDownCell)CellTemplate;
+ }
+ }
+
+ ///
+ /// Returns a standard compact string representation of the column.
+ ///
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder(100);
+ sb.Append("DataGridViewNumericUpDownColumn { Name=");
+ sb.Append(Name);
+ sb.Append(", Index=");
+ sb.Append(Index.ToString(CultureInfo.CurrentCulture));
+ sb.Append(" }");
+ return sb.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Misc/DataGridViewNumericUpDownEditingControl.cs b/Misc/DataGridViewNumericUpDownEditingControl.cs
new file mode 100644
index 0000000..9b1722b
--- /dev/null
+++ b/Misc/DataGridViewNumericUpDownEditingControl.cs
@@ -0,0 +1,353 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace GradeCalc
+{
+ ///
+ /// Defines the editing control for the DataGridViewNumericUpDownCell custom cell type.
+ ///
+ internal class DataGridViewNumericUpDownEditingControl : NumericUpDown, IDataGridViewEditingControl
+ {
+ // Needed to forward keyboard messages to the child TextBox control.
+ [DllImport("USER32.DLL", CharSet = CharSet.Auto)]
+ private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
+
+ // The grid that owns this editing control
+ private DataGridView dataGridView;
+
+ // Stores whether the editing control's value has changed or not
+ private bool valueChanged;
+
+ // Stores the row index in which the editing control resides
+ private int rowIndex;
+
+ ///
+ /// Constructor of the editing control class
+ ///
+ public DataGridViewNumericUpDownEditingControl()
+ {
+ // The editing control must not be part of the tabbing loop
+ TabStop = false;
+ }
+
+ // Beginning of the IDataGridViewEditingControl interface implementation
+
+ ///
+ /// Property which caches the grid that uses this editing control
+ ///
+ public virtual DataGridView EditingControlDataGridView
+ {
+ get {
+ return dataGridView;
+ }
+ set {
+ dataGridView = value;
+ }
+ }
+
+ ///
+ /// Property which represents the current formatted value of the editing control
+ ///
+ public virtual object EditingControlFormattedValue
+ {
+ get {
+ return GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting);
+ }
+ set {
+ Text = (string)value;
+ }
+ }
+
+ ///
+ /// Property which represents the row in which the editing control resides
+ ///
+ public virtual int EditingControlRowIndex
+ {
+ get {
+ return rowIndex;
+ }
+ set {
+ rowIndex = value;
+ }
+ }
+
+ ///
+ /// Property which indicates whether the value of the editing control has changed or not
+ ///
+ public virtual bool EditingControlValueChanged
+ {
+ get {
+ return valueChanged;
+ }
+ set {
+ valueChanged = value;
+ }
+ }
+
+ ///
+ /// Property which determines which cursor must be used for the editing panel,
+ /// i.e. the parent of the editing control.
+ ///
+ public virtual Cursor EditingPanelCursor
+ {
+ get {
+ return Cursors.Default;
+ }
+ }
+
+ ///
+ /// Property which indicates whether the editing control needs to be repositioned
+ /// when its value changes.
+ ///
+ public virtual bool RepositionEditingControlOnValueChange
+ {
+ get {
+ return false;
+ }
+ }
+
+ ///
+ /// Method called by the grid before the editing control is shown so it can adapt to the
+ /// provided cell style.
+ ///
+ public virtual void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
+ {
+ Font = dataGridViewCellStyle.Font;
+ if (dataGridViewCellStyle.BackColor.A < 255)
+ {
+ // The NumericUpDown control does not support transparent back colors
+ Color opaqueBackColor = Color.FromArgb(255, dataGridViewCellStyle.BackColor);
+ BackColor = opaqueBackColor;
+ dataGridView.EditingPanel.BackColor = opaqueBackColor;
+ }
+ else
+ {
+ BackColor = dataGridViewCellStyle.BackColor;
+ }
+ ForeColor = dataGridViewCellStyle.ForeColor;
+ TextAlign = DataGridViewNumericUpDownCell.TranslateAlignment(dataGridViewCellStyle.Alignment);
+ }
+
+ ///
+ /// Method called by the grid on keystrokes to determine if the editing control is
+ /// interested in the key or not.
+ ///
+ public virtual bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)
+ {
+ switch (keyData & Keys.KeyCode)
+ {
+ case Keys.Right:
+ {
+ TextBox textBox = Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ // If the end of the selection is at the end of the string,
+ // let the DataGridView treat the key message
+ if (RightToLeft == RightToLeft.No && !(textBox.SelectionLength == 0 && textBox.SelectionStart == textBox.Text.Length) ||
+ RightToLeft == RightToLeft.Yes && !(textBox.SelectionLength == 0 && textBox.SelectionStart == 0))
+ {
+ return true;
+ }
+ }
+ break;
+ }
+
+ case Keys.Left:
+ {
+ TextBox textBox = Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ // If the end of the selection is at the begining of the string
+ // or if the entire text is selected and we did not start editing,
+ // send this character to the dataGridView, else process the key message
+ if (RightToLeft == RightToLeft.No && !(textBox.SelectionLength == 0 && textBox.SelectionStart == 0) ||
+ RightToLeft == RightToLeft.Yes && !(textBox.SelectionLength == 0 && textBox.SelectionStart == textBox.Text.Length))
+ {
+ return true;
+ }
+ }
+ break;
+ }
+
+ case Keys.Down:
+ // If the current value hasn't reached its minimum yet, handle the key. Otherwise let
+ // the grid handle it.
+ if (Value > Minimum)
+ {
+ return true;
+ }
+ break;
+
+ case Keys.Up:
+ // If the current value hasn't reached its maximum yet, handle the key. Otherwise let
+ // the grid handle it.
+ if (Value < Maximum)
+ {
+ return true;
+ }
+ break;
+
+ case Keys.Home:
+ case Keys.End:
+ {
+ // Let the grid handle the key if the entire text is selected.
+ TextBox textBox = Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ if (textBox.SelectionLength != textBox.Text.Length)
+ {
+ return true;
+ }
+ }
+ break;
+ }
+
+ case Keys.Delete:
+ {
+ // Let the grid handle the key if the carret is at the end of the text.
+ TextBox textBox = Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ if (textBox.SelectionLength > 0 ||
+ textBox.SelectionStart < textBox.Text.Length)
+ {
+ return true;
+ }
+ }
+ break;
+ }
+ }
+ return !dataGridViewWantsInputKey;
+ }
+
+ ///
+ /// Returns the current value of the editing control.
+ ///
+ public virtual object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
+ {
+ bool userEdit = UserEdit;
+ try
+ {
+ // Prevent the Value from being set to Maximum or Minimum when the cell is being painted.
+ UserEdit = (context & DataGridViewDataErrorContexts.Display) == 0;
+ return Value.ToString((ThousandsSeparator ? "N" : "F") + DecimalPlaces.ToString());
+ }
+ finally
+ {
+ UserEdit = userEdit;
+ }
+ }
+
+ ///
+ /// Called by the grid to give the editing control a chance to prepare itself for
+ /// the editing session.
+ ///
+ public virtual void PrepareEditingControlForEdit(bool selectAll)
+ {
+ TextBox textBox = Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ if (selectAll)
+ {
+ textBox.SelectAll();
+ }
+ else
+ {
+ // Do not select all the text, but
+ // position the caret at the end of the text
+ textBox.SelectionStart = textBox.Text.Length;
+ }
+ }
+ }
+
+ // End of the IDataGridViewEditingControl interface implementation
+
+ ///
+ /// Small utility function that updates the local dirty state and
+ /// notifies the grid of the value change.
+ ///
+ private void NotifyDataGridViewOfValueChange()
+ {
+ if (!valueChanged)
+ {
+ valueChanged = true;
+ dataGridView.NotifyCurrentCellDirty(true);
+ }
+ }
+
+ ///
+ /// Listen to the KeyPress notification to know when the value changed, and
+ /// notify the grid of the change.
+ ///
+ protected override void OnKeyPress(KeyPressEventArgs e)
+ {
+ base.OnKeyPress(e);
+
+ // The value changes when a digit, the decimal separator, the group separator or
+ // the negative sign is pressed.
+ bool notifyValueChange = false;
+ if (char.IsDigit(e.KeyChar))
+ {
+ notifyValueChange = true;
+ }
+ else
+ {
+ System.Globalization.NumberFormatInfo numberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
+ string decimalSeparatorStr = numberFormatInfo.NumberDecimalSeparator;
+ string groupSeparatorStr = numberFormatInfo.NumberGroupSeparator;
+ string negativeSignStr = numberFormatInfo.NegativeSign;
+ if (!string.IsNullOrEmpty(decimalSeparatorStr) && decimalSeparatorStr.Length == 1)
+ {
+ notifyValueChange = decimalSeparatorStr[0] == e.KeyChar;
+ }
+ if (!notifyValueChange && !string.IsNullOrEmpty(groupSeparatorStr) && groupSeparatorStr.Length == 1)
+ {
+ notifyValueChange = groupSeparatorStr[0] == e.KeyChar;
+ }
+ if (!notifyValueChange && !string.IsNullOrEmpty(negativeSignStr) && negativeSignStr.Length == 1)
+ {
+ notifyValueChange = negativeSignStr[0] == e.KeyChar;
+ }
+ }
+
+ if (notifyValueChange)
+ {
+ // Let the DataGridView know about the value change
+ NotifyDataGridViewOfValueChange();
+ }
+ }
+
+ ///
+ /// Listen to the ValueChanged notification to forward the change to the grid.
+ ///
+ protected override void OnValueChanged(EventArgs e)
+ {
+ base.OnValueChanged(e);
+ if (Focused)
+ {
+ // Let the DataGridView know about the value change
+ NotifyDataGridViewOfValueChange();
+ }
+ }
+
+ ///
+ /// A few keyboard messages need to be forwarded to the inner textbox of the
+ /// NumericUpDown control so that the first character pressed appears in it.
+ ///
+ protected override bool ProcessKeyEventArgs(ref Message m)
+ {
+ TextBox textBox = Controls[1] as TextBox;
+ if (textBox != null)
+ {
+ SendMessage(textBox.Handle, m.Msg, m.WParam, m.LParam);
+ return true;
+ }
+ else
+ {
+ return base.ProcessKeyEventArgs(ref m);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Misc/Misc.csproj b/Misc/Misc.csproj
index e157d72..624dfe6 100644
--- a/Misc/Misc.csproj
+++ b/Misc/Misc.csproj
@@ -34,6 +34,7 @@
+
@@ -46,6 +47,11 @@
+
+
+
+ Component
+