Un Gran Proyecto, Paso a Paso

 

Quinta Entrega (7/Abr/97)

Entregas anteriores: Primera, Segunda, Tercera, Cuarta
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 5 primeras entregas. (gsnotas_htm.zip 55.2 KB)
Bajate los listados del proyecto. (gsnotas.zip 20.9 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)


Hoy no voy a abordar todavía el tema de las consultas, lo siento. Ese será el tema de la próxima entrega.
No es que quiera ponerme "intrigante", pero es que en ese apartado se van a tener que hecer una serie de "replanteamientos" del programa y habrá que añadir algunas estructuras de datos y eso voy a dejarlo después de los pequeños cambios que hoy tengo pensado. A estas alturas sabrás que no tengo este "cursillo" planeado ni planificado, va saliendo poco a poco y como no soy una persona de ideas fijas, pues me permito "cambiar" de parecer y espero que no sea a costa de tu aburrimiento.

Ah!, por cierto, en cuanto a la gente que "apuesta" por que sea un proyecto de 16 bits, decirte que aún no hay NI UNA.
Espero que esto siga así, la verdad es que no me hace mucha ilusión tener que hacer el planteamiento para los 16 bits, no sería bueno, creo. Sé que aún hay gente que "tiene" que trabajar con Win 3.x, pero ya va siendo hora de que cambien...
De todas formas, si hubiese alguna persona interesada en convertirlo a VB3, me podría tomar la molestia de hacer una versión "paralela", aunque eso sí, con menos "actualizaciones". (el plazo termina el próximo Miércoles dia 9 de Abril)

Una vez hecha estas aclaraciones, vamos al tema que nos interesa. Hoy tengo pensado estas cosas:
(
Antes de pasar a estos puntos, debemos hacer unos cambios al form gsNotas)

Los cambios a realizar en el form gsNotas, son los siguientes:
---Al Picture1, cambiale el Nombre para que se llame ToolBar
---Añadir un pictureBox y asignarle las siguientes propiedades:
Name = StatusBar (hay que ir preparándose para los 32 bits!)
Height = 270
Align = 2-Align Botton
BorderStile = 0-None
---Añadir un Label dentro del Picture que acabas de insertar y asigna las siguientes propiedades:
Name = LblStatus
BorderStile = 0-None

Ahora añade el siguiente código:
En las declaraciones, después del Option Explicit, deberás poner esto otro, para que la rutina de búsqueda "no falle"

Option Compare Text

De esta forma, al comparar cadenas (incluso con el Instr), no se tendrán en cuenta las mayúsculas/minúsculas.
Estas declaraciones, también en la parte general del form, para "recordar" el tamaño inicial de la ventana, después se usará cuando cambiemos el tamaño.

Dim iH As Integer                   'Tamaño mínimo de la ventana
Dim iW As Integer

Ahora si, estas son las cosillas para hoy:

  1. Añadir unos menús
  2. Ajustar los controles al redimensionar la ventana
  3. Nuevo Form para Buscar/Reemplazar
  4. Una rutina para Reemplazar datos
  5. Informar en que posición dentro del TextBox estamos

1.- Añadir unos menús.

Los vamos a necesitar, ya que se añadirán más opciones de las que en principio van a "coger" en la barra de herramientas.
Para ello, muestra el form gsNotas. Pulsa en Tools/Menu Editor...
Añade los siguientes "menús"

&Archivo		mnuArc
--&Nuevo		mnuNuevo
--G&uardar		mnuGuardar
--&Borrar		mnuBorrar
-- -			mnuArcSep1
--&Salir		mnuSalir
&Edición		mnuEd
--C&ortar		mnuCortar	Shortcut        =   ^X
--&Copiar		mnuCopiar	Shortcut        =   ^C
--&Pegar		mnuPegar	Shortcut        =   ^V
-- -			mnuEdSep1
--S&eleccionar Todo	mnuSelecTodo	Shortcut        =   ^E
-- -			mnuEdSep2
--&Buscar...		mnuBuscar	Shortcut        =   ^B
--Buscar Si&guiente	mnuBuscSig	Shortcut        =   {F3}
--&Reemplazar		mnuReemplazar	Shortcut        =   ^R

