Un Gran Proyecto, Paso a Paso

 

Séptima Entrega (15/Abr/97)
...y al séptimo día, viendo que lo que había hecho estaba bien, descansó.
(espero que no mosquee a ningún "religioso", por el subtítulo... no soy Dios, pero es que venía el comentario a "huevo")

Entregas anteriores: Primera, Segunda, Tercera, Cuarta, Quinta, Sexta
Lo dicho en otras ocasiones, es recomendable que les eches una visual para seguir el hilo del proyecto.

Bajate las páginas HTML y los gráficos de las 7 entregas. (gsnotas_htm.zip 84.3 KB)
Bajate los listados del proyecto. (gsnotas.zip 32.4 KB)
(Estos tamaños variarán según el número de entregas; para saber el tamaño actual, deberías ver la última entrega)


Esta entrega, cómo viene siendo habitual en las últimas, empezaremos ajustando algunas cosas. Y para que puedas acceder desde el principio a lo que más te interese, te pongo una relación de que es lo que vas a encontrar en esta, por ahora última entrega de este proyecto. Más adelante usaremos otras cosas, pero la voy a dejar "reposar" durante algún tiempo. Lo que se pueda "innovar" te lo dejo a tí. Más adelante, a lo mejor la semana que viene, quién sabe, seguiremos con otras cosillas, como puede ser el uso de la otra tabla de la base de datos... ya veremos.
Aquí tienes lo que he puesto en esta página:

  1. Secciones particulares para los últimos datos buscados/reemplazados.
  2. Asignar el tamaño y la posición de la ventana (personalizada para cada usuario)
  3. Nuevas secciones: Buscar, Reemplazar, Seleccionar Todo, dentro del textbox, no en la base de datos.
  4. Añadir el "shortcut" Alt+F4 al menú de Salir
  5. Asignar el usuario y la base a cargar de forma automática.

 


1.- Secciones particulares para los últimos datos buscados/reemplazados. (vale la pena leer esto)

Esto en principio tal como está estructurado el programa y la forma en que se asignan los nombres de las secciones para Buscar y Reemplazar en el form gsDBR, no debería tener más problema que hacer lo siguiente: asignar los valores correspondientes al tag de los combos del susodicho form, es decir antes de llamar a cada una de las funciones de buscar y reemplazar hacer esta asignación:

'en la búsqueda:

        'Para "personalizar" la sección de búsqueda...
        gsDBR.Combo1(0).Tag = "Buscar_" & sUsuario
        If gsBuscar(sBuscar, , "Buscar datos") > cFFAc_IDLE Then

'en el reemplazo:

        'Personalizar las secciones de buscar/reemplazar
        gsDBR.Combo1(0).Tag = "Buscar_" & sUsuario
        gsDBR.Combo1(1).Tag = "Reemplazar_" & sUsuario
        iFFAccion = gsReemplazar(sFFBuscar, sFFPoner)

Aclaración: si te has fijado, (si aún no lo has hecho, ¡ya va siendo hora!), en el Form gsDBR se usa el TAG de los combos para saber que nombre de sección se debe usar y el archivo de configuración será es indicado en la variable sFFIni.

'en el Form_Load del formulario gsDBR, tenemos:

    For i = 0 To 1
        sTag = Trim$(Combo1(i).Tag)
        n = 0
        n = LeerIni(sFFIni, sTag, "NumEntradas", n)
 

Pruebalo y verás lo que ocurre.

¿Lo has probado? No, porque seguramente ni te molestarás en seguir esto haciendolo tú, es más cómodo usar el código que envio. ¡No importa! Yo haría lo mismo. Te lo explico:
Cuando se carga el Form gsDBR, se ejecuta el código que está en ese procedimiento y una vez procesado, se continua con lo que sea. Al asiganar el valor del combo en el form gsNotas, se efectúa un Load automático del formulario de búsqueda, por tanto al principio el Tag de los combos toma los valores asignados en tiempo de diseño, es decir Buscar y Reemplazar, y se lee el contenido de estas secciones; pero después de cargarse, se asigna el nuevo valor: "Buscar_" & sUsuario, por tanto al actualizarse en el archivo INI el contenido de los combos, se usa el nuevo valor del Tag. Por tanto se leerá de la sección "Buscar", pero se almacenará en la sección específica: "Buscar_" & sUsuario.
¿Lo captas? Espero que sí. Pero si no lo has entendido, pregúntamelo: No lo he captado tío (y te lo explico).

