Para Novatos
y otras cosillas: incluso para VB3, etc.

Actualizado el 11/Ene/2003 (9-Dic-1997)


Contenido: Novato = Al_que_empieza

Los links:

  1. Cuidado al dimensionar varias variables con un solo DIM (11/Ene/2003)

Los links del año 1997:

  1. (24/Ene) Bucles For
  2. (24/Ene) Usa siempre Option Explicit
  3. (24/Ene) Hacer comparaciones sin importar que sean mayúsculas o minúsculas
  4. (25/Ene) Evitar que un sub entre en un bucle sin fin...
  5. (15/Feb) Sobre los argumentos con ByVal y ByRef
  6. (15/Feb) Cuidado con las cadenas pasadas al API de Windows con ByVal
  7. (22/Feb) Efecto ToolTip para VB 2.0 y superior
  8. (5/Mar) Comparaciones más rápidas con IF...THEN
  9. (24/Mar) Los declaraciones de Funciones del API y Tipos definidos en un Form o módulo de Clase
  10. (24/Mar) La visibilidad de las variables
  11. (24/Mar) El Tipo de las variables por defecto
  12. (8/Abr) Listados de ejemplo para crear un ToolBar, ToolTips y efectos 3D para VB3
  13. (6/Jul) Evitar que una aplicación se cargue por segunda vez (VB2 y posteriores)
  14. (9/Jul) Evitar los eventos en cascada... ¿te suena el OUT OF STACK SPACE?

1.- Bucles For

Me imagino que sabes hacer un bucle For. Lo que voy a contarte es como hacer que funcione más rápido.

  • Usa variables enteras para el índice
  • Procura no hacer cálculos dentro del bucle, de valores que no van a cambiar
  • No indiques la variable del bucle después de Next
  • No salgas con GOTO, (¿alguien lo usa todavía?), de un bucle For
Listado 1 (correcto, pero...)

' Las variables x, y, b serán del tipo por defecto (Variant)
Dim x, y, b, a$

a$="Hola Mundo"

For x=1 To 10
    b = Len(a$)
    For y = 1 To b
        If Mid$(a$, y, 1) = Chr$(32) Then
            GoTo OtraLinea
        End If
    Next y
OtraLinea:
Next x
Listado 2 (pues, eso...)

Dim x As Integer, y As Integer, b As Integer, a$

a$="Hola Mundo"

b = Len(a$)
For x=1 To 10
    For y = 1 To b
        If Mid$(a$, y, 1) = Chr$(32) Then
            Exit For
        End If
    Next
Next

2.- Usa siempre Option Explicit

Acostúmbrate a indicar siempre que sea necesario declarar las variables.
Esta opción era una de las pocas cosas que me gustaban de C o Pascal, ya que si no te acostumbras a declarar las variables, al final acabas creando muchas más de las que necesitas.
Antes, (léase Qbasic, GWBASIC, etc.), tenía excusa. Ahora no! Ya que puedes obligarte a declarar las variables, si indicas al inicio del código: Option Explicit
Visual Basic puede hacerlo por ti, si se lo indicas en las opciones del proyecto...
Menú Tools, solapa Environment, opción: Require Variable Declaration

La ventaja: que si te equivocas al escribir una variable, ¿a quién no le ha ocurrido?, VB te indicará que no existe.
La desventaja: Que tienes que declarar todas las variables con Dim, etc.
Ya sabes que si no declaras una variable, y por supuesto no tienes la orden Option Explicit, Visual Basic le asigna por defecto el tipo Variant y la crea si no existe. Con los arrays, crea los valores de 0 a 10.
Por tanto puede que incluso estés desperdiciando memoria:

  1. Variant ocupa más espacio de memoria que cualquier otro tipo
  2. Puede que sólo necesites un array de 5 elementos, el resto que no necesitas, está ocupando memoria que puede ser necesaria para otras cosas: velocidad, más variables, etc.

3.- Hacer comparaciones sin importar que sean mayúsculas o minúsculas

