|
Curso básico
de programación en Visual Basic
Lección
46
Ampliar
el funcionamiento de un control mediante una clase
|
Realizamos
programas para la gestión de empresas. Empresas medianas y
pequeñas. Programas de contabilidad, cartera de pedidos
clientes proveedores, facturación control de albaranes,
tesorería cartera de cobros y pagos y estadísticas.
Nuestro
agradecimiento a todos los que por unas causas o por otras
visitan nuestra web. Gestión de empresas PYMES. Curso de
programación de Visual Basic.
|
Muy buenas, aquí estamos con una
nueva entrega del Curso Básico de Programación con Visual Basic
(el clásico) y dentro del plazo ese que me puse para que no pasara
mucho más de un mes entre cada una de ellas, a ver si lo sigo
cumpliendo...
En la
entrega anterior, (¿te has fijado que
casi en todas las entregas hago una referencia a la entrega
anterior?), te dije que en esta nueva veríamos cómo ampliar
el funcionamiento de un control ya existente. Puede que pensaras que
¡por fin! te iba a explicar cómo crear tus propios controles,
pero... no voy a tratar de ese tema, ya que voy a seguir con la
creación/definición de clases. Aunque el tema ese de crear
nuestros propios controles seguramente lo veremos en alguna entrega
posterior... y digo "seguramente" porque no se si ese tema
(cuantos temas) debería entrar en un
"curso básico", aunque la verdad es que el concepto de curso
básico creo que ya ha sido casi superado... ¿o no? no sé, en
fin...
A lo que vamos.
Como vimos en la entrega anterior, podemos definir nuevos eventos en
un formulario, en esa ocasión lo que hicimos fue añadir un
formulario al proyecto y definir un evento, el cual podríamos
interceptar desde otro sitio. Pero en el caso de los controles, no
podemos hacerlo de esa forma, por la sencilla razón de que no
existe una clase específica de un control en la que podamos añadir
esos nuevos eventos. Por tanto, tendremos que crear una clase en la
que se pueda manejar un control y será en esa clase donde definamos
nuevos eventos y también nuevas propiedades y métodos. Esto mismo
lo podemos hacer con los formularios además de con prácticamente
cualquier control.
¿Para qué ampliar un control?
Si realmente te hicieras
esa pregunta, no se si decirte que te dediques a otra cosa... pero
bueno, supongamos que te intriga el saber qué puede llevarnos a
ampliar el funcionamiento de un control... Una de las razones
podría ser que no te gusta el funcionamiento estándar de un
control y quieres que haga más cosas o que las que hace, las haga a
tu gusto o a la forma que a ti te gustaría.
Por ejemplo, imagínate que quieres que una caja de textos te
muestre los números negativos en color rojo o que te permita
indicarle que sólo quieres que acepte números o letras o... pueden
ser tantas las cosas que se te podrían ocurrir, que necesitaríamos
como mínimo una entrega para nombrarlas.
En esta entrega, (no se si me
también en la siguiente), vamos a ampliar el funcionamiento de una
caja de textos no multilínea, a la que añadiremos algún nuevo
evento y propiedades, así como también algún que otro método...
ya veremos qué le añadimos, pero sea lo que sea, te servirá para
saber cómo hacerlo y después puedes ampliarlo o modificarlo a tu
gusto.
Creando una clase para ampliar un TextBox
Vamos a empezar haciendo poca cosa con el TextBox ampliado por lo
que seguramente te preguntarás ¿para qué hacer esto?, la
respuesta es bien simple, para que no te compliques con cosas nuevas
sin llegar a comprender lo básico, que al fin y al cabo es de lo
que se trata: aprender lo básico para que estés preparado para
hacer lo que quieras... o casi...
Para probar lo que te voy a contar
a continuación, necesitaremos crear un nuevo proyecto. Al form que
se crea junto con el proyecto, vamos a añadirle dos cajas de textos
y cuatro etiquetas. Uno de esos textbox (Text2) será el que
usaremos para ampliarlo... Aunque en esta primera tentativa no lo
vamos a ampliar, simplemente lo manipularemos mediante una clase y
aprenderemos cómo usar ese control mediante la clase.
Para ello, añade un módulo de clase y dale el nombre cTextBoxEx.
Lo primero que tendremos que hacer es crear una variable a nivel de
módulo que nos permita interceptar los eventos de un control de
tipo TextBox, para ello añadiremos esta declaración:
Option Explicit
' creamos una variable para manejar el textbox
' la declaramos con WithEvents para que interceptemos los eventos
' y podamos adaptarlos a nuestro gusto
Private WithEvents mText As TextBox
Con esto lo que hacemos es declarar
la variable mText para que sea del tipo TextBox, al estar
declarada con WithEvents, podremos interceptar los eventos que
produzca el textbox que esté asociado a esa variable.
Nota:
Las variables declaradas con WithEvents, pueden ser privadas o
públicas, pero nunca se pueden crear arrays (o matrices) ni
usarlas para instanciar directamente la clase con New.
A continuación vamos a declarar
varios eventos públicos, estos eventos estarán disponible en
cualquier sitio en el que se utilice esta clase declarada con
WithEvents.
Estos eventos, simplemente serán unos cuantos de los que ya
disponen todos los TextBox, pero vamos a usarlos para que sepas
cómo manipularlos y, si así lo deseas, adaptarlos a tu gusto o
necesidad.
' Los eventos públicos que producirá la clase
' Aquí se ha usado la misma definición que los eventos originales
' pero podríamos haberlos declarado como se nos antojara.
Public Event Change()
Public Event GotFocus()
Public Event KeyDown(KeyCode As Integer, Shift As Integer)
Public Event KeyPress(KeyAscii As Integer)
Public Event KeyUp(KeyCode As Integer, Shift As Integer)
Public Event LostFocus()
Public Event MouseDown(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
Public Event MouseMove(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
Public Event MouseUp(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
Una vez que hemos definido los eventos que
nuestra clase podrá producir, tenemos que encontrar la forma de
producirlos, en esta ocasión los produciremos cuando se produzcan en el
objeto que la clase manipulará, por tanto, en los eventos producidos en el
TextBox, produciremos nuestros eventos.
' los eventos producidos por el TextBox original
' desde estos eventos se lanzarán los que la clase implementa
' por tanto, podemos cambiar ese comportamiento a nuestro gusto
Private Sub mText_Change()
RaiseEvent Change
End Sub
Private Sub mText_GotFocus()
RaiseEvent GotFocus
End Sub
Private Sub mText_KeyDown(KeyCode As Integer, Shift As Integer)
RaiseEvent KeyDown(KeyCode, Shift)
End Sub
Private Sub mText_KeyPress(KeyAscii As Integer)
RaiseEvent KeyPress(KeyAscii)
End Sub
Private Sub mText_KeyUp(KeyCode As Integer, Shift As Integer)
RaiseEvent KeyUp(KeyCode, Shift)
End Sub
Private Sub mText_LostFocus()
RaiseEvent LostFocus
End Sub
Private Sub mText_MouseDown(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
RaiseEvent MouseDown(Button, Shift, X, Y)
End Sub
Private Sub mText_MouseMove(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
RaiseEvent MouseMove(Button, Shift, X, Y)
End Sub
Private Sub mText_MouseUp(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
RaiseEvent MouseUp(Button, Shift, X, Y)
End Sub
Es decir, simplemente usamos RaiseEvent
seguido del evento que haya que producir. Esto, como puedes comprobar, es lo
que habría que hacer o si lo prefieres, es la única forma de hacerlo, al
menos en este caso, ya que los eventos del objeto mText sólo se
producen en la clase y no fuera de ella, ya que fuera, se producirán los
que la propia clase haya implementado, esto lo comprobaremos en un momento.
Ahora vamos a ver cómo indicarle a la
clase el objeto del tipo TextBox que debe manipular.
Para ello, crearemos un procedimiento (método), el cual recibirá un
parámetro, que será el TextBox que la clase debe manipular. A ese método
lo llamaremos de la misma forma que la clase... Sí, así es como se hace en
C++/C# ¿y que pasa? ¿que podría haberlo llamado New como lo hace el
VB.NET? ¡pues no! además de porque no me gusta, por la sencilla razón de
que no podemos tener un método que se llame igual que una palabra clave del
Visual Basic.
Veamos el "constructor" extra de
la clase, y digo "extra", por la sencilla razón de que todas las
clases tienen un constructor, es decir, un procedimiento que se ejecuta
cuando se crea una nueva instancia de la clase (usando New), ese
procedimiento es: Class_Initialize, pero al crear una nueva instancia
de la clase, no se puede indicar ningún parámetro, por tanto tendremos que
crear un procedimiento al que "forzosamente" haya que llamar para
que la clase sepa que TextBox es el que manipulará.
También veremos el código del destructor de la clase, (Class_Terminate),
es decir, el código que se ejecutará cuando la clase ya no esté
referenciada por ningún objeto.
' IMPORTANTE:
' Hay que usar este método para inicializar el control a ampliar
Public Sub cTextBoxEx(ByVal value As TextBox)
If TypeOf value Is TextBox Then
Set mText = value
Else
Err.Raise 13, "cTextBoxEx", "El objeto debe ser del tipo TextBox"
End If
End Sub
Private Sub Class_Initialize()
' este procedimiento se produce al crear una nueva instancia de la clase.
End Sub
Private Sub Class_Terminate()
' este se produce cuando la clase ya no se utiliza más.
' aquí liberaremos los recursos que estemos empleando.
Set mText = Nothing
End Sub
Como puedes ver en el comentario del
procedimiento cTextBoxEx, es muy IMPORTANTE que se llame a ese
método antes de hacer nada con el objeto creado a partir de la clase; esto
es así, por la sencilla razón de que si no lo hacemos, la variable mText
no estará apuntando a ningún objeto.
También puedes comprobar que hago una comprobación de que el tipo del
parámetro sea un TextBox, para ello he usado la siguiente línea:
If TypeOf
value Is TextBox Then
Realmente no sería necesario, pero... así, si decides cambiar el
parámetro a un tipo más genérico, como por ejemplo Object, te asegurarás
de que se esté asignando un objeto del tipo TextBox, que es el tipo de
datos que la clase manipulará. En caso de que el objeto no fuera del tipo
adecuado, se produciría un error indicando ese hecho, para ello usamos el
método Raise del objeto Err.
En el procedimiento (realmente es un
evento) Class_Initialize no hacemos nada, ya que, al menos por ahora,
no necesitamos hacer nada; sin embargo en el evento Class_Terminate,
asignamos a la variable mText un valor Nothing, para liberar
recursos, aunque si no lo hacemos, será el propio VB el que se encargue de
liberar esos recursos, pero, a mi me gusta hacerlo de forma explícita...
cosas mías.
Ahora veamos el código a usar en el
formulario.
Recuerda que tenemos dos objetos del tipo TextBox, uno de ellos, el Text2,
será el que nuestra clase manipulará. De las cuatro etiquetas que te
indiqué, dos de ellas se usarán para mostrar información de que se han
producido los eventos. También tendremos un botón para cerrar el
formulario (cmdCerrar).
Veamos el código del formulario:
Option Explicit
Private WithEvents txtBoxEx As cTextBoxEx
Private Sub cmdCerrar_Click()
Unload Me
End Sub
Private Sub Form_Load()
Set txtBoxEx = New cTextBoxEx
txtBoxEx.cTextBoxEx Text2
End Sub
' Text1 será un control TextBox normal
Private Sub Text1_Change()
lblTxt1 = " evento Text1_Change"
End Sub
Private Sub Text1_GotFocus()
lblTxt1 = " evento Text1_GotFocus"
End Sub
Private Sub Text1_KeyPress(KeyAscii As Integer)
lblTxt1 = " evento Text1_KeyPress, con KeyAscii= " & CStr(KeyAscii)
End Sub
' txtBoxEx será nuestra clase, que manipulará al Text2
Private Sub txtBoxEx_Change()
lblTxt2 = " evento txtBoxEx_Change"
End Sub
Private Sub txtBoxEx_GotFocus()
lblTxt2 = " evento txtBoxEx_GotFocus"
End Sub
Private Sub txtBoxEx_KeyPress(KeyAscii As Integer)
lblTxt2 = " evento txtBoxEx_KeyPress, con KeyAscii= " & CStr(KeyAscii)
End Sub
Como puedes comprobar, este código no
tiene ningún misterio, ya que es así como habría que hacerlo y para
ayudarnos, el propio Visual Basic nos informa de los eventos que produce el
objeto indicado por la variable txtBoxEx, por la sencilla razón de
que la hemos declarado con WithEvents.
En lo único que debes tener "cuidado" es hacer lo que se hace en
el evento Form_Load, ya que en ese evento se crea la nueva instancia de la
clase:
Set
txtBoxEx = New cTextBoxEx
y se asigna el textbox que debe manipular:
txtBoxEx.cTextBoxEx Text2.
Añadir nuevos miembros a la clase
Ahora que tenemos "la base" de cómo habría que manipular un
TextBox desde una clase, vamos a añadir nueva funcionalidad a esa clase,
más que nada para que tenga alguna razón el rizar el rizo, ya que si
simplemente queremos tener la misma funcionalidad que tiene un TextBox
normal, no hace falta crear ninguna clase intermedia...
Empezaremos añadiendo un método ToString, el cual se usará de la
misma forma que lo hace su hermano Visual Basic .NET (o las clases de .NET
Framework para ser más precisos).
Lo que este método hará, será devolver una cadena del contenido del
TextBox, pero, para que tenga alguna "gracia", (ya que eso se
puede hacer simplemente llamando a la propiedad Text del objeto), vamos a
usar un parámetro, el cual indicará el formato que queramos que devuelva.
Por ejemplo, si el contenido de la clase es una fecha, podría interesarnos
que devuelva esa fecha en el formato dd/mm/yyyy o si es un número que
podamos darle "formato", para ello usaremos la función Format$
del VB para que aplique el formato que le indiquemos.
Veamos el código del método ToString de la clase cTextBoxEx:
Public Function ToString(Optional ByVal formato As String = "") As String
' Si se produce algún error, ignorarlo
On Error Resume Next
'
' devolvemos el contenido del text con el formato indicado,
' si es que se ha indicado algún formato
If formato = "" Then
ToString = mText.Text
Else
ToString = Format$(mText.Text, formato)
End If
End Function
En la declaración indicamos que el
parámetro es opcional, por tanto, esta función se puede usar de dos
formas:
1- sin indicar el parámetro
2- indicando el formato a aplicar al contenido del textbox
En caso de que no se indique el parámetro, se asignará a dicha variable el
valor por defecto que se indica en la declaración, es decir: una cadena
vacía. Este hecho se tiene en cuenta en la comparación que hay dentro de
la función y se hará una cosa u otra, según se haya o no indicado el
parámetro.
En el caso de que no se haya indicado, o si se haya indicado, pero sea una
cadena vacía, se devolverá el contenido de la propiedad Text del TextBox
usado internamente.
Si se indica un formato, este será el que se use para devolver el contenido
de dicha propiedad. Debido a que es posible que se use un formato erróneo,
utilizamos una captura de errores, de forma que si se produce un error,
simplemente se continúe con la siguiente línea.
Como sabemos, si declaramos una función o
un procedimiento Sub de forma Public, ese procedimiento se convierte en un
método de la clase y por tanto lo podemos usar desde cualquier sitio. (Sí,
ya se que esto lo sabes, pero es simplemente para recordártelo y así poder
tener tiempo para poner el ejemplo.)
Veamos cómo usarlo desde el código del formulario, para ello, añade un
botón (CommandButton) al que llamaremos cmdFormato y añade este
código al evento Click:
Private Sub cmdFormato_Click()
lblTxt2 = txtBoxEx.ToString("dd/mm/yyyy")
End Sub
Bien, ya tenemos la base y el conocimiento
de cómo podemos ampliar un TextBox, ahora sólo falta echarle un poco de
imaginación y adaptarlo a nuestras necesidades.
Para ampliar más la clase, vamos a
añadirle una propiedad que indicará el tipo de datos que manipulará la
clase, esos datos podrán ser de tres tipos diferentes:
-Normal, aceptará cualquier cosa, tal como lo hace el textbox de forma
predeterminada
-Numérico, sólo aceptará números, estos a su vez podrán ser con y sin
decimales
-Fecha, aceptará sólo fechas o al menos lo intentará...
Para indicar estos valores, vamos a
declarar una enumeración pública en la clase, de forma que la propiedad
usada para el tipo de datos, utilice los valores de esa enumeración.
También declararemos una variable privada para mantener el valor de esa
propiedad, aunque podríamos haber declarado la propiedad como una variable
pública, pero esto no nos permitiría hacer ciertas comprobaciones, como
por ejemplo, comprobar que el valor asignado a esa propiedad es uno de los
valores permitidos.
Veamos cómo hacer todo esto y después utilizaremos esa propiedad para que
realmente sólo acepte ese tipo de datos:
' enumeración de los valores posibles para el tipo de datos
Public Enum eTipo
Normal
Enteros
Decimales
Fecha
End Enum
' variables (campos) privados para las propiedades de la clase
Private m_Tipo As eTipo
Este código lo tendrás que añadir en la
parte "General" de la clase y lo que sigue, lo puedes añadir al
final del código que ya tenemos:
' las propiedades de la clase
Public Property Get Tipo() As eTipo
Tipo = m_Tipo
End Property
Public Property Let Tipo(ByVal value As eTipo)
' aquí podríamos comprobar que el tipo asignado sea el correcto
Select Case value
Case eTipo.Decimales, eTipo.Enteros, eTipo.Fecha, eTipo.Normal
' no hacemos nada
Case Else
value = eTipo.Normal
End Select
m_Tipo = value
End Property
El Property Let es el código que se
utiliza cuando asignamos un valor a la propiedad, por tanto será aquí
donde hagamos la comprobación de que el valor asignado sea del tipo
correcto, es decir, uno de los valores de la enumeración. Para ello, usamos
un bloque Select Case para comprobar esos valores y en el caso de que
no sea uno de los permitidos, asignamos el valor Normal, que es el
que tendrá esa propiedad de forma predeterminada, por la sencilla razón de
que al ser el primer valor de la enumeración, valdrá cero.
Si quisiéramos que dicho valor predeterminado fuese otro, lo podríamos
asignar en el constructor de la clase, tal como podemos comprobar a
continuación:
Private Sub Class_Initialize()
' este procedimiento se produce al crear una nueva instancia de la clase.
' asignamos el valor predeterminado de la propiedad Tipo:
m_Tipo = eTipo.Normal
End Sub
Pero como te he dicho, no es necesario, por
la sencilla razón de que eTipo.Normal vale cero.
Y aquí vamos a acabar esta entrega... no
sin antes proponerte un ejercicio.
Ese ejercicio consiste en tener en cuenta si el tipo de datos que
manipulará la clase es de tipo Decimal o Entero sólo permita que se
asignen números... pero ¡cuidado! los números también permiten que se
indique si es negativo y en caso de que sean números decimales, podría ser
necesario que admita valores de tipo "científico", es decir que
el número puede contener la letra E o D, según sea del tipo Single o
Double.
La solución
en la próxima entrega.
Nos vemos
Aquí
tienes el fichero zip con el código usado en esta entrega:
basico46_cod.zip 4.97 KB

|