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. (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?)