Para solucionar esto, podríamos poner la parte correspondiente a la rutina de asignación (lectura) de las secciones de Buscar y Reemplazar fuera del código Form_Load del gsDBR. Pero, ¿cómo se lee entonces el contenido de estas secciones?
Podríamos pensar, yo así lo hice, que poniendo un simple DoEvents (no siempre soluciona todos nuestros problemas) y leyendo las secciones en un procedimiento fuera del Form_Load, se arreglaría esto. Pero NO. Y es porque al estar la llamada dentro del form_load, se ejecuta mientras se está cargando y después se termina de cargar el form. Veamos el ejemplo:

'En el form_load de gsDBR:
DoEvents
IniciarCombos

'Añadir este procedimiento al form gsDBR
Private Sub IniciarCombo()
    Dim j As Integer
    Dim i As Integer
    Dim n As Integer
    Dim vTmp As String
    Dim sTmp As String
    Dim sTag As String
    
    'asignar los valores anteriores del combo
    For i = 0 To 1
        sTag = Trim$(Combo1(i).Tag)
        n = 0
        n = LeerIni(sFFIni, sTag, "NumEntradas", n)
        If n > NumeroMaximoDeItems Then n = NumeroMaximoDeItems
        'For j = n - 1 To 0 Step -1
        For j = 0 To n - 1
            vTmp = "Entrada" & CStr(j)
            sTmp = LeerIni(sFFIni, sTag, vTmp, "")
            If Len(sTmp) Then
                Combo1(i).AddItem sTmp
            End If
        Next
    Next
    Combo1(0).Text = ""
    Combo1(1).Text = ""
End Sub

Como he dicho antes, esto no funciona.
¿Cómo se arregla el tema?
Pues usando un temporizador, para engañar al personal, en este caso al form_load.
¿Me lo explicas?
Pues, es fácil, una vez que se sabe de que va el tema, (
tengo que reconocer, y no me importa hacerlo, que esta solución la he visto en un libro y me parece "excelente", no sé cómo no se me ha ocurrido a mi!!!), fuera de coña, siempre se aprenden cosas cuando se quiere y se lee lo que otros hacen... así que espero que esto os sirva de algo.
Vamos a la explicación:
Cuando se usa un Timer, al cargar el form y usar el Timer.Enabled, se producirá el evento después de que pase el tiempo asignado en el TimerInterval, por tanto tendremos ese "lapso" de tiempo para permitir que el form termine de cargarse y dentro del evento Timer del temporizador leeremos el contenido del archivo ini. Como a este procedimiento se llega después de haber terminado la carga del formulario, el código permite que se asigne el nuevo valor al Tag y así se leerá de forma correcta de la sección que queremos...

Me he enterado, quiero pasar de la explicación.(
deberías verla para saber lo que se añade)

No me he enterado, es que soy novatillo/a o simplemente ¡no te explicas Guille!
(
¡¡¡ GRRRR !!! (¡"Carmate" Guille!, esa era la voz de mi otro yo))
Veamos el código y cómo funciona esto del Timer...

'Hay que poner un Timer en el form de gsDBR
'En el Form_Load tendremos:

    'En un sub, para que acepte el tag de los combos.
    'Si se dejaba en el Form_Load, no se actualizaban los valores de inicio
    Timer1.Interval = 100
    Timer1.Enabled = True

'Añadir este sub al formulario gsDBR:

Private Sub Timer1_Timer()
    'asignar los valores anteriores del combo
    Timer1.Enabled = False	'Ya no necesitaremos más este evento!!!
    IniciarCombo