Es muy fácil creer, sobre todo para el no iniciado, que "Hola" es lo mismo que "HOLA"
Para VB, o cualquier otro lenguaje, no es así. Estos trabajan comparando carácter por carácter, y la A (a mayúsculas, código ASCII 65), no es igual a la a (a minúscula, código ASCII 97)
Si en a$ esperamos una entrada que sea igual a "Clave", al hacer esta comparación, puede que pienses que estás comprobándolo correctamente:
If a$ = "Clave" Then...
Pero el usuario, puede haber escrito "clave" o "CLAVE", (hay mucha gente que todo lo escribe en mayúsculas...), y la condición no se cumple.
Para "arreglarlo", podemos hacerlo de la siguiente forma:
If Ucase$(a$) = "CLAVE" Then... o
If LCase$(a$) = "clave" Then...
Pero hay otra forma, con la que no tenemos que preocuparnos de cómo se introduzcan los datos, cualquiera de las formas que usemos para comprobarlo, dará un resultado TRUE en la comparación...
¿Cómo?
Indicando OPTION COMPARE TEXT en el inicio del módulo en el que hagamos la comparación.
OJO, sólo a nivel de módulo y en el módulo que se especifique.
De esta forma, el usuario puede escribir "CLAVE" y esta comparación se cumplirá:
If a$ = "Clave" Then...


4.- Evita que al cambiar un ListBox entre en un bucle sin fin.

Para hacer esto, yo uso variables estáticas mientras cambio un ComboBox o un ListBox...
Ya sabes que las variables estáticas conservan el valor entre cada llamada.
A diferencia de las locales-dinámicas, que se generan cada vez que entras en un Sub.
También puedes usar una variable a nivel de módulo, en lugar de una estática.
Ya que la estática es visible sólo por ese Sub, mientras que la variable a nivel de módulo, es visible por todo el módulo, o por toda la aplicación, si está declarada como Pública o Global.

Sub Combo1_Change()
    Static YaEstoy as Boolean

    If YaEstoy Then Exit Sub

    YaEstoy = True

    'El código...

    YaEstoy = False
End Sub

5.- Sobre los argumentos con ByVal y ByRef: Argumentos por Valor o por Referencia (15/Feb)

Cuando VB pasa una variable a un procedimiento, lo hace por referencia, es decir, le dá al procedimiento la variable, de modo que cualquier cambio que se haga en dicha variable, se reflejará en el contenido de la misma.
¿Lógico? Se supone, ya que estamos acostumbrados a que así sea, (me refiero a los programadores de Basic). Si queremos pasar sólo el valor de una variable, debemos ponerla entre paréntesis, de esta forma cualquier cambio que el procedimiento realice en la variable, no será "actualizado" al contenido de la misma... Esta bien, veamos unos ejemplos...
Tenemos un sub que recibe dos variables y le suma a la primera el valor de la segunda:

Sub Sumar (A As Integer, B As Integer)
    A = A + B
End Sub

Veamos que ocurre con esto del valor y la referencia:

'Parámetros por referencia
M = 5
N = 3
Sumar M, N
Print M	'Imprimirá 8, 5+3

'Parámetros por valor
M = 5
N = 3
Sumar (M), N
Print M	'Imprimirá 5, ya que no se cambia el valor

En el segundo ejemplo, la llamada al procedimiento, podríamos haberlo puesto también de esta forma:
Sumar ByVal M, N
El valor de la variable M, al pasarse por valor, no se modifica en el suprograma Sumar, ya que lo que recibe este Sub es una copia con el valor de la variable, no una referencia a la dirección de memoria en la que la variable almacena el valor.

Todo esto viene a cuento de que en ocasiones, necesitamos manejar una varibale en un procedimiento, pero no nos interesa que se modifique "accidentalmente" el valor. Este no sería el caso del Sub Sumar, ya que la intención es modificar el valor del primer parámetro. Veamos otro ejemplo, pero esta vez con cadenas de caracteres. (la aclaración de caracteres es para el que aún no se ha enterado que normalmente en basic cuando uno se refiere a cadenas, se hace referencia a las cadenas de caracteres, de nada.)
Esta función, que de paso puede sernos de utilidad, recibe dos cadenas, y devolverá el resultado de "extraer" de la primera, el contenido de la segunda; en caso de que la segunda no esté dentro de la primera, se devuelve la primera completa.
Por ejemplo:
Extrae("Hola mundo","mundo") devolvería "Hola " y
Extrae("Hola mundo","gente") devolvería "Hola mundo"
Veamos la declaración de esta función