Ya tenemos unos cuantos menús creados, ahora vamos a asignarles los comandos a realizar. El tema de la Edición (cortar, copiar, etc., lo dejaremos para otra ocasión)

Añade la siguiente declaración en la parte general del Form:

Const CMD_Reemplazar = 5

Y este es el código para los menús que ahora estan operativos: (fijate que la opción de Reemplazar, se efectuará en el interior del procedimiento del cmdAccion, aunque ese botón no exista, el VB sólo procesa la orden que le digamos y no comprueba si el valor Index recibido en el procedimiento está dentro del rango de botones creados)

Private Sub mnuBorrar_Click()
    cmdAccion_Click CMD_BORRAR
End Sub

Private Sub mnuBuscar_Click()
    cmdAccion_Click CMD_BUSCAR
End Sub

Private Sub mnuBuscSig_Click()
    cmdAccion_Click CMD_BuscarSiguiente
End Sub

Private Sub mnuGuardar_Click()
    cmdAccion_Click CMD_ACTUALIZAR
End Sub

Private Sub mnuNuevo_Click()
    cmdAccion_Click CMD_NUEVO
End Sub

Private Sub mnuReemplazar_Click()
    cmdAccion_Click CMD_Reemplazar
End Sub

Private Sub mnuSalir_Click()
    cmdSalir_Click
End Sub

2.- Ajustar los controles al redimensionar la ventana

Para ajustar el tamaño de los controles, usaremos el procedimiento Form_Resize. En esta rutina, se comprueba de que no se haga ningún cambio, si está minimizada y que no se pueda hacer el form más pequeño del tamaño inicial.
Veamos el código: (el Form_Load también se ha modificado)

Private Sub Form_Load()
    'El tamaño por defecto
    iH = Height
    iW = Width
    
    'El archivo de configuración
    sFFIni = ficIni

    Show
    DoEvents
    'Cargar la tabla
    CargarTabla
End Sub


Private Sub Form_Resize()
    Dim i As Integer
    
    'No hacer nada si se minimiza
    If WindowState = vbMinimized Then Exit Sub
    
    'No permitir un tamaño menor que el inicial
    If Width < iW Then
        Width = iW
        Exit Sub
    End If
    If Height < iH Then
        Height = iH
        Exit Sub
    End If
    
    Data1.Width = ScaleWidth - 180
    Text2.Left = ScaleWidth - Text2.Width - 90
    Label1(0).Left = Text2.Left - 450
    'Los textBox de Asunto y descripción
    For i = 2 To 3
        With Text1(i)
            .Width = ScaleWidth - .Left - 90
        End With
    Next
    'Los texts y labels del final
    For i = 4 To 5
        With Text1(i)
            .Top = ScaleHeight - 750
            Label1(i).Top = .Top + 30
        End With
    Next
    Check1.Top = Label1(4).Top
    'El alto del text de la descripción
    With Text1(3)
        .Height = Text1(4).Top - .Top - 75
    End With
    
    'Move es más rápido que efectuar los 3 cambios
    LblStatus.Move 60, 30, ScaleWidth - 120
End Sub

3.- Nuevo Form para Buscar/Reemplazar

Para la tarea de Reemplazar, vamos a necesitar otro form, el cual nos va a servir tanto para pedir los datos a Reemplazar como para la rutina de Buscar, con lo cual no necesitaremos, al menos por ahora, el módulo y el form gsInput, así que puedes "quitarlos" del proyecto y añadir los siguientes:
gsDBR.Frm y gsDBR.Bas

Form de Buscar y Reemplazar
Esta es una "foto" del form gsDBR

El código de estos dos nuevos módulos es el siguiente:

'----------------------------------------------------
'Form genérico para diálogo Buscar/Reemplazar
'
'©Guillermo Som Cerezo, 1996-97
'----------------------------------------------------
Option Explicit