End Sub

Fijate que se deshabilita el Timer dentro del evento, para así no volver a usarlo. Esto sólo se hace para dar un respiro al form load y así terminar la carga del formulario y después hacer el resto. El comportamiento es el siguiente:

Al hacer la llamada: gsDBR.Combo1(0).Tag = "Buscar_" & sUsuario, se efectúa un Load antes de la asignación del tag, es decir que cuando el Visual Basic se encuentra con gsDBR. (hasta el punto), piensa: HEY MAN (ten en cuenta que es yankee) ESTE QUIERE CARGAR EL FORM gsDBR, así que lo carga y se ejecuta todo lo que está en ese procedimiento de carga.
Pero aquí tenemos lo del Timer, así que vuelve a pensar: EL TIMER LO DEJO "PA" MÁS DESPUÉS (
es que ya le he enseñado algunas palabrillas de mi tierra) y sale del Form_Load y continúa con lo que hay después del punto, es decir asigna la cadena al Tag del Combo. Después pasa el intervalo que hemos asignado, (trabaja rápido el guiri este, ¿eh?), y continúa con la lectura de los valores para el combo. Por tanto se asignará de forma correcta.
¡Ahora si que lo he entendido! (
y si no es así, que te zurzan (GUILLE, ¡COMPORTATE!))

Bueno, espero que ahora la cosa esté clara. Ya puedes usar de forma fiable estas asignaciones de secciones personalizadas.
Una cosa importante, es que la parte que inicia el Timer debería estar al final del Form_Load, porque si tenemos mucho proceso en el interior de este procedimiento, puede que se "dispare" el temporizador y no nos haya servido para nada lo que hemos hecho, así que o bien le pones un intervalo largo, (
no te lo recomiendo), o bien lo pones al final, (más seguro)


2.- Asignar el tamaño y la posición de la ventana (personalizada para cada usuario)

Esta es otra cosa que me gusta tener en la mayoría de las aplicaciones: Guardar el tamaño y la posición de la ventana.
Para ello tendremos una sección en nuestro archivo de inicialización o configuración , como prefieras, para almacenar esos valores, estos deberán guardarse cada vez que se cambia el tamaño de la ventana, pero teniendo en cuenta que no se actualicen cuando está minimizada.
Al cargar el Form leeremos el tamaño y al cambiar el tamaño, guardaremos la posición y tamaño, para usarlos en futuras ocasiones. Pero debes tener un par de cosillas en mente:
Cuando se carga por primera vez el Formulario, se ejecuta un Resize, así que si guardamos los valores en el Form_Resize, nunca podremos asignar los valores "particulares". Para solucionarlo (y no enrollarme con chorradas como en la sección anterior), deberás tener una variable que sirva de indicador para saber si es la primera vez que cambias el tamaño, o por el controrario, ya lo has hecho con anterioridad. Veamos el código del Form_Load (de gsNotas):

'...
    'El archivo de configuración
    sFFIni = ficIni
    'le damos tiempo para que haga el Resize
    DoEvents
    'Ahora podemos asignar el tamaño y posición que tenía antes:
    Dim tL&, tT&, tW&, tH&
    tL = Val(LeerIni(ficIni, "Posición_" & sUsuario, "gsNotas_Left", CStr(Left)))
    tT = Val(LeerIni(ficIni, "Posición_" & sUsuario, "gsNotas_Top", CStr(Top)))
    tW = Val(LeerIni(ficIni, "Posición_" & sUsuario, "gsNotas_Width", CStr(Width)))
    tH = Val(LeerIni(ficIni, "Posición_" & sUsuario, "gsNotas_Height", CStr(Height)))
    'Asignamos el nuevo tamaño
    Move tL, tT, tW, tH
    
    Show
    DoEvents

