using System; using System.Drawing; using System.Globalization; using System.Runtime.InteropServices; using System.Windows.Forms; namespace CC_Functions.Misc { /// /// Defines the editing control for the DataGridViewNumericUpDownCell custom cell type. /// internal class DataGridViewNumericUpDownEditingControl : NumericUpDown, IDataGridViewEditingControl { // The grid that owns this editing control private DataGridView dataGridView; // Stores the row index in which the editing control resides // Stores whether the editing control's value has changed or not private bool valueChanged; /// /// Constructor of the editing control class /// // The editing control must not be part of the tabbing loop public DataGridViewNumericUpDownEditingControl() => TabStop = false; // Beginning of the IDataGridViewEditingControl interface implementation /// /// Property which caches the grid that uses this editing control /// public virtual DataGridView EditingControlDataGridView { get => dataGridView; set => dataGridView = value; } /// /// Property which represents the current formatted value of the editing control /// public virtual object EditingControlFormattedValue { get => GetEditingControlFormattedValue(DataGridViewDataErrorContexts.Formatting); set => Text = (string) value; } /// /// Property which represents the row in which the editing control resides /// public virtual int EditingControlRowIndex { get; set; } /// /// Property which indicates whether the value of the editing control has changed or not /// public virtual bool EditingControlValueChanged { get => 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 => Cursors.Default; /// /// Property which indicates whether the editing control needs to be repositioned /// when its value changes. /// public virtual bool RepositionEditingControlOnValueChange => 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); } 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; } } // 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); // 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 { NumberFormatInfo numberFormatInfo = 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; } return base.ProcessKeyEventArgs(ref m); } } }