Introducción:
Esta utilidad (al menos a la hora de escribir estas líneas el 9
de Diciembre de 2007) tiene la misma funcionalidad que la utilidad
Buscar ficheros (gsBuscar) que
publiqué el 29 de Agosto y revisé el 1 de Septiembre, es decir, puedes usarla para indicar una
"especificación" de ficheros (o archivos) que quieres buscar en un directorio determinado. Esa
especificación puede tener caracteres comodines, por ejemplo *.exe
buscaría todos los ejecutables.
Lo que tiene de nuevo esta primera revisión de la nueva versión (2.0), además de otras cosas
que ahora te comentaré, es que en la especificación esa de lo que quieres buscar puedes indicar
varias cosas, siempre que las separes por puntos y comas, por ejemplo, si indicas *.zip;
*.rar te buscará todos los ficheros (o archivos) comprimidos que tengan esas extensiones.
Y esa búsqueda la hará en lo directorios que indiques (esto
también lo hacía antes) e incluso si quieres que lo haga en los subdirectorios que tenga
ese directorio.
La novedad en este caso de los nombres de los directorios, es que también puedes indicar varios
directorios separándolos por puntos y comas (para que veas que
los puntos y comas no solo se usan en ciertos lenguajes, je, je). De esa forma, puedes
indicar que se busque en los directorios E:\Datos; E:\Mis cosas y lo que hará será buscar
esa especificación (o especificaciones) en cada uno de esos directorios.
Como ves, solo por estas cosas, esta nueva versión ya te será de más utilidad que la
anterior.
Nuevas cosas que hará la utilidad
Pero la nueva versión aún "quiere" hacer más (quiere, pero
todavía no lo hace, al menos a día de hoy 9 de Dic del 07), como puede ser que solo
busque los ficheros modificados en una fecha indicada o que permita buscar cierto texto en esos
ficheros hallados o que pueda incluso cambiar los textos hallados... ya que esas nuevas
funcionalidades vendrán en futuras versiones, pero no en esta actual.
Cosas interesantes desde el punto de vista del programador curioso
Pero el que haya publicado ya esta nueva versión de la utilidad, y no haya esperado a que
esté más "currada", no es porque haya agregado esas dos cosillas que te comenté antes, que para
ser sinceros, son mejoras menores, si no porque en esta nueva versión está hecha con la versión
final de Visual Basic 2008 (el proyecto también lo podrás abrir
con la versión Express de Visual Basic 2008), y no solo porque esté hecha con la versión
2008 del lenguaje, y por tanto con el .NET Framework 3.5, sino que la "gracia" está en que estoy
usando un control de usuario WPF (Windows Presentation Foundation o WinFX) dentro de un
formulario normal (de Windows.Forms).
Es decir, he unido funcionalidad de dos mundos totalmente distintos, al menos en la forma de
presentar los controles. Ya que los controles de Windows.Forms no se manejan de la misma forma
que los controles de WPF, (y si no te fías de lo que te digo,
puedes echar un vistazo a los ejemplos que tengo publicados en
la sección de WinFX o .NET Framework 3.0), ya que
los controles de WPF se escriben usando XAML y los de Windows.Forms, pues de la forma habitual (habitual
al menos hasta hace unos meses, ya que desde que apareció el .NET Framework 3.0, las cosas se
pueden hacer de dos formas distintas).
Antes de seguir contándote las cosas nuevas, mejor que veas un aspecto del formulario en modo
de diseño.
Figura 1. El formulario en modo de diseño
Como puedes observar en la figura 1 no hay nada que te indique que esto no es un formulario
normal, salvo si te fijas en los dos "expanders" (esos dos circulitos que hay encima de Buscar y
Filtro búsqueda).
En realidad los controles que están en la parte de arriba (que están dentro del recuadro
punteado) corresponden a un control de usuario creado con WPF (se crea seleccionando un nuevo
elemento del tipo User Control (WPF)) y como puedes suponer, la forma de definir ese
control de usuario es por medio de código XAML.
Aquí abajo tienes
el código XAML del control de usuario WPF llamado
OpcionesBuscar.
He puesto comentarios en el código para que te sea más fácil comprender el porqué de ese código.
También he intentado seguir un orden a la hora de definir las propiedades de cada control, y al
menos están en este orden: nombre del control, fila/columna en el Grid
y después el resto de las propiedades.
Si sabes un poco de cómo se definen los controles WPF por medio de XAML te
puedes saltar esta sección y
pasar a la siguiente.
Como te he comentado antes, el control de usuario WPF tiene las partes de la aplicación que
define si se va a buscar texto o si se va a reemplazar ese texto por otro diferente, también
define otra "zona" de opciones para indicar en qué directorio se buscará y qué "filtro" debe
usarse en los ficheros que se van a buscar.
(Ya sabes que esto de buscar y reemplazar texto aún no está implementado, pero lo estará en una
futura revisión.)
El haberlo hecho con WPF es porque quería usar un control Expander, ya sabes que ese
tipo de control lo que te permite es mostrar u ocultar diferentes opciones, así que... como en
Windows Vista eso es un "estándar" de diseño, pues me dije (porque
yo algunas veces hablo conmigo mismo y me digo cosas, je, je; valeeee, en realidad no me lo
digo, sino que lo pienso y es como si me lo dijera yo a mi mismo): Voy a hacerlo con
varios Expander que así es más "cool" (o molón).
La creación de controles WPF la puedes hacer directamente en una aplicación "normal", es
decir, no tienes porqué crear el control de forma separada, compilarlo y después usarlo. También
lo puedes hacer así, pero en este caso, simplemente lo que he hecho es agregar un elemento nuevo
de ese tipo y después he definido el contenido y la funcionalidad que yo quiero en este caso
particular.
La única pega que tengo con este control WPF en particular es que define ciertos eventos para
habilitar ciertos controles, por ejemplo, cuando no está seleccionada la opción de Buscar (chkBuscar),
tengo que deshabilitar todos los controles de esa "zona" del control, además de cambiar el texto
mostrado en la "cabecera" (propiedad Header) del
Expander
correspondiente; y al tener esos eventos funcionando, pues el diseñador de Visual Studio 2008 se
hace un lío y me da error...
Para evitar esos errores del diseñador, lo que hago en ese control, es definir los manejadores
de evento en una propiedad que asigno cuando la aplicación está en ejecución. Y cuando la
aplicación se acaba, quito esos manejadores de eventos, (esto último no es necesario, pero... lo
he hecho).
En particular los eventos que asigno manualmente son los que controlan el estado de los
controles CheckBox.
El código que se encarga de hacer esto que te comento está en
la propiedad llamada
EnEjecucion (puedes ver el código ahí abajo, en
la parte del código del
control de usuario
OpcionesBuscar).
Como puedes ver en el código del control, (independiente de que se asignen manualmente los
"manejadores de eventos"), también uso la variable privada de esa propiedad (m_EnEjecucion)
para saber si deben hacerse comprobaciones sobre los controles o no, esto es algo habitual que
siempre habría que hacer, ya que muchas veces, esos eventos se producen mientras se están
creando los controles (y asignando los valores a las propiedades que producen el evento ese en
concreto), por tanto, puede ocurrir que el resto de controles que se están usando aún no estén
creados.
Si quieres probarlo (o comprobarlo), puedes quitar (o comentar) la primera línea de código de
los métodos expanderBuscar_Collapsed y
expanderBuscar_Expanded
y verás que bonito error te dará al ejecutar la aplicación. La razón de ese error es porque se
intenta acceder al control gridTexto y ese control aún está en el limbo, es decir, aún no existe
(sí, tienes razón, algunos siempre están -o estamos- en el limbo,
y aún así, existimos, je, je, je).
Cosas a destacar del código del control OpcionesBuscar
Del código del control WPF, quisiera que te fijaras, además de lo que te comentaba sobre que
no entre la ejecución en los métodos de los eventos cuando no deben entrar, la forma de asignar
nuevos directorios al usar el método del botón btnExaminarDir. Como te he comentado, los
directorios a examinar pueden ser varios, estando separados por puntos y comas, por tanto, se
tiene en cuenta ese "detalle" de forma que al añadir un nuevo valor, si ya había algo en el
texto del combo correspondiendo (cboDir), se añade al final, añadiendo un punto y coma.
De esa forma es más fácil agregar nuevos directorios.
Por supuesto, cuando se pulsa en ese botón se tiene en cuenta si hay varios valores separados
por puntos y comas, de ser así, se usa el primero de esos valores. De eso se encarga la parte de
la comprobación
If Me.cboDir.Text.Contains(";")
Then.
Debido a que desde el formulario (una opción del menú Fichero) necesito llamar al
método del evento Click del botón Examinar, he creado un método público para hacer eso (btnExaminarClick),
aunque también podría haber cambiado la visibilidad del método del evento
Click, pero... solo es cuestión de gustos...
En cuanto al "porqué" de la comprobación que hay en el evento del
chkBuscar:
If chkPoner.Tag
IsNot
Nothing AndAlso
CBool(chkPoner.Tag) = False
Then
Esto es porque en el formulario asigno un valor False a la propiedad Tag
del control
chkPoner, ya que aún no está implementada la funcionalidad y por tanto, no tiene ningún
motivo que se puedan usar los controles que están relacionados con esa opción.
Aunque la verdad es que eso no hace falta, ya que desde el propio formulario principal he
deshabilitado el chkBuscar, ya que tampoco está implementada la funcionalidad de buscar
texto en los ficheros... pero más que nada me ha servido para saber si todo va funcionando como
espero que funcione.
Por último, resaltar que en los cuatro eventos de los Expander
(dos para cada control, según se oculten o muestren), lo que hago es mostrar u ocultar el
Grid
asociado con cada control Expander, con idea de que el StackPanel
actúe visualmente sobre esos controles, así se moverá hacia arriba el
Expander de los ficheros cuando el Expander
de la búsqueda se contraiga.
Además produzco (o lanzo) el evento ExpanderExpandedChanged con idea de que el formulario
se entere de que hay que tener en cuenta el nuevo tamaño o espacio ocupado por la parte visual
del control y así poder posicionar adecuadamente el ListView que hay debajo del control
de usuario.
En la siguiente figura puedes ver la aplicación en ejecución con el primer Expander
contraído (ocultando las opciones de búsqueda de texto) y como ves, el
ListView se adapta al nuevo tamaño.
Figura 2. La utilidad en ejecución con un Expander contraído
Nota (ejercicio a hacer antes de ver el código del proyecto completo):
En el código mostrado si se suelta un fichero o directorio en el formulario (o el control de
usuario) se asigna ese valor al texto del combo de los directorios.
Como ejercicio te propongo que el fichero soltado se agregue a lo que ya haya (como al usar el
botón de examinar).
Te recuerdo (por si no quieres ver la solución) que en el código del proyecto ya está ese
código, así que... si quieres hacerlo por tu cuenta, no mires el código del proyecto completo.
En el código del proyecto en realidad lo que se hace es añadir TODOS los ficheros/directorios
que se arrastren y suelten, pero también tienes el código de cómo añadir solo el primero de esos
ficheros.
Explicación del código del formulario
Del código del formulario te voy a explicar bien poco, ya que he puesto los suficientes
comentarios como para que sepas que se hace en cada caso.
Aquí abajo tienes
el código del formulario.
Recuerda el ejercicio comentado antes, ya que en el evento DraqDrop también habría que tener
en cuenta lo que quieres agregar al texto del combo.
Resaltarte un par de cosas que te pueden resultar interesantes (al menos desde mi punto de
vista, claro):
Cuando arrastras y sueltas cosas en el combo del directorio (en realidad en el combo del
directorio no te deja soltar, pero para el caso es lo mismo), es posible que lo que sueltes sea
un fichero en lugar de un directorio, e incluso es posible que escribas el nombre de un fichero,
y en esos casos (al menos en los que he probado), lo que hago es detectar que el directorio no
existe y buscar el nombre del directorio que debería usarse, e intentar usar ese nombre de
directorio (esas comprobaciones las hago en el código del evento Click del botón Buscar).
If di.Exists = False _
AndAlso di.Attributes = FileAttributes.Archive Then
di = New DirectoryInfo(Path.GetDirectoryName(di.FullName))
End If
If di.Exists Then
recorrerDir(di)
End If
Y si el caso de que ese directorio no exista es porque has agregado ficheros, es muy posible
que agregues varios ficheros de un mismo directorio o que añadas el mismo directorio más de una
vez.
En esos casos (en los que el mismo directorio está repetido) lo que pasaría es que se examinaría
el mismo directorio más de una vez, y por tanto, podría haber ficheros repetidos. Así que, para
evitar esas repeticiones de directorios y evitar hacer el mismo trabajo más de una vez, pues he
usado una colección llamada dirExaminados a la que voy agregando cada directorio, pero
solo se añade si no existe en la colección, y en caso de que exista, pues... simplemente se sale
del método recorrerDir (que es donde se hace la comprobación).
El código usado es el siguiente:
If dirExaminados.Contains(di.FullName.ToLower()) Then
Exit Sub
End If
dirExaminados.Add(di.FullName.ToLower())
Por supuesto, el contenido de esa colección se "limpia" antes de empezar a procesar las cosas
(es decir, cada vez que pulsas en el botón Buscar).
Además de esto que te acabo de contar, también se tiene en cuenta que se puedan indicar
varios directorios y varias especificaciones o filtros, con idea de que en una misma búsqueda
puedas tener en cuenta más de un directorio y más de un fichero a buscar (o tener en cuenta en
la búsqueda).
Conclusiones
Espero que te sea de utilidad y te sirva para algo más que para usar simplemente la
utilidad... que es de lo que se trata todo esto que te estoy contando, y que aunque no te lo
crea, lo empecé a escribir a las 03.42 (aunque en realidad
debería empezar a contar a eso de las 14 horas, ya que a las 03.42 lo que hice fue preparar la
página para escribir, pero después fui a llevar a mi hijo al aeropuerto y cuando volví me acosté
y en realidad hasta eso de las 2 de la tarde no empecé "en serio") y lo he acabado a las
22.55, (en realidad, con los ajustes que le he hecho, ya son es
la 01.06 del lunes día 10), por tanto, han sido más de 9 horas "efectivas" las que he
tardado en dejar listo este artículo (y el de
la explicación del formato XAML) (sí, ya lo sé, es que soy mu
pamplinas, je, je).
Así que... me gustaría que todo ese tiempo empleado te sea de utilidad .
Más abajo tienes el ZIP con el proyecto completo para Visual Basic 2008,
ese proyecto lo puedes abrir tanto con la versión normal de Visual Studio 2008 como con la
versión Express de Visual Basic 2008.
Si te preguntas porqué no hay código de C#...
Bueno... decirte que es muy posible que a partir de ahora no ponga siempre los ejemplos
en Visual Basic y en C#, (algo que he estado haciendo en
todos (o casi todos) mis artículos desde Enero de 2001), ya que... (¿cómo
decirlo para que nadie se moleste?), bueno... a ver... es que como últimamente hay
una corriente de comentarios (aún más fuerte de lo habitual,
¿será por el cambio climático?) que dicen que los que usamos Visual Basic sabemos
menos que los que usan C#, pues... por lógica hay que deducir que los que usan C# son más
listos o saben más que los que usamos Visual Basic, así que... como los de C# son más
listos, pues seguro que sabrán convertir el código de VB a C# sin problemas, e incluso no
les hará falta ni convertirlo, porque ya sabrán hacerlo... así que... si eres de los que
están en el grupo que usa C# para escribir sus programas, pues... por ahora no estará este
ejemplo para C#... ¡lo siento!
Nos vemos.
Guillermo
P.S.
Si quieres hacer algún comentario sobre este artículo, por favor usa el siguiente link para
hacerlo en mis foros:
Artículo: gsBuscarTexto (v2.0 para VB2008)
Gracias.