'En el form_Resize, añade al final este código:

    'Asegurarnos de que no se actualice la primera vez que se carga.
    Static YaHeEstado As Boolean
    
    If YaHeEstado Then
        'Guardar el tamaño.
        GuardarIni ficIni, "Posición_" & sUsuario, "gsNotas_Left", CStr(Left)
        GuardarIni ficIni, "Posición_" & sUsuario, "gsNotas_Top", CStr(Top)
        GuardarIni ficIni, "Posición_" & sUsuario, "gsNotas_Width", CStr(Width)
        GuardarIni ficIni, "Posición_" & sUsuario, "gsNotas_Height", CStr(Height)
    Else
        YaHeEstado = True
    End If

Fijate que es importante, (una vez que se ve, parece de "cajón"), el uso de la variable estática, de esta forma estamos seguros de proceder de forma correcta.
Para rematar el tema de la posición, deberías guardar la posición en que estaba cuando descargas el formulario, ya que el Resize sólo controla el tamaño, no la posición. Para ello en el Form_Unload, pon esto, siempre que esté en tamaño normal:

    'Sólo si está mostrada de forma normal
    If WindowState = vbNormal Then
        GuardarIni ficIni, "Posición_" & sUsuario, "gsNotas_Left", CStr(Left)
        GuardarIni ficIni, "Posición_" & sUsuario, "gsNotas_Top", CStr(Top)
    End If

Y eso es todo.


3.- Nuevas opciones: Buscar, Reemplazar, Seleccionar Todo, dentro del textbox, no en la base de datos.

En ocasiones es necesario sólo buscar dentro del TextBox en el que estamos en ese momento, esto evitará tener que buscar en toda la base, cuando lo que pretendemos es buscar sólo en el sitio en el que nos encontramos.
Para ello, vamos a añadir al menú de edición dos nuevas opciones:
---Buscar en el campo actual (F5)
---Buscar siguiente en el campo actual (F7)
---Reemplazar en el campo actual (F8)
---Seleccionar todo el campo actual (Ctrl+E)
Añadiremos unas nuevas constantes, para proceder de forma correcta, (en las declaraciones del Form gsNotas)
Un nuevo procedimiento (Accion) para procesar estas nuevas opciones y las asignaciones a los eventos del menú.

En este procedimiento, he utilizado un par de variables estáticas, para "recordar" en que campo hemos buscado anteriormente y la posición en que nos quedamos, para seguir buscando. Porque a diferencia de la búsqueda en la base de datos, que sólo encuentra la primera coincidencia en cada registro, al buscar dentro de un mismo textbox, nos interesa buscar todas las palabras que estén, no sólo la primera.
Veamos todo este tema.

'Constantes para la edición en el campo actual:
'
Const CMD_BuscarActual = 101
Const CMD_BuscarSigActual = 102
Const CMD_ReemplazarActual = 103
Const CMD_SeleccionarTodo = 104


'Los nuevos menús añadidos:

Private Sub mnuBuscarActual_Click()
    Accion CMD_BuscarActual
End Sub

Private Sub mnuBuscSigActual_Click()
    Accion CMD_BuscarSigActual
End Sub

Private Sub mnuReemplazarActual_Click()
    Accion CMD_ReemplazarActual
End Sub

Private Sub mnuSelecTodo_Click()
    Accion CMD_SeleccionarTodo
End Sub


