Windows Forms. DataGridView. Columna para números con decimales (I)
Una vez definida la clase para la definición de la columna aún nos quedaría implementar la lógica de la clase NumericGridCell que se encargará de controlar los caracteres introducidos por el usuario y el formato en el que se mostrarán los números en la celda.
En primer lugar voy a crear el constructor, que simplemente llamará al constructor de la clase base, y una propiedad privada de sólo lectura que devolverá el formato de número definido en la columna.
public class NumericGridCell: DataGridViewTextBoxCell { public NumericGridCell() : base() { } private NumberFormatInfo NumberFormat { get { return ((NumericGridColumn)OwningColumn).NumberFormat; } } }
Public Class NumericGridCell Inherits DataGridViewTextBoxCell Public Sub New() MyBase.New() End Sub Private ReadOnly Property NumberFormat As NumberFormatInfo Get Return CType(OwningColumn, NumericGridColumn).NumberFormat End Get End Property End Class
En la clase NumericGridCell voy a sobreescribir la propiedad ValueType para especificar que el tipo de los valores de la celda es decimal? (el que sea un tipo de datos que acepte nulos nos permitirá diferenciar entre un valor 0 y un valor vacío), y el método InitializeEditingControl para añadir un controlador de evento al evento KeyPress del control de edición de la celda.
El controlador de este evento (el método NumericCell_KeyPress) va a ser el responsable de controlar que los caracteres introducidos son válidos. Si se trata de un carácter numérico se comprueba que el número de decimales no exceda del indicado en la propiedad DecimalDigits de la columna. Si se trata del carácter definido como separador decimal en la propiedad DecimalSeparator de la columna, se comprueba que no se haya introducido anteriormente. En caso contrario establece la propiedad Handled del objeto KeyPressEventArgs a true para anular el tratamiento de la tecla pulsada.
public override Type ValueType { get { return typeof(decimal?); } } public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) { base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); Control ctl = DataGridView.EditingControl; ctl.KeyPress += new KeyPressEventHandler(NumericCell_KeyPress); } private void NumericCell_KeyPress(object sender, KeyPressEventArgs e) { DataGridViewCell currentCell = ((IDataGridViewEditingControl)sender).EditingControlDataGridView.CurrentCell; if (!(currentCell is NumericGridCell)) return; DataGridViewTextBoxEditingControl ctl = (DataGridViewTextBoxEditingControl)sender; if (char.IsDigit(e.KeyChar)) { int separatorPosition = ctl.Text.IndexOf(NumberFormat.NumberDecimalSeparator); if (separatorPosition >= 0 && ctl.SelectionStart>separatorPosition) { int currentDecimals = ctl.Text.Length - separatorPosition - NumberFormat.NumberDecimalSeparator.Length - ctl.SelectionLength; if (currentDecimals >= NumberFormat.NumberDecimalDigits) e.Handled = true; } } else if (e.KeyChar.ToString().Equals(NumberFormat.NumberDecimalSeparator)) { e.Handled = ctl.Text.IndexOf(NumberFormat.NumberDecimalSeparator) >= 0; } else { e.Handled = !char.IsControl(e.KeyChar); } }
Public Overrides ReadOnly Property ValueType As Type Get Return GetType(Decimal?) End Get End Property Public Overrides Sub InitializeEditingControl(rowIndex As Integer, initialFormattedValue As Object, dataGridViewCellStyle As DataGridViewCellStyle) MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle) Dim ctl As Control = DataGridView.EditingControl AddHandler ctl.KeyPress, AddressOf NumericCell_KeyPress End Sub Private Sub NumericCell_KeyPress(sender As Object, e As KeyPressEventArgs) Dim currentCell As DataGridViewCell = CType(sender, IDataGridViewEditingControl).EditingControlDataGridView.CurrentCell If TypeOf currentCell IsNot NumericGridCell Then Return Dim ctl As DataGridViewTextBoxEditingControl = CType(sender, DataGridViewTextBoxEditingControl) If Char.IsDigit(e.KeyChar) Then Dim separatorPosition As Integer = ctl.Text.IndexOf(NumberFormat.NumberDecimalSeparator) If separatorPosition >= 0 AndAlso ctl.SelectionStart > separatorPosition Then Dim currentDecimals = ctl.Text.Length - separatorPosition _ - NumberFormat.NumberDecimalSeparator.Length - ctl.SelectionLength If currentDecimals >= NumberFormat.NumberDecimalDigits Then e.Handled = True End If End If ElseIf e.KeyChar.ToString().Equals(NumberFormat.NumberDecimalSeparator) Then e.Handled = ctl.Text.IndexOf(NumberFormat.NumberDecimalSeparator) >= 0 Else e.Handled = Not Char.IsControl(e.KeyChar) End If End Sub
A continuación sobrescribiré la propiedad DefaultNewRowValue para asegurarme de que el valor devuelto es un valor válido. La propiedad devolverá null si no se ha definido un valor decimal válido como valor por defecto.
public override object DefaultNewRowValue { get { object defaultValue = base.DefaultNewRowValue; if (defaultValue is decimal) return defaultValue; else return null; } }
Public Overrides ReadOnly Property DefaultNewRowValue As Object Get Dim defaultValue As Object = MyBase.DefaultNewRowValue If TypeOf defaultValue Is Decimal Then Return defaultValue Else Return Nothing End If End Get End Property
Por último, y para que el valor de la celda se represente de acuerdo al formato indicado en las propiedades de la columna, sobrescribiré GetInheritedStyle para establecer la propiedad FormatProvider del objeto DataGridViewCellSytle devuelto al formato definido en la propiedad privada NumberFormat. Y el método GetFormattedValue para que devuelva el valor de la celda correctamente formateado:
public override DataGridViewCellStyle GetInheritedStyle(DataGridViewCellStyle inheritedCellStyle, int rowIndex, bool includeColors) { DataGridViewCellStyle style= base.GetInheritedStyle(inheritedCellStyle, rowIndex, includeColors); style.FormatProvider = NumberFormat; return style; } protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context) { if (value == null) return null; else return ((decimal)value).ToString(cellStyle.FormatProvider); }
Public Overrides Function GetInheritedStyle(inheritedCellStyle As DataGridViewCellStyle, rowIndex As Integer, includeColors As Boolean) As DataGridViewCellStyle Dim style As DataGridViewCellStyle = MyBase.GetInheritedStyle(inheritedCellStyle, rowIndex, includeColors) style.FormatProvider = NumberFormat Return style End Function Protected Overrides Function GetFormattedValue(value As Object, rowIndex As Integer, ByRef cellStyle As DataGridViewCellStyle, valueTypeConverter As TypeConverter, formattedValueTypeConverter As TypeConverter, context As DataGridViewDataErrorContexts) As Object If value Is Nothing Then Return Nothing Else Return CType(value, Decimal).ToString(cellStyle.FormatProvider) End If End Function
Para probar la nueva columna simplemente deberemos añadir una nueva columna a un control DataGridView seleccionando NumericGridColumn como tipo de columna.
Dentro de las propiedades de la columna encontraremos las dos nuevas propiedades:
Al ejecutar comprobaremos que la columna nos permite introducir valores decimales con el separador decimal y el número de decimales indicados:
El código completo de este artículo, tanto en C# como en Visual Basic, está disponible en:
Códigos de muestra - Ejemplos MSDN
Este comentario ha sido eliminado por el autor.
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarHola Assier, soy yo de nuevo despues de mucho tiempo, este ejemplo era lo que estaba esperando de hace mucho tiempo, se ve realmente bueno, lo único que no me sirvió, me muestra el grid, pro al introducir los números, no me pone el punto decimal, estoy en VB 2015, proyecto de inicio es Grid test.
ResponderEliminarMe puedes ayudar ?
Gracias
excelente aporte, tenia tiempo tratando de resolver una situacion con estas caracteristicas.
ResponderEliminarHola como podria hacer una columna para direccion IP ejemplo 253.255.255.255
ResponderEliminar