Curso Básico de Programación
en Visual Basic

 

Entrega Cuarenta y seis: 03/Mar/2003
por Guillermo "guille" Som

Si quieres linkar con las otras entregas, desde el índice lo puedes hacer

 

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
Guillermo
 

Aquí tienes el fichero zip con el código usado en esta entrega:  basico46_cod.zip 4.97 KB


 
entrega anterior ir al índice siguiente entrega

Ir al índice principal del Guille