'El procedimiento para las nuevas acciones:
Private Sub Accion(Index As Integer)
    Static sBuscar As String
    Static lngUltimaPos As Long
    Static UltimoControl As Integer
    Dim lngPosActual As Long
    Dim sTmp As String
        
    'Si no estamos en un Text de búsqueda, salir
    If ControlActual = 0 Then Exit Sub
    
    
    'para procesar las otras acciones adicionales   (15/Abr/97)
    Select Case Index
    Case CMD_BuscarActual
        'Si hay texto seleccionado...
        With Text1(ControlActual)
            If .SelLength > 0 Then
                sBuscar = Trim$(.SelText)
            End If
        End With
        'Para "personalizar" la sección de búsqueda...
        gsDBR.Combo1(0).Tag = "Buscar_" & sUsuario
        If gsBuscar(sBuscar, , "Buscar en el campo actual") > cFFAc_IDLE Then
            sBuscar = Trim$(sBuscar)
            If Len(sBuscar) Then
                LblStatus = "Buscando en el campo actual " & sBuscar & "..."
                DoEvents
                lngUltimaPos = 0&
                UltimoControl = ControlActual
                lngPosActual = InStr(Text1(ControlActual), sBuscar)
                If lngPosActual Then
                    lngUltimaPos = lngPosActual + 1
                    'posicionarse en esa palabra:
                    With Text1(ControlActual)
                        .SelStart = lngPosActual - 1
                        .SelLength = Len(sBuscar)
                    End With
                Else
                    Beep
                    MsgBox "No se ha hallado el texto buscado en el campo actual: " & Text1(ControlActual).DataField, vbOK + vbInformation, "Buscar en el campo actual"
                End If
                'posicionarse en ese control
                Text1(ControlActual).SetFocus
            End If
        End If
    Case CMD_BuscarSigActual
        'Si no hay nada hallado con anterioridad
        'o no se ha procesado la última búsqueda en este control
        If UltimoControl <> ControlActual Or Len(sBuscar) = 0 Or lngUltimaPos = 0& Then
            Accion CMD_BuscarActual
        Else
            LblStatus = "Buscando " & sBuscar & "..."
            DoEvents
            lngPosActual = InStr(lngUltimaPos, Text1(ControlActual), sBuscar)
            If lngPosActual Then
                lngUltimaPos = lngPosActual + Len(sBuscar)
                'posicionarse en esa palabra:
                With Text1(ControlActual)
                    .SelStart = lngPosActual - 1
                    .SelLength = Len(sBuscar)
                End With
            Else
                lngUltimaPos = 1&
                Beep
                MsgBox "No se ha hallado el texto buscado en el campo actual: " & Text1(ControlActual).DataField, vbOK + vbInformation, "Buscar en el campo actual"
            End If
            'posicionarse en ese control
            Text1(ControlActual).SetFocus
        End If
    Case CMD_ReemplazarActual
        'Si hay texto seleccionado...
        With Text1(ControlActual)
            If .SelLength > 0 Then
                sBuscar = Trim$(.SelText)
            End If
        End With
        
        sFFBuscar = sBuscar
        sFFPoner = ""
        'Personalizar las secciones de buscar/reemplazar
        gsDBR.Combo1(0).Tag = "Buscar_" & sUsuario
        gsDBR.Combo1(1).Tag = "Reemplazar_" & sUsuario
        iFFAccion = gsReemplazar(sFFBuscar, sFFPoner, , "Reemplazar en el campo actual")
        If iFFAccion <> cFFAc_Cancelar Then
            MousePointer = vbHourglass
            DoEvents
            sBuscar = Trim$(sFFBuscar)
            If Len(sFFBuscar) <> 0 And Len(sFFPoner) <> 0 Then
                If iFFAccion = cFFAc_Reemplazar Or iFFAccion = cFFAc_ReemplazarTodo Then
                    LblStatus = "Reemplazando " & sBuscar & "..."
                    DoEvents
                    lngUltimaPos = 0&
                    UltimoControl = ControlActual
                    lngPosActual = InStr(Text1(ControlActual), sBuscar)
                    If lngPosActual Then
                        lngUltimaPos = lngPosActual + Len(sBuscar)
                        sTmp = Text1(ControlActual).Text
                        sTmp = Left$(sTmp, lngPosActual - 1) & sFFPoner & Mid$(sTmp, lngPosActual + Len(sFFBuscar))
                        Text1(ControlActual).Text = sTmp
                        'Si sólo es reemplazar uno...
                        If iFFAccion = cFFAc_Reemplazar Then Exit Sub
                        'Cambiar todas las coincidencias en el mísmo text
                        lngUltimaPos = 1
                        Do
                            lngPosActual = InStr(lngUltimaPos, sTmp, sFFBuscar)
                            If lngPosActual Then
                                lngUltimaPos = lngPosActual + 1
                                sTmp = Left$(sTmp, lngPosActual - 1) & sFFPoner & Mid$(sTmp, lngPosActual + Len(sFFBuscar))
                                Text1(ControlActual).Text = sTmp
                            End If
                        Loop While lngPosActual
                        DoEvents
                    Else
                        Beep
                        MsgBox "No se ha hallado el texto buscado en el campo actual: " & Text1(ControlActual).DataField, vbOK + vbInformation, "Buscar en el campo actual"
                    End If
                    'Si se ha reemplazado to, no debe estar esta palabra...
                    lngUltimaPos = 0&
                End If
            End If
            MousePointer = vbDefault
            DoEvents
        End If
    Case CMD_SeleccionarTodo
        With Text1(ControlActual)
            .SelStart = 0
            .SelLength = Len(.Text)
        End With
    End Select