Const NumeroMaximoDeItems = 100
Dim bBuscandoEnCombo As Boolean


Private Sub cmdCancel_Click()
    ActualizarCombo
    
    iFFAccion = cFFAc_Cancelar
    Unload Me
End Sub


Private Sub cmdFindNext_Click()
    ActualizarCombo
    sFFBuscar = txtFind.Text
    sFFPoner = ""
    
    iFFAccion = cFFAc_BuscarSiguiente
    Unload Me
End Sub


Private Sub cmdReplace_Click()
    ActualizarCombo
    sFFBuscar = txtFind.Text
    sFFPoner = txtReplace.Text
    If Len(sFFPoner) = 0 Then
        iFFAccion = cFFAc_Buscar
    Else
        iFFAccion = cFFAc_Reemplazar
    End If
    Unload Me
End Sub


Private Sub cmdReplaceAll_Click()
    ActualizarCombo
    sFFBuscar = txtFind.Text
    sFFPoner = txtReplace.Text
    If Len(sFFPoner) = 0 Then
        iFFAccion = cFFAc_Buscar
    Else
        iFFAccion = cFFAc_ReemplazarTodo
    End If
    Unload Me
End Sub


Private Sub Combo1_Change(Index As Integer)
    
    If bBuscandoEnCombo Then Exit Sub
    
    If Index = 0 Then
        txtFind = Combo1(0).Text
    Else
        txtReplace = Combo1(1).Text
    End If
End Sub


Private Sub Combo1_Click(Index As Integer)
    
    If bBuscandoEnCombo Then Exit Sub
    
    If Combo1(Index).ListIndex Then
        Combo1(Index).Text = Combo1(Index).List(Combo1(Index).ListIndex)
    End If
    If Index = 0 Then
        txtFind = Combo1(Index).Text
    Else
        txtReplace = Combo1(Index).Text
    End If
End Sub


Private Sub Form_Load()
    Dim j As Integer
    Dim i As Integer
    Dim n As Integer
    Dim vTmp As String
    Dim sTmp As String
    Dim sTag As String
    
    If sFFIni = "" Then
        sFFIni = "BuscReemp.ini"
    End If
    'Posicionar en el centro de la ventana principal
    Move (Screen.Width - Width) \ 2, (Screen.Height - Height) \ 2
    '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


Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    'Si se cierra por el controlbox, o cualquier forma distinta del propio código,
    'asumir que se ha cancelado.
    If UnloadMode <> vbFormCode Then
        iFFAccion = cFFAc_Cancelar
    End If
End Sub


Private Sub Form_Unload(Cancel As Integer)
    Dim n As Integer
    Dim vTmp As String
    Dim sTmp As String
    Dim i As Integer
    Dim j As Integer
    Dim sTag As String
    
    If iFFAccion <> cFFAc_Cancelar Then
        ActualizarCombo
        For i = 0 To 1
            n = Combo1(i).ListCount
            sTag = Trim$(Combo1(i).Tag)
            If n > NumeroMaximoDeItems Then n = NumeroMaximoDeItems
            GuardarIni sFFIni, sTag, "NumEntradas", CStr(n)
            For j = 0 To n - 1
                vTmp = "Entrada" & CStr(j)
                sTmp = Combo1(i).List(j)
                GuardarIni sFFIni, sTag, vTmp, sTmp
            Next
        Next
    End If
    Set gsDBR = Nothing
End Sub


Private Sub ActualizarCombo()
    '-----------------------------------------------------
    'Esta rutina actualiza el contenido de los dos combos,
    'si la entrada en el Combo.Text no está, la incluye.
    'Se podría usar la llamada al API de Windows.
    '-----------------------------------------------------
    'Actualizar el contenido del Combo
    Dim sTmp As String
    'Para más rapidez...
    Static i As Integer
    Static j As Integer
    Static hallado As Boolean
    Static k As Integer
    '
    bBuscandoEnCombo = True
    For k = 0 To 1
        hallado = False
        sTmp = Combo1(k).Text
        If Len(Trim$(sTmp)) Then
            j = Combo1(k).ListCount - 1
            For i = 0 To j
                If StrComp(Trim$(sTmp), Trim$(Combo1(k).List(i))) = 0 Then
                    hallado = True
                    Exit For
                End If
            Next
            If Not hallado Then
                Combo1(k).AddItem sTmp, 0
            End If
        End If
    Next
    bBuscandoEnCombo = False
