Curso Básico de Programación
en Visual Basic

Entrega Veinte: 24/Jun/98
por Guillermo "guille" Som

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

 

Para compensar un poco el que hayan pasado casi dos meses entre la entrega 18 y la 19, te doy esta del tirón... no es por nada, es que ya la tenía lista y realmente tenía que ir junto con la diecinueve, lo que pasa es que no quise que la entrega anterior fuese demasiado larga y así te lo tomas con más ganas... espero.

 

Ya hemos visto las distintas formas de acceder a los ficheros, ahora vamos a ver una instrucción que puede sernos útil cuando decidamos "movernos" dentro del fichero.
Me explico: cuando se trató el acceso secuencial, comenté que la información había que leerla de forma secuencial, es decir un dato después de otro, bueno, pues esto es cierto sólo a medias. No empieces a pegar saltos de alegría, porque tampoco es para tanto. El tema está en que si lees la información de forma "seguida", entonces si que es así, pero, si te entra hipo, puedes acceder a cualquier parte del fichero. ¿Cómo? Pues con la siguiente instrucción que te voy a presentar ahora...

A ver, instrucción ven, que te voy a presentar... venga, no te de vergüenza, estamos en confianza... (es que dice que no le gusta su nombre), aquí está...

Ñoras, ñores, damas y caballeros, les presento a: SEEK

Esta instrucción (que también es una función) se usa, en modo instrucción, para posicionarnos en cualquier parte del fichero abierto, tanto para leer como para escribir.
Si se usa como función, nos devuelve la posición actual, es decir en la que nos encontramos, del fichero.
Veamos cómo usarla:
Seek #numFic, posición y también variable = Seek(#numFic)

El valor devuelto es de tipo Long.
Dependiendo del modo en el que esté abierto el fichero habrá que "interpretar" el valor de distinta forma:
Para los ficheros de tipo secuencial y binario, nos da la posición en bytes (o caracteres).
Para los ficheros abiertos como aleatorios (random), nos da la posición en número de registros.

Cuando lo usamos en modo instrucción, lo que hace es posicionar el puntero dentro del fichero, de modo que lo siguiente que se lea o se escriba se hará en la posición indicada. Ni que decir tiene que el valor de la posición debe ser un valor legal. Es decir, no podemos posicionarnos en la posición CERO ni en una posición NEGATIVA, ya que no es "legal".

El uso de esta función/instrucción es útil cuando necesitemos avanzar o retroceder dentro del fichero.

Y como el movimiento se demuestra andando, vamos a ver un ejemplo.
Vamos a crear una pequeña utilidad que leerá datos de un fichero, buscando claves especiales.
Imaginate que quieres acceder a un fichero al estilo de los ficheros INI, en ese tipo de ficheros existen una serie de secciones que están "enmarcadas" entre corchetes y a continuación vienen una serie de datos (claves) con una especie de asignaciones que representan los valores de esas claves.
Veamos un ejemplo de un fichero de este tipo:

[elGuille]
nombre=guille
email=mensaje@elguille.info

La sección se llama "elGuille" y los dos campos son "nombre" y "email"
Realmente para acceder a este tipo de ficheros no necesitaríamos usar Seek, ya que podemos acceder secuencialmente, pero vamos a ver cómo podemos "posicionarnos" en una sección en concreto, después de haber leído el contenido y haber tomado "buena nota" de las posiciones.

La utilidad de ejemplo, nos va a mostrar todas las secciones disponibles y después podremos acceder rápidamente a una posición en concreto.
Veamos el aspecto del formulario y el código correspondiente:

¿Se nota que ya tengo el Win98?

'Esta fecha no está mal, es que ya lo tenía "manuscrito" desde entonces...
'Ejemplos del curso básico, ejemplo de Seek             (26/May/98)
'
Option Explicit

Private Type tSecciones
    Nombre As String
    Posicion As Long
End Type
Private aSecciones() As tSecciones
Private nSecciones As Integer

Private sFic As String
Private nFic As Integer


Private Sub Form_Load()
    'deshabilitar el botón de leer contenidos
    cmdLeerContenido.Enabled = False
    Text1 = ""
    List1.Clear

    'Creamos el fichero de ejemplo
    sFic = "basico_20.ini"
    nFic = FreeFile
    Open sFic For Output As nFic
    Print #nFic, "[elProfe]"
    Print #nFic, "Nombre=Guillermo"
    Print #nFic, "email=mensaje@elguille.info"
    Print #nFic, ""
    Print #nFic, "[Alumnos]"
    Print #nFic, "Cantidad=2"
    Print #nFic, "Nombre_01=Pepito"
    Print #nFic, "email_01=pepito@servidor.com"
    Print #nFic, "Nombre_02=Juanita"
    Print #nFic, "email_02=juani@servidora.net"
    Print #nFic, ""
    Print #nFic, "[Fecha]"
    Print #nFic, "Fichero creado el día=26/May/1998"
    Print #nFic, "Fichero actualizado el día=" & Format$(Now, "dd/mmm/yyyy")
    Close