End Sub


4.- Asignar el ShortCut Alt+F4 a la opción de Salir.

Esto aunque parezca una tontería, no lo es tanto, ya que no podemos asignar este "shortcut" en el editor de menús.
Para ello, añade la siguiente línea en el Form_Load:

'Añadir el short-cut de Alt+F4 a la opción Salir:
mnuSalir.Caption = "&Salir" & Chr$(9) & "Alt+F4"

5.- Asignar el usuario y la base a usar, al cargar el programa.

Esto puede servir para, una vez que sabemos la base que vamos a usar y nuestro nombre (!!!), no necesitamos que se nos pida esa información. Esto no es conveniente si después añadimos algún tipo de "seguridad" por medio de claves, etc.
Pero nos va a servir para saber como "captar" la línea de comandos.

Esto tendremos que hacerlo en el form de carga, una vez que sepamos que hay alguna base incluida...

    'Si hay datos en el Combo, seleccionar el primero
    If Combo1.ListCount Then
        Combo1.ListIndex = 0
        'Aquí pondremos las opciones de entrada "personalizada"
        'es decir sólo si hay bases asignadas.
        If Len(Trim$(Command$)) Then
            ProcesarLineaComandos
        End If
    End If

Ahora, vamos a ver lo que se hace dentro del procedimiento ProcesarLineaComandos:

Private Sub ProcesarLineaComandos()
    'La forma de los parámetros será:
    '/U nombre_usuario /B nombre_base
    Dim sTmp As String
    Dim sUser As String
    Dim sBase As String
    Dim i As Long
    
    Show
    DoEvents
    
    sTmp = Trim$(Command$)
    
    'tomar el nombre del usuario
    i = InStr(sTmp, "/U")
    If i Then
        sUser = Mid$(sTmp, i + 2)
        i = InStr(sUser, "/")
        If i Then
            sUser = Left$(sUser, i - 1)
        End If
        sUser = Trim$(sUser)
        Text1 = sUser
    End If
    'Ahora la base:
    i = InStr(sTmp, "/B")
    If i Then
        sBase = Mid$(sTmp, i + 2)
        i = InStr(sBase, "/")
        If i Then
            sBase = Left$(sBase, i - 1)
        End If
        sBase = Trim$(sBase)
        'Comprobar si la base existe en el combo
        '   Si no existe, añadirla al combo
        i = ActualizarLista(sBase, Combo1)
        If i = -1 Then
            'Este caso seguramente nunca se dará, pero...
            MsgBox "Se ha producido un error inesperado al añadir al combo", vbCritical, _
							"Cargando automáticamente"
            Unload Me
            End
        End If
        Combo1.Text = sBase
    End If
    'Hacer como si se hubiese pulsado en aceptar
    cmdAceptar_Click
End Sub

Y esto es todo por ahora. Ya veremos cuando "retomo" el tema de este proyecto. Por ahora lo voy a dejar "reposar", para dedicarme a otras cosillas.

Nos vemos. (porque aún no te irás de estas páginas, ¿verdad?)


ir al índice