End Sub

Este es el listado del módulo con las rutinas de petición de los datos

'---------------------------------------------------------------
'gsDBR.bas Módulo para el diálogo de Buscar y Reemplazar
'
'(c)Guillermo Som, 1996-97
'---------------------------------------------------------------
Option Explicit

'Variables y constantes para buscar/reemplazar
Global sFFBuscar As String
Global sFFPoner As String
Global iFFAccion As Integer

'Constantes para la acción a realizar
Global Const cFFAc_Cancelar = True
Global Const cFFAc_IDLE = 0
Global Const cFFAc_Buscar = 1
Global Const cFFAc_BuscarSiguiente = 2
Global Const cFFAc_Reemplazar = 3
Global Const cFFAc_ReemplazarTodo = 4
Global Const cFFAc_Aceptar = 5
'
Global sFFIni As String                     'Archivo de configuración


Public Function gsReemplazar(sBuscar As String, sPoner As String, Optional vModo, Optional vCaption) As Integer
    'Prepara el diálogo de Reemplazar
    Dim iModo As Integer
    Dim sCaption As String
    
    If IsMissing(vModo) Then
        iModo = cFFAc_Reemplazar
    Else
        iModo = vModo
    End If
    
    If IsMissing(vCaption) Then
        sCaption = "Reemplazar"
    Else
        sCaption = CStr(vCaption)
    End If
    
    iFFAccion = cFFAc_IDLE
    With gsDBR
        .Caption = sCaption
        .cmdFindNext.Default = False
        .cmdFindNext.Visible = False
        .cmdReplaceAll.Default = True
        .Combo1(0).Text = sBuscar
        .Combo1(1).Text = sPoner
        'Mostrar el form y esperar a que se tome una acción
        .Show vbModal
        'Do
        '    .Show
        '    DoEvents
        'Loop Until iFFAccion
    End With
    'Devolver la cadena a reemplazar y buscar
    sBuscar = sFFBuscar
    sPoner = sFFPoner
    'Si tanto buscar como poner están en blanco, devolver cancelar
    If Len(Trim$(sBuscar)) = 0 Then
        If Len(Trim$(sPoner)) = 0 Then
            iFFAccion = cFFAc_Cancelar
        End If
    End If
    'Devolver la acción
    gsReemplazar = iFFAccion
End Function


Public Function gsBuscar(sBuscar As String, Optional vModo, Optional vCaption) As Integer
    'Prepara el diálogo para buscar
    Dim iModo As Integer
    Dim sCaption As String
    
    If IsMissing(vModo) Then
        iModo = cFFAc_Buscar
    Else
        iModo = vModo
    End If
    'Sólo permitir buscar y buscar-siguiente
    Select Case iModo
    Case cFFAc_Buscar, cFFAc_BuscarSiguiente
        'está bien, no hay nada que hacer
    Case Else
        iModo = cFFAc_Buscar
    End Select
    
    If IsMissing(vCaption) Then
        sCaption = "Buscar"
    Else
        sCaption = CStr(vCaption)
    End If
    
    iFFAccion = cFFAc_IDLE
    With gsDBR
        .Caption = sCaption
        .cmdReplace.Visible = False
        .lblReplace.Visible = False
        .cmdReplaceAll.Visible = False
        .Combo1(1).Visible = False
        .cmdFindNext.Left = .cmdReplaceAll.Left
        If iModo = cFFAc_BuscarSiguiente Then
            .cmdFindNext.Caption = "Siguiente"
            DoEvents
        End If
        .Combo1(0).Text = sBuscar
        'Mostrar el form y esperar a que se tome una acción
        .Show vbModal
        'Do
        '    .Show
        '    DoEvents
        'Loop Until iFFAccion
    End With
    'Devolver la cadena seleccionada/introducida
    sBuscar = sFFBuscar
    'Devolver la acción
    gsBuscar = iFFAccion