End Sub


Private Sub cmdLeerContenido_Click()
    'Leemos el contenido de la sección seleccionada en el list
    Dim sCadena As String
    Dim nItem As Long

    nItem = List1.ListIndex
    If nItem >= 0 Then
        'borramos el contenido del Text1
        Text1 = ""
        nFic = FreeFile
        Open sFic For Input As nFic
        'posicionamos el fichero en el sitio que nos interesa
        Seek nFic, aSecciones(nItem + 1).Posicion
        'ahora leemos el contenido del fichero hasta encontrar [
    'o hasta que se acabe el fichero
        Do While Not EOF(nFic)
            Line Input #nFic, sCadena
            If Left$(sCadena, 1) = "[" Then
                'nada más que leer
                Exit Do
            Else
                Text1 = Text1 & sCadena & vbCrLf
            End If
        Loop
        Close nFic
    End If
End Sub


Private Sub cmdLeerSecciones_Click()
    Dim sCadena As String
    Dim Posicion As Long

    'Borramos el contenido del ListBox
    List1.Clear
    'Leemos las secciones disponibles
    nFic = FreeFile
    Open sFic For Input As nFic
    Do While Not EOF(nFic)
        Line Input #nFic, sCadena
        'guardamos la posición actual, justo después de leer,
        'de esta forma nos indicará la posición a partir de la
        'cual leeremos el contenido...
        Posicion = Seek(nFic)
        'Si es una sección
        If Left$(sCadena, 1) = "[" Then
            'incrementamos el número de secciones
            nSecciones = nSecciones + 1
            'redimensionamos el array
            ReDim Preserve aSecciones(nSecciones)
            'asignamos los valores al array
            With aSecciones(nSecciones)
                .Nombre = sCadena
                .Posicion = Posicion
            End With
            'añadimos esta sección a la lista
            List1.AddItem sCadena
        End If
    Loop
    Close nFic

    If nSecciones Then
        'MsgBox "Se han hallado " & nSecciones & " secciones"
        'seleccionamos el primer item del listbox
        List1.ListIndex = 0
        'habilitamos el botón
        cmdLeerContenido.Enabled = True
    Else
        MsgBox "No hay secciones en el fichero: " & sFic
    End If
End Sub


Private Sub List1_DblClick()
    'También podemos ver el contenido de una sección
    'haciendo doble-click
    cmdLeerContenido_Click
End Sub

Te explico un poco cómo funciona todo esto. Ya que de lo que se trata no es sólo de ver código sino de explicarlo, ¿verdad?

En primer lugar declaramos las variables que se van a usar.
Una de estas variables es un tipo definido que contendrá el nombre de la sección y la posición dentro del fichero.
Creamos un array dinámico (es decir redimensionable) de este nuevo tipo de datos, en él guardaremos cada una de las secciones halladas en el fichero procesado. También dimensionamos una variable que contendrá el número de secciones halladas, aunque sólo se usa mientras se lee la información del fichero, por tanto no es necesario que esté declarada en la parte general de las declaraciones del formulario.
Recuerda que las variables declaradas en la parte general de un formulario, están disponibles en todo el formulario.

En el Form_Load, que es el punto de entrada de nuestra utilidad, además de asignar el nombre del fichero y guardar un contenido de ejemplo, borramos el contenido del Text1 y el List1, además deshabilitamos el botón de leer el contenido de una sección, para que no se use hasta que no haya datos.