' Listado 1
Public Function Extrae( Cadena As String, Parte As String) As String
    Dim i As Integer

    i = Instr(Cadena, Parte)
    If i Then
	Extrae = Left$(Cadena, i-1) & Mid$(Cadena, i + Len(Parte))
    Else
	Extrae = Cadena
    End If
End Function

Tal como se manejan los datos en esta declaración, ni Cadena ni Parte se modifican. Pero podríamos tener el código "mal" definido y por "accidente" cambiar estos valores. Vemoslo con una modificación de la función, para que el valor devuelto no tenga espacios delante ni detrás:

' Listado 2
Public Function Extrae( Cadena As String, Parte As String) As String
    Dim i As Integer

    Cadena = Trim$(Cadena)
    i = Instr(Cadena, Parte)
    If i Then
	Cadena = Trim$(Left(Cadena, i-1)) & Trim$(Mid$(Cadena, i+Len(Parte)))
    End If
    Extrae = Cadena
End Function

Ahora tanto Cadena, como Extrae devolverían lo mismo. Pero puede que nos interese que Cadena siga teniendo el valor original. Esto podemos solventarlo de dos formas:
La primera, usando una variable temporal:

' Listado 3
Public Function Extrae( Cadena As String, Parte As String) As String
    Dim i As Integer
    Dim sTmp As String

    sTmp = Trim$(Cadena)
    i = Instr(sTmp, Parte)
    If i Then
	sTmp = Trim$(Left(sTmp, i-1)) & Trim$(Mid$(sTmp, i+Len(Parte)))
    End If
    Extrae = sTmp
End Function

La segunda, cambiado la definición del parámetro Cadena para que sea por valor: en el listado 2, cambiar la definición de la función para que esté de esta forma:
Public Function Extrae( ByVal Cadena As String, Parte As String) As String
De esta forma, no se reflejará ningún cambio en Cadena y no tendremos que usar otra variable, para hacer el trabajo.

Resumiendo:
ByRef (por Referencia) es la forma por defecto y se pasa la variable, o mejor dicho: se pasa la dirección en donde se almacenan los datos de esa variable, después verás a que viene esta aclaración.
ByVal pasa sólo el valor.

El siguiente apartado, te muestra las precauciones que debes tener con lo que se ha comentado, cuando se pasan valores a las API de Windows.


6.- Cuidado con las cadenas pasadas al API de Windows con ByVal (15/Feb)

Normalmente las llamadas al API, se hacen por valor, que es la forma que tiene el lenguaje C de hacerlo.
Según lo expuesto en el
apartado anterior, si se pasan variables por valor, estamos pasando el valor de la variable y por tanto no se modificará su contenido: PUES NO... Bueno, no cuando al API de Windows se refiere y si se trata de cadenas.
En el caso de las cadenas pasadas al API con ByVal, se le está pasando la dirección que apunta a los datos, (
puntero o referencia a la variable, según los señores de C ), por tanto la función podrá "libremente" modificar el contenido de la "susodicha" cadena.
Por tanto: precaución. Y para ser precavidos, pues eso: mejor prevenir que curar.
Cuando pasemos cadenas al API de Windows, u otro API cualquiera que use funciones en formato del lenguaje C, debemos asegurarnos que las cadenas son suficientemente largas, para que almacene el valor que deba almacenar.
La precaución: no ser "roñosos" a la hora de declarar la variable.
Por ejemplo, si queremos usar una función que devuelve una cadena, como sería el caso de GetWindowsDirectory, que devuelve el directorio de Windows:

Declare Function GetWindowsDirectory Lib "Kernel32" Alias "GetWindowsDirectoryA" _
		(ByVal lpBuffer As String, ByVal nSize As Long) As Long

Dim WinDir As String
Dim Cadena As String
Dim ret As Long

Cadena = String$(300, Chr$(0))
ret = GetWindowsDirectory(Cadena, Len(Cadena))
WinDir = Left$(Cadena, ret)	'Esta sería la forma "lógica" de obtener el valor
'Pero podemos "rizar el rizo" y hacerlo de esta otra:
WinDir = Left$(Cadena, Instr(Cadena, Chr$(0)) - 1)