End Function


Public Sub gsPedirUnValor(spuvTitulo As String, spuvMensaje As String, spuvPregunta As String, spuvValor As String, spuvBoton As String)
    
    'Rutina de propósito general para pedir un valor (00.22 23/May/96)
    With gsDBR
        .Caption = spuvTitulo
        .Combo1(0).Visible = False
        .lblBuscar.Width = .ScaleWidth - 120
        .lblBuscar = spuvMensaje
        .Combo1(0).Visible = False
        .cmdReplace.Visible = False
        .cmdFindNext.Default = False
        .cmdFindNext.Visible = False
        .lblReplace = spuvPregunta
        .cmdReplaceAll.Default = True
        .cmdReplaceAll.Caption = spuvBoton
        If Len(Trim$(spuvValor)) Then
            .Combo1(1).Text = spuvValor
        Else
            If .Combo1(1).ListCount Then
                .Combo1(1).ListIndex = 0
            End If
        End If
        .Show vbModal
    End With
    spuvValor = sFFPoner
End Sub

Ahora la rutina de búsqueda quedaría así, (he puesto también que si hay texto seleccionado, se ponga ese para buscar)

    Case CMD_BUSCAR             'Buscar registros
        'Si no estamos en un Text de búsqueda, salir
        If ControlActual = 0 Then Exit Sub
        
        'Si hay texto seleccionado...
        With Text1(ControlActual)
            If .SelLength > 0 Then
                sBuscar = "*" & Trim$(.SelText)
            End If
        End With
        
        If gsBuscar(sBuscar, , "Buscar datos") > cFFAc_IDLE Then
            sBuscar = Trim$(sBuscar)
            If Len(sBuscar) Then
                YaEstoyAqui = True
                Data1.Recordset.FindFirst Text1(ControlActual).DataField & " LIKE '" & sBuscar & "*'"
                If Data1.Recordset.NoMatch Then
                    Beep
                    MsgBox "No se ha hallado el dato buscado en el campo: " & Text1(ControlActual).DataField, vbOK + vbInformation, "Buscar"
                    Text1(ControlActual).SetFocus
                Else
                    sTmp = sBuscar
                    If Left(sTmp, 1) = "*" Then
                        sTmp = Mid$(sTmp, 2)
                    End If
                    'Seleccionar el texto hallado
                    With Text1(ControlActual)
                        i = InStr(.Text, sTmp)
                        .SelStart = i - 1
                        .SelLength = Len(sTmp)
                        'posicionarse en ese control
                        .SetFocus
                    End With
                End If
                YaEstoyAqui = False
            End If
        End If

Ahora el tema de Reemplazar, (ya era hora tío!)


4.- Una rutina para Reemplazar datos

Deberás tener estas declaraciones de variables al principio del procedimiento (no es necesario que estén al principio, pero queda como más "mono"):

    Dim BusquedaNoHallada As Boolean
    Dim j As Integer