Al pulsar en el botón que lee las secciones, primero borramos lo que hubiese antes en el array de secciones y asignamos cero a la variable que contiene el número de secciones.
Después de abrir el fichero, en modo secuencial, vamos leyendo línea por línea, en cuanto nos encontramos con una línea que empieza por corchete [, quiere decir que hemos encontrado una sección, por tanto, incrementamos la variable que lleva la cuenta de las secciones halladas, redimensionamos el array usando Preserve para no perder la información antes almacenada, y asignamos la información del nombre y la posición que hemos obtenido con Seek.
Fíjate que la lectura de la posición se hace después de haber leído la sección del fichero, esto es así, porque lo que necesitamos saber es la posición que viene a continuación de la sección, ya que después de la sección es cuando vienen las claves. (En realidad, se asigna siempre después de leer una línea, pero a nosotros sólo nos interesa su valor cuando hemos encontrado una sección).
Seguimos leyendo hasta encontrar el final del fichero.

¿Por qué se ha usado el acceso secuencial?
Por la sencilla razón de que este tipo de fichero suele ser de tipo ASCII, es decir que no contiene caracteres "raros" y que normalmente se editan en programas del tipo NotePad. De hecho el Windows tiene asociado al bloc de notas (Notepad) para abrir los ficheros que contengan la extensión INI.
Además de que al no saberse la longitud de los datos que contiene, pero que si sabemos que cada línea termina con un retorno de carro (o cambio de línea), es más cómodo usar el Line Input # para leer toda la línea; el modo binario no nos sería de utilidad, salvo que leyésemos el fichero caracter por caracter, cosa que ralentizaría el proceso.

Para acceder a una sección en concreto, cosa que ocurre al pulsar en el botón cmdLeerContenido, simplemente abrimos el fichero, posicionamos el "puntero" en el sitio adecuado y leemos lo que haya en el fichero hasta que encontremos otra sección, (que empezará por un corchete), o hasta que lleguemos al final del fichero.
También he puesto código para que al hacer doble-click en un elemento del List1, se lea el contenido de la sección correspondiente, para ello lo único que se hace es llamar al evento Click del botón cmdLeerContenido.

 

Y hasta aquí ha llegado esta entrega, (que realmente formaba parte de la entrega 19), ahora vamos a ver un par de ejercicios para que te vayas soltando en esto de la programación con el Visual Basic.

El primer ejercicio realmente no usa Seek pero si algo parecido a la utilidad esta de leer el contenido de los ficheros del tipo INI, y consiste en crear un array con el contenido de todas las secciones y todas las claves y valores de cada sección. De forma que el fichero sólo se lea una vez y cuando se quiera mostrar el contenido de una sección se use el contenido del array.
La verdad es que no es nada fácil, pero tampoco pienses que es tan complicado como para no poder resolverlo, al menos deberías intentarlo y no coger el camino fácil de ver la solución, entre otras cosas, porque lo que se pretende con estos ejercicios es que cojas "soltura" en la programación y si además de soltarte te quedas con las "buenas" costumbres, pues mejor.
¿A que buenas costumbres me refiero?
A usar Option Explicit en todos los módulos, para de esta forma declarar las variables antes de usarlas y a "indentar" el código para que te sea más fácil seguirlo...
Después del sermón vamos a ver la pista que te doy:
La pista es que puedes usar estos tipos definidos para crear el array de claves y su contenido, y el array para almacenar cada sección y las claves de cada una de ellas.

'
Private Type tContenidos
    Clave As String
    Contenido As String
End Type

Private Type tSecciones
    Nombre As String
    NumClaves As Integer
    Contenidos() As tContenidos
End Type
Private aSecciones() As tSecciones

Como sabes cada clave tiene este formato: Clave=Contenido. Esto te lo digo para que la clave vaya por un lado y el contenido por otro, aunque sea algo más complicado que almacenar simplemente la clave y el contenido, a la larga te ayudará a manipular mejor las cadenas de caracteres y también le darán mayor utilidad al código que se cree con este ejercicio.

Como segundo ejercicio, haz lo mismo, pero en lugar de almacenar en el array cada clave y su contenido, usa Seek para "recordar" la posición de cada una de las claves de cada sección para después poder acceder a esa parte del fichero para leer lo que nos interesa.

Este segundo ejercicio es un poco más complicadillo, ya que necesitará usar de forma correcta Seek, tanto en modo función como en modo instrucción.
Este es el tipo de datos que tendrás que usar:

'
Private Type tSecciones
    Nombre As String
    NumClaves As Integer
    Contenidos() As Long
End Type
Private aSecciones() As tSecciones

Fíjate que aquí sólo guardamos en Contenidos la posición de cada clave dentro del fichero.

Suerte y no desesperes si no lo consigues, no me gustaría perder a todos mis alumnos de golpe... creo que no lo soportaría.

Las soluciones de los dos ejercicios están en este link.

 

Y ya sólo queda que hagas tu comentario sobre esta entrega. Y si hay algo que necesites que te aclare, relacionado con lo que se ha visto en esta entrega, no te cortes y pregúntame, pero sólo relacionado con lo que estamos viendo, ¿vale?

Nos vemos.
Guillermo


 
entrega anterior ir al índice siguiente entrega

Ir al índice principal del Guille