Fijate en que he puesto dos formas de asignar a WinDir el directorio de Windows.
La primera es la que se debería hacer, ya que el valor devuelto por la función GetWindowsDirectory es la longitud de la cadena resultante.
Mientras que la segunda es una forma genérica de obtener las cadenas cuando es una función escrita en C la que la devuelve. Y es por la razón de que las cadenas en C, normalmente, siguen el formato ASCIIZ, es decir una cadena acabada en el carácter de código ASCII 0 (cero)

Lo que quiero decir con todo este rollo, es que debemos ser generosos con la longitud de las cadenas pasadas a funciones del API, si sabemos que son directorios o archivos los valores que van a devolver, con una longitud de 260, sería más que suficiente, ya que este valor es el máximo soportado por los nombres largo. Este valor, está definido en la constante MAX_PATH del SDK del API de Windows.
Por cierto en el fichero Win32API.TXT está mal, ya que indica que son 32 caracteres, cualdo deberían ser 260.


7.- Efecto ToolTip para Visual Basic 2.0 y superiores (22/Feb)

Este programa es para hacer el efecto ToolTip.
Adjunto un listado de ejemplo, válido para todos los VB a partir de la versión 2 incluida.

Si quieres echarle un vistazo al listado de la rutina que se encarga de hacer el efecto, pulsa aquí. Está en formato TXT para que puedas verlo directamente con el browser.

Listado de ejemplo en formato zip (tooltip.zip 3.27 KB)


8.- Comparaciones más rápidas con IF...THEN (5/Mar)

Este es un truco "antiguo", pero que aún sirve. Lo he sacado de una revista que tenía de mis tiempos del VIC-20.
El mirar la revista ha sido un poco de añoranza, ya que al comunicarme Álvaro Ibañez de que me había incluido en los recomendados de iWorld, comentó que
"Es bueno encontrar gente que *también* conoció los tiempos del Vic-20 & Co... :-)"
Y me puse a hojear las revistas que tenía del Commodore World, que por cierto las tengo todas, soy un "forofo" de las colecciones de revistas que me interesan...
Bueno, vamos al truco en cuestión:
Esto es para cuando se quieren hacer "múltiples" comparaciones usando el AND, por ejemplo:

If A=2 AND B=3 Then ...
Se puede sustituir por esto otro:
If A=2 Then If B=3 Then ...
O si lo hacemos estructurado:
If A=2 Then
    If B=3 Then
	'...
    End If
End If

Como verás, haciendo la comparación con IF en bloques, se entiende mejor el porqué es más rápido de la segunda forma.
Sólo se pasa al segundo IF si el primero se cumple, lo mismo que ocurriría con el AND.


9.- Las declaraciones del API y Tipos definidos en un Form o módulo de Clase (24/Mar)

Cuando declaras una función del API o de una DLL externa, se suele hacer de la siguiente forma:
Declare Nombre_Funcion...
Si pretendes usarla de forma local en un Form o un módulo de Clase, debes ponerle delante Private y así no te dará error.
Private Declare Nombre_Funcion...
Lo mísmo ocurre con los tipos definidos. Antes (VB3 y anteriores), sólo se permitía en los módulos BAS, pero ahora se pueden declarar en los Form y CLS, pero si son privados. Por tanto debes hacerlo con Private delante:
Private Type El_tipo_que_sea...
Por supuesto puedes usar también Private en un módulo BAS, pero sólo estarán visibles dentro de ese módulo.


10.- La "visibilidad" de las variables (24/Mar)

Ya que he comentado lo de las declaraciones en el "consejo" anterior, voy a aclarar un poco de que va eso de la visibilidad de las variables. Y viene al caso porque a más de uno le ha pasado que "se encuentra" atascado porque le da problemas el programilla con cosas "raras".

Veamos las posibilidades que podemos tener al declarar las variables:

Globales: Para usarlas en todo el proyecto.

Cuando queramos que una variable sea "visible" a todo el proyecto en el que estamos trabajando, (es decir que podamos usar esa variable en cualquier sitio), debemos declararla como Pública o Global en las declaraciones generales de un módulo BAS.

Globales
(Propiedades)
Para usarlas en todo el proyecto, pero poniendo el nombre del Form en el que se han declarado delante de la variable, si queremos usarla fuera del propio form.

A estas variables se las considera Propiedades del Formulario.
Esto es parecido a una variable declarada en un módulo BAS, pero a diferencia de la anterior, debemos especificar el form al que pertenece y la vida de esta variable será mientras exista el form en el que se ha declarado.

Nivel de Módulo: Para usarlas sólo en un módulo o form determinado.

Las variables declaradas en la parte de las declaraciones de un módulo, (BAS, FRM o CLS) con el atributo de Private o simplemente con DIM, se consideran locales al módulo y sólo serán visibles dentro de ese módulo.

Nivel de Procedimiento: Para usarlas sólo en un procedimiento.

Las que se declaren dentro de un procedimiento (SUB, FUNCTION o PROPERTY) sólo serán visibles dentro de ese procedimiento.

Hay que tener en cuenta que cuando se declara una variable en un nivel inferior, esta declaración "prevalece" sobre cualquier otra, de forma que usará la que tenga más cercana a donde se usa, al menos así ocurre con los procedimientos, en caso de variables a nivel de módulo con respecto a las globales, no lo he probado...

Veamos un ejemplo:

Tenemos un módulo BAS con esta declaración:

Option Explicit

Global sVar1 As String

En el Form_Load:

sVar1= "en Form_Load"
'Mostramos el contenido de sVar1
MsgBox "El contenido de sVar1 es: " & sVar1

En un procedimiento tenemos:

Private Sub Form_Click()
    Dim sVar1 As String

    sVar1 = "en Procedimiento"
    'Mostramos el contenido de sVar1
    MsgBox "El contenido de sVar1 es: " & sVar1
End Sub

Bien prueba este ejemplo y verás lo que ocurre. Para comprobarlo, haz CLICK en el form.


11.- El Tipo de las variables por defecto (24/Mar)

En los tiempos del GWBASIC y otros Basics, antes del Visual Basic versión 2.0, las variables que no se definían de un tipo en especial, eran variables tipo Single. A partir de la versión 2.0 de VB se introdujo el tipo Variant y de esta forma si la variable no se declara de un tipo en especial, pues VB entiende que son del tipo Variant. En aquellos tiempos (los del Basic normal), era una costumbre más o menos extendida, al menos para los que nos gustaba "ahorrar" memoria y ganar en velocidad, poner al principio del módulo la siguiente instrucción: Defint A-Z, con ella declarabamos todas las variables sin un tipo específico como variables INTEGER. Pues hoy he recibido un mail de Harvey Triana para que lo recomendara a los "novatos" y así lo hago.
Si sueles usar, por regla general, variables de tipo entero, acostúmbrate a incluir al principio de cada módulo (Frm, Bas o Cls) las siguientes declaraciones:

Option Explicit
Defint A-Z

De esta forma te obligará a declarar todas las variables (Option Explicit) y a las que no le des un tipo en particular, las tomará por Integer (Defint A-Z). ¡OJO CON ESTO! si estás acostumbrado a que las variables por defecto sean Variant, sobre todo en las que declares como opcionales, que siempre deben ser Variant, al menos en la versión 4, ya que VB5 permite especificar el tipo.


12.- Listados de ejemplo para crear un ToolBar, ToolTips y efectos 3D para VB3 (8/Abr)

Estos listados son un ejemplo para crear un ToolBar, incluye también la rutina para hacer el efecto de los ToolTips y hacer el efecto 3D en cualquier control.
Estos listados son válidos para VB3 (y creo que VB2)

Listados de ejemplo para crear el ToolBar, los ToolTips y ejemplo para efectos 3D (tb_vb3.zip 5.67 KB)

Aclaración: El hecho de que esté aquí es porque en su día puse en este apartado lo de los ToolTips.


13.- Evitar que una aplicación se cargue más de una vez (VB2 y posteriores) (6/Jul)

Rectificación (11/Jul): Por error u omisión... vamos, por despiste, indicaba que el PrevInstance no servía para VB3 y si que funciona; es con el VB2 (¿y anterior?) con el que hay que usar el API para impedir que una aplicación se cargue más de una vez. Gracias a Arturo Tena por la aclaración en las NEWS, pero es que algunas veces no prueba uno todo lo que dice...

