Cómo detectar la tecla pulsada en una celda del control DataGridView |
Por Enrique Martínez Montejo |
Última revisión: 05/09/2007 |
Este es uno de los temas estrella del grupo de noticias en español de Visual Basic .net, y su razón de ser tiene, porque en principio, todos creemos que mediante los clásicos eventos KeyDown, KeyPress y KeyUp del control DataGridView, podemos tener un cierto control sobre lo que se escribe en sus celdas. Pero cuando observamos que los eventos KeyDown y KeyUp sólo responden a un cierto tipo de teclas que son obvias (modificadoras, de desplazamiento, de función, escape, intro, etc.), y que ninguno de los tres eventos se desencadena al pulsar las teclas correspondientes a letras, números o símbolos, nos entra la desesperación y un tanto de desilusión.
Es cierto que existen otras formas de controlar de una manera general, la pulsación de las teclas que se efectúan en el control DataGridView. Por ejemplo, podemos utilizar los tres eventos de teclado del formulario que contiene al control DataGridView, o podemos también reeemplazar la función ProcessCmdKey. Pero mientras los primeros requieren que la propiedad KeyPreview del formulario tenga el valor True (para que se desencadenen los eventos del formulario antes que los mismos eventos correspondientes a los controles incluidos en aquel), el reemplazar la función ProcessCmdKey requiere trabajar con mensajes de Windows e identificadores de ventanas (los famosos valores hWnd o Handled de la API de Windows).
Aparte, tanto los eventos del formulario, como la función ProcessCmdKey, se desencadenarían, no sólo para los eventos de teclado del control DataGridView, sino para todos los eventos de teclado de los restantes controles existentes en el formulario, con lo cual tendríamos que estar comprobando qué control tiene el foco, o el identificador de ventana (hWnd) del control que ha provocado el evento. En mi opinión personal, hay otra solución más eficaz, que es tratar por todos los medios que los propios eventos de teclado del control DataGridView, sobre todo el evento KeyPress, respondan a las pulsaciones de las teclas presionadas. Pero antes de entrar en detalle sobre los eventos de teclado, vamos a conocer un poco más sobre lo que se esconde por dentro del control DataGridView, que no es, ni más ni menos, que una serie de controles que los programadores de Visual Basic suelen conocer bastante bien.
Esta clase representa una celda individual existente en un control DataGridView, que podemos referenciar fácilmente efectuando una simple consulta a la propiedad CurrentCell del control DataGridView:
' Referenciamos la celda actual
'
Dim dgvCell
As DataGridViewCell = Me.DataGridView1.CurrentCell
De esta manera ya estamos en condiciones de obtener el valor de las restantes propiedades de la celda, como el índice de la columna (ColumnIndex), el índice de la fila (RowIndex), o el valor de la celda (Value). Pero también podemos conocer igualmente, el tipo de control de edición que se aloja en la celda mediante la propiedad EditType del control DataGridView, que dependiendo del tipo asignado a la columna, en principio puede corresponderse con alguna de las siguientes clases:
Tipos de columnas | Tipos de objetos existentes en las celdas | ||
DataGridViewButtonColumn | DataGridViewButtonCell | ||
DataGridViewCheckBoxColumn | DataGridViewCheckBoxCell | ||
DataGridViewComboBoxColumn | DataGridViewComboBoxCell | ||
DataGridViewImageColumn | DataGridViewImageCell | ||
DataGridViewLinkColumn | DataGridViewLinkCell | ||
DataGridViewTextBoxColumn | DataGridViewTextBoxCell |
Parece ser que el nombre de cada clase denota bien el tipo de control que representan las columnas y celdas del control DataGridView.
Cuando referenciamos una celda cualquiera del control DataGridView, automáticamente obtendremos el tipo de control alojado en ella, que se corresponderá con el tipo de control asignado a la columna a la que pertenece la celda. Continuando con el ejemplo anterior, si la celda actual se encuentra en una columna tipo DataGridViewCheckBoxColumn, la variable objeto dgvCell referenciará a un objeto DataGridViewCheckBoxCell, y si la columna es del tipo DataGridViewTextBoxColumn, el tipo de objeto de la celda será DataGridViewTextBoxCell.
Esta clase es la encargada de mostrar texto modificable en una celda del control DataGridView, y es la clase que por defecto tendrán todas las celdas existententes en dicho control, salvo que expresamente le hayamos asignado a la columna otro tipo de clase DataGridViewXXXColumn de las mencionadas en el apartado anterior, en cuyo caso, las celdas de esa columna serán del tipo de objeto especializado.
La clase DataGridViewTextBoxCell hereda directamente de la clase base DataGridViewCell, de la que toma la inmensa mayoría de sus propiedades y métodos.
Si nuestra intención es referenciar el objeto DataGridViewTextBoxCell alojado en la celda actual del control DataGridView, ejecutaríamos algo parecido a lo siguiente:
' Referenciamos el control
DataGridViewTextBoxCell actual
'
Dim textBoxCell
As DataGridViewTextBoxCell =
_
TryCast(Me.DataGridView1.CurrentCell,
DataGridViewTextBoxCell)
La importancia de esta clase radica en que la celda actualmente seleccionada, aloja a su vez un control de la clase DataGridViewTextBoxEditingControl, que es el control que nos permite editar el valor de la celda, siempre y cuando el valor de la propiedad ReadOnly del control DataGridViewTextBoxCell sea False.
Por fin hemos llegado al objeto más recóndito de la celda; al objeto DataGridViewTextBoxEditingControl alojado en el objeto DataGridViewTextBoxCell existente a su vez en el objeto DataGridViewCell, representado éste por la celda actual del control DataGridView.
Desde luego, no me extraña que con tantos nombres parecidos, y de gran longitud, más de uno se haga un pequeño lío. Pero cuando se hayan peleado un par de veces con las celdas del control DataGridView, les aseguro que verán las cosas de otra manera.
Como bien nos da a entender el nombre del control DataGridViewTextBoxEditingControl, éste hereda directamente del clásico control de edición, el control TextBox del espacio de nombres System.Windows.Forms, por tanto, implementa todas las propiedades, métodos y eventos de éste último control, permitiéndonos editar el contenido de la celda.
Cuando una celda cualquiera del control DataGridView pasa a modo de edición, se desencadena el evento EditingControlShowing, donde el segundo parámetro de su firma es un objeto de la clase DataGridViewEditingControlShowingEventArgs (otro nombre más largo aún para añadir a la lista). Esta clase dispone únicamente de dos propiedades públicas: CellStyle (un objeto DataGridViewCellStyle que referencia a la celda editada), y Control (que es el control proporcionado para editar el valor de la celda). Será de la propiedad Control donde obtendremos el control DataGridViewTextBoxEditingControl escondido, tal y como muestra el siguiente ejemplo:
Private Sub
DataGridView1_EditingControlShowing( _
ByVal sender
As Object, _
ByVal e As
DataGridViewEditingControlShowingEventArgs) _
Handles
DataGridView1.EditingControlShowing
' Referenciamos el control TextBox
subyacente en la celda actual.
'
Dim cellTextBox
As DataGridViewTextBoxEditingControl = _
TryCast(e.Control,
DataGridViewTextBoxEditingControl)
' Obtenemos el valor actual de la
celda.
'
MessageBox.Show(cellTextBox.Text)
' Obtenemos el estilo de la celda
actual
'
Dim style
As DataGridViewCellStyle = e.CellStyle
' Mientras se edita la celda,
aumentaremos la fuente
' y rellenaremos el color de fondo de la celda actual.
'
With style
.Font =
New Font(style.Font.FontFamily, 10, FontStyle.Bold)
.BackColor = Color.Beige
End
With
End Sub
El resultado será el que se muestra en la imagen, donde se observa que la celda cambia de color de fondo y aumenta el tamaño de la fuente mientras se está editando.
Una vez que hemos sido capaces de referenciar el objeto derivado de la clase TextBox existente en una celda, ¿por qué no vamos a ser capaces de controlar las pulsaciones de teclas que se efectúan en el mismo, si en definitiva se trata de un simple control TextBox? La solución se encuentra en instalar los correspondientes controladores para los tres eventos del teclado del control TextBox: KeyDown, KeyPress y KeyUp.
En el ejemplo anterior, la variable cellTextBox está declarada a nivel del propio procedimiento que la contiene, por lo su ámbito es local. Es cierto que en el mismo procedimiento podemos instalar los correspondientes controladores de evento mediante la instrucción AddHandler, pero cada vez que se edite una celda, se estará instalando un controlador, y si se editan 20.000 celdas, se instalarán 20.000 controladores, que desencadenarán 20.000 eventos, que en el caso de estar instalados para los tres eventos de teclado, producirán 60.000 eventos de teclado. Para evitar que se instale un controlador cada vez que se edita una celda, habría que eliminar primero el controlador con la instrucción RemoveHandler, por ejemplo, en el evento CellEndEdit del control DataGridView, que se produce cuando finalice la edición de la celda.
Pero, ¡claro! Esto último tiene un gran inconveniente, porque si eliminamos el controlador de evento en otro procedimiento distinto (CellEndEdit) a aquel donde se ha instalado (EditingControlShowing), necesitaremos una variable objeto con un ámbito superior al local para poder eliminar el controlador de evento. La solución pasa por declarar a nivel del propio formulario que contiene el control DataGridView, una variable objeto que será la que sucesivamente estará referenciando al control DataGridViewTextBoxEditingControl de la celda que actualmente se encuentre en modo de edición, y declararemos la variable objeto con la palabra clave WithEvents, para no tener que añadir y eliminar manualmente los controladores para los tres eventos de teclado, tal y como muestro a continuación:
Private
WithEvents cellTextBox
As DataGridViewTextBoxEditingControl
Private Sub
cellTextBox_KeyDown( _
ByVal sender
As Object, _
ByVal e
As System.Windows.Forms.KeyEventArgs)
Handles cellTextBox.KeyDown
End Sub
Private Sub
cellTextBox_KeyPress( _
ByVal sender
As Object, _
ByVal e
As
System.Windows.Forms.KeyPressEventArgs) Handles
cellTextBox.KeyPress
End Sub
Private Sub
cellTextBox_KeyUp( _
ByVal sender
As Object, _
ByVal e
As System.Windows.Forms.KeyEventArgs)
Handles cellTextBox.KeyUp
End Sub
Private Sub
DataGridView1_EditingControlShowing( _
ByVal sender
As Object, _
ByVal e As
DataGridViewEditingControlShowingEventArgs) _
Handles
DataGridView1.EditingControlShowing
' Este evento se producirá
cuando la celda pasa a modo de edición.
' Referenciamos el control DataGridViewTextBoxEditingControl
actual.
'
cellTextBox = TryCast(e.Control,
DataGridViewTextBoxEditingControl)
' Obtenemos el estilo de la celda
actual
'
Dim style
As DataGridViewCellStyle = e.CellStyle
' Mientras se edita la celda,
aumentaremos la fuente
' y rellenaremos el color de fondo de la celda actual.
'
With style
.Font =
New Font(style.Font.FontFamily, 10, FontStyle.Bold)
.BackColor = Color.Beige
End
With
End Sub
Cómo declarar un objeto DataGridViewTextBoxEditingControl para ser utilizado por los eventos del teclado
Bueno. Ya está en condiciones de detectar la tecla pulsada en cualquier celda del control DataGridView que se encuentre actualmente en modo de edición.
Los eventos correspondientes al control de edición de la celda, unidos a los tres eventos del teclado del propio control DataGridView, en teoría nos debe de dar toda la flexibilidad posible para detectar las teclas que el usuario ha presionado.
A continuación paso a exponer algunos ejemplos para trabajar con los eventos del teclado descritos, dependiendo de ciertas acciones en concreto. Con total seguridad, en la Red de redes existirán miles de ejemplos mejores que los que encontrará aquí, pero lo que sólo pretendo es mostrarle simplemente una manera más de realizar una operación concreta, que no quiere decir que sea la mejor.
Si en nuestro control DataGridView tenemos una columna donde nos interesaría que sólo se introdujeran datos numéricos (enteros o decimales), controlaríamos la pulsación de teclas en el evento KeyPress del objeto DataGridViewTextBoxEditingControl declarado a nivel del formulario.
Por supuesto que el ejemplo también serviría para permitir los citados caracteres en un simple control TextBox, que como se ha explicado en el artículo, es el control que existe en las celdas de un control DataGridView.
Private
Sub cellTextBox_KeyPress(ByVal sender
As Object, ByVal e
As
KeyPressEventArgs) Handles
cellTextBox.KeyPress
' Referenciamos el control TextBox
subyacente.
'
Dim tb
As TextBox =
TryCast(sender, TextBox)
' Si la conversión ha fallado,
abandonamos el procedimiento.
'
If (tb
Is Nothing)
Then
e.Handled =
True
Return
End
If
Dim isDecimal, isSign,
isValidChar As
Boolean
Dim decimalSeparator
As String
= Nothing
Select
Case e.KeyChar
Case
"."c, ","c
' Obtenemos el carácter separador decimal existente
' actualmente
en la configuración regional de Windows.
'
decimalSeparator = Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator
'
Hacemos que el carácter tecleado coincida con el
' carácter
separador existentente en la configuración
' regional.
'
e.KeyChar =
decimalSeparator.Chars(0)
' Si el primer carácter que se teclea es el
separador decimal,
' o si bien,
existe un signo en el primer carácter, envío la
' combinación
'0,'.
'
If (((tb.TextLength = 0)
OrElse (tb.SelectionLength =
tb.TextLength)) OrElse _
((tb.TextLength = 1) AndAlso
((tb.Text.Contains("-")) OrElse _
(Text.Contains("+"))))) Then
' NOTA: Envío la combinación "0," mediante el
método Send,
' para que en el código cliente se desencadenen los
' eventos de teclado.
'
SendKeys.Send("{0}")
SendKeys.Send("{" & decimalSeparator & "}")
e.Handled = True
Return
End If
' Es un carácter válido.
'
isDecimal
= True
isValidChar = True
Case
"-"c, "+"c
' Signos negativo y positivo
' Es un carácter válido.
'
isSign =
True
isValidChar =
True
Case
Else
' Sólo se admitirán números y la tecla de
retroceso.
'
Dim
isDigit As Boolean =
Char.IsDigit(e.KeyChar)
Dim isControl
As Boolean =
Char.IsControl(e.KeyChar)
If ((isDigit)
OrElse (isControl))
Then
isValidChar =
True
Else
e.Handled =
True
Return
End If
End
Select
' Si es un carácter válido, y el
texto del control
' se encuentra totalmente seleccionado, elimino
' el valor actual del control.
'
If ((isValidChar)
And (tb.SelectionLength = tb.TextLength))
Then
tb.Text =
String.Empty
End
If
If (isSign)
Then
'
Admitimos los caracteres positivo y negativo, siempre y cuando
' sea el primer carácter del texto, y
no exista ya ningún otro
' signo escrito en el control.
'
If
((tb.SelectionStart <> 0) OrElse
_
(tb.Text.IndexOf("-") >= 0) OrElse _
(tb.Text.IndexOf("+") >= 0)) Then
e.Handled =
True
Return
End If
End
If
If (isDecimal)
Then
' Si en el
control hay ya escrito un separador decimal,
' deshechamos insertar otro separador
más.
'
If
(tb.Text.IndexOf(decimalSeparator) >= 0) Then
e.Handled =
True
End
If
End
If
End Sub
Como bien podrá comprobar, el ejemplo tiene en cuenta el carácter separador decimal existente en la configuración regional del usuario, que en algunos casos será el punto (.), y en otros la coma (,).
Asimismo, también tiene en cuenta si se desea introducir un numéro negativo, que sólo se permitirá si el signo menos (-) es el primer carácter existente en la celda o en el control TextBox.
Otros enlaces de interés:
Índice de la colección de ejemplos de las clases del marco de trabajo de .NET
Enrique Martínez Montejo - 2007
NOTA: La información contenida en este artículo, así como el código fuente incluido en el mismo, se proporciona COMO ESTÁ, sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo explicado, recomendado o sugerido en el presente artículo.
NOTE: The information contained in this article and source code included therein, is provided AS IS without warranty of any kind, and confers no rights. You assume any risk to implement, use or run it explained, recommended or suggested in this article.