Este es el código de la rutina de "Reemplazo"

    Case CMD_Reemplazar
        'Si no estamos en un Text de búsqueda, salir
        If ControlActual = 0 Then Exit Sub

        'Si hay texto seleccionado...
        With Text1(ControlActual)
            If .SelLength > 0 Then
                sBuscar = "*" & Trim$(.SelText)
            End If
        End With
        
        sFFBuscar = sBuscar
        sFFPoner = ""
        iFFAccion = gsReemplazar(sFFBuscar, sFFPoner)
        If iFFAccion <> cFFAc_Cancelar Then
            MousePointer = vbHourglass
            DoEvents
            sBuscar = Trim$(sFFBuscar)
            'Quitar de los caracteres de asteríscos
            Do While InStr(sFFBuscar, "*")
                i = InStr(sFFBuscar, "*")
                sFFBuscar = Left$(sFFBuscar, i - 1) & Mid$(sFFBuscar, i + 1)
            Loop
            If Len(sFFBuscar) <> 0 And Len(sFFPoner) <> 0 Then
                If iFFAccion = cFFAc_Reemplazar Or iFFAccion = cFFAc_ReemplazarTodo Then
                    LblStatus = "Buscando " & sBuscar & "..."
                    DoEvents
                    YaEstoyAqui = True
                    Data1.Recordset.FindFirst Text1(ControlActual).DataField & " LIKE '" & sBuscar & "*'"
                    If Data1.Recordset.NoMatch Then
                        Beep
                        MsgBox "No se ha hallado el dato buscado en el campo: " & Text1(ControlActual).DataField, vbOK + vbInformation, "Reemplazar"
                        Text1(ControlActual).SetFocus
                        BusquedaNoHallada = True
                    End If
                    YaEstoyAqui = False
                    Do Until BusquedaNoHallada
                        sTmp = Text1(ControlActual).Text
                        'cambiar... (comprobar si es palabra completa)
                        If Left$(sBuscar, 1) = "*" Then
                            i = InStr(sTmp, sFFBuscar)
                        Else
                            If Left$(sTmp, Len(sFFBuscar)) = sFFBuscar Then
                                i = 1
                            Else
                                i = 0
                            End If
                        End If
                        If i Then
                            sTmp = Left$(sTmp, i - 1) & sFFPoner & Mid$(sTmp, i + Len(sFFBuscar))
                            Text1(ControlActual).Text = sTmp
                        End If
                        If iFFAccion = cFFAc_Reemplazar Then Exit Do
                        'Cambiar todas las coincidencias en el mísmo text
                        j = 1
                        Do
                            If Left$(sBuscar, 1) = "*" Then
                                i = InStr(j, sTmp, sFFBuscar)
                            Else
                                If Left$(sTmp, Len(sFFBuscar)) = sFFBuscar Then
                                    i = 1
                                Else
                                    i = 0
                                End If
                            End If
                            If i Then
                                j = i + 1
                                sTmp = Left$(sTmp, i - 1) & sFFPoner & Mid$(sTmp, i + Len(sFFBuscar))
                                Text1(ControlActual).Text = sTmp
                            End If
                        Loop While i
                        DoEvents
                        YaEstoyAqui = True
                        Data1.Recordset.FindNext Text1(ControlActual).DataField & " LIKE '" & sBuscar & "*'"
                        If Data1.Recordset.NoMatch Then
                            BusquedaNoHallada = True
                        Else
                            BusquedaNoHallada = False
                        End If
                        YaEstoyAqui = False
                    Loop
                End If
            End If
            MousePointer = vbDefault
            DoEvents
        End If

5.- Informar en que posición del TextBox estamos.

Esto es para "rematar" el tema por hoy. Ya que tenemos el LblStatus, vamos a darle una utilidad.
A mi particularmente me gusta saber en que posición se encuentra el cursor cuando estoy editando un campo, sobre todo cuantos caracteres me quedan aún. ¿? Suponte que estás en el campo de Asunto y quieres saber cuantos caracteres puedes utilizar, ya sabes que son 255 el máximo, así que lo que viene a continuación, es para indicarnos eso precisamente, la posición dentro del Text y cuantos caracteres son en total.
Mejor ver el código, que ya no controlo demasiado...

Private Sub Text1_Click(Index As Integer)
    LblStatus = "Pos: " & Text1(Index).SelStart + 1 & "/" & Text1(Index).MaxLength
End Sub


Private Sub Text1_KeyUp(Index As Integer, KeyCode As Integer, Shift As Integer)
    LblStatus = "Pos: " & Text1(Index).SelStart + 1 & "/" & Text1(Index).MaxLength
End Sub

Ahora si que hemos terminado. Espero que saques algunas cosillas de provecho de todo lo de hoy.
Hasta la próxima entrega, esta vez no digo para cuando que después me dices que no cumplo mi palabra...


ir al índice