Algunas veces nos puede interesar que nuestra aplicación sólo se ejecute una vez, pues bien, con el VB3 y superiores eso es fácil, ya que existe una instrucción que ahora veremos, pero en VB2, la cosa no es tan sencilla, aquí te muestro cómo hacerlo para todas las versiones.

Para VB3 y posteriores usa la propiedad PrevInstance del objeto App. Sólo tienes que poner esto en el Form_Load:

If App.PrevInstance Then
    End
End If

En el caso de VB2, podemos usar unas funciones del API de Windows: GetModuleHandle y GetModuleUsage.
Donde dice "nombre.exe" deberás usar el nombre que tenga la aplicación.
Un aviso: Esto sólo debes usarlo en Win3.x aunque en Win95 funcione... debes usarlo con prudencia...

'Poner estas declaraciones en un módulo BAS
Declare Function GetModuleHandle Lib "Kernel" (ByVal lpModuleName As String) As Integer
Declare Function GetModuleUsage Lib "Kernel" (ByVal hModule As Integer) As Integer

'En el Form de inicio, poner esto en el Form_Load:
Private Sub Form_Load()
    Dim hModule As Integer
    Dim numCopias As Integer

    hModule = GetModuleHandle("nombre.exe")
    numCopias = GetModuleUsage(hModule)
    If numCopias > 1 Then
	End
    End If
    '...
End Sub

14.- Evitar los eventos en cascada... ¿te suena OUT OF STACK SPACE? (9/Jul)

Sé que no es cosa nueva, incluso el 4º truco de esta sección trata de lo mismo, pero al parecer alguno no captasteis la intención de la solución allí presentada. Aprovecho una de las consultas hechas para poner la respuesta, que de camino sirve un poco de explicación. Allá va... Y si aún no te enteras... pregunta, pregunta.

> una: que es el error "cascada de eventos"
Esto es un error que nos suele pasar a todos cuando empezamos con el
VB, ya que el Windows está enfocado en los eventos, es decir que si
pulsas sobre un TextBox con el ratón, se producen una serie de
eventos, si pulsas una tecla en un control se producen otra serie de
eventos... total que hasta si te rascas la cabeza se produce un
evento... y si mientras estás escribiendo en un TextBox haces algo
que obligue a que se produzca ese evento de nuevo... produces una
cascada de eventos...
Por ejemplo prueba esto:

Private Sub Text1_Change()
 Dim sTmp As String

 sTmp = Text1.Text
 Text1.Text= sTmp & "a"
 sTmp =""
End Sub

Esto te producirá un error de que no hay espacio en la pila o algo
similar, ya que cada vez que se produce el "evento" Change, haces que
cambie el contenido del Text, por lo que vuelve a producirse un nuevo
evento change, sin que termine el anterior... y el VB se pone "malo"

Solución: evitar este tipo de cosas... (elemental)
¿Cómo? con trucos y variables estáticas, por ejemplo:

Private Sub Text1_Change()
 Dim sTmp As String
 Static YaEstoy As Integer

 If YaEstoy Then Exit Sub
 YaEstoy = TRUE
 sTmp = Text1.Text
 Text1.Text= sTmp & "a"
 sTmp =""
 YaEstoy = FALSE
End Sub

Al crear una variable estática mantiene el valor entre llamadas.
La primera vez que entra en el evento, al valer CERO (FALSE), pasa
por alto el IF YAESTOY THEN...
Por tanto se pasa al resto del código y se asigna un valor TRUE
Ahora al asignar el valor al Text1, se produce el evento change, pero
cuando entra por segunda vez, se encuentra con que la condición es
cierta, por tanto se sale sin continuar...
Una vez que termina el primer evento, el valor YaEstoy vuelve a
ponerse a CERO para que en el siguiente se vuelva a procesar lo que
haya que procesar...

Novato = Al_que_empieza (24/Ene)

Que nadie se mosquee. No es por faltar al respeto; pero el que se inicia, siempre es un novato; aunque algunos que llevamos algunos años, puede que sepamos menos, pero... así es la vida, los novatos son los que se inician y los veteranos, aunque sepan igual o menos, son lo que llevan más tiempo... 8-)
En fin, esta página va dedicada a aquellos que necesitan saberlo todo desde el principio.
A ver si os ponéis pronto al día en la programación en VB.
¿Por qué te das por aludido Manolo? 8-))))


ir al índice