La batallita del agüelo:
El portátil que tengo ahora (un Sony Vaio) no se porqué, pero no "activa" el
salvapantallas de Windows Vista ni se desconecta la pantalla pasado un tiempo. Y
como no me gusta que la pantalla se quede con la imagen fija todo el tiempo, (el
portátil no lo apago nuca), pues pensé que si el salvapantallas no se "dispara"
solo, lo mejor sería tener un acceso directo en el escritorio a un
salvapantallas y poder ponerlo en marcha cuando me apeteciera.
Estuve mirando en las propiedades de Windows para ver los salvapantallas que
había disponibles y así poder elegir uno de ellos. Me gustó el de la aurora
boreal esa... así que... me dije, bueno, vamos a buscar ese ficherillo en el
disco C. Y como yo ya sabía que los salvapantallas tienen la extensión .scr abrí
el "Search" de Windows en el disco C, escribí el típico "*.scr" en la casilla de
búsqueda y esperé a ver si me lo encontraba... seguí esperando... y después de
mostrarme... ¡nada! me harté... Bueno, respiré un poco, me calmé (es que lo de
buscar en Windows Vista siempre me altera la bilirrubina o lo que me tenga que
alterar), y me dije, busquemos por el nombre, así que, escribí Aurora que es el
nombre del salvapantallas ese. No sé cuantas Auroras tengo en la libreta de
direcciones de Windows Mail (la versión para Windows Vista de mi querido Outlook
Express), pero me salieron unas cuantas... y es que con tantos años que llevo
guardando las direcciones de los correos que recibo o respondo, pues... es
lógico que salieran algunas...
La cuestión es que salían nombres, pero ningún fichero de salvapantallas...
Hasta llegué a pensar que esta gente de Microsoft al hacer lo de Windows Vista
pondrían de otra forma el tema este de los salvapantallas para que no se colara
ningún intruso por ese medio... es que como los salvapantallas se pueden hacer
hasta con Visual Basic, pues... cualquiera puede hacer algo "raro" con ese tipo
de programa...
Me puse a buscar directamente en el directorio de Windows, pero allí solo
estaba el salvapantallas de BOINC, aunque la verdad es que lo probé y no
funcionaba, así que... podía ser que mis sospechas tuvieran algo de validez y
los salvapantallas ahora estuvieran "escondidos".
Pero como tampoco me parecía algo "lógico", eso de que escondieran tanto los
salvapantallas me parecía algo exagerado, pues pensé que por "lógica deducción"
y conociendo los antecedentes que tiene el sistema de búsqueda de Windows Vista,
(que sí, que está muy bien para buscar en los mensajes y todo eso, pero es una
patata para buscar ficheros), me decidí a hacerme un programilla que buscara en
el disco (o directorio) que yo le dijera y buscando por lo que yo quisiera, en
este caso, una extensión, pero también vale para cualquier cosa que forme parte
del nombre de un fichero.
Y así es como ha surgido la versión de Visual Basic 2005 de esta utilidad.
Porque, aunque publico las cosas en Visual Basic y en C#, siempre empiezo los
nuevos proyectos con Visual Basic 2005 que es más fácil de programar, al menos
para mí.
La utilidad de buscar
El objetivo de esta utilidad lo tenía claro:
-Indicar una "especificación" es decir, algo que esté en el nombre de un
fichero, como puede ser la extensión.
-Indicar en que directorio debería buscar.
-Indicar si se debían examinar los directorios que tuviera ese directorio
(subdirectorios).
El resultado de la búsqueda lo mostraría en un control ListView
en vista
detalles con idea de mostrar el nombre del fichero y el directorio, de forma que
al hacer doble clic en el elemento que quisiera se abriera en el explorer ese
directorio, de esa forma podría acceder fácilmente al fichero en cuestión y
poder crear el acceso directo.
Buscar algo en el disco con las clases de .NET es algo muy sencillo. Lo que
yo necesitaba era recuperar la lista de ficheros que cumplieran la condición que
pusiera y mostrar esos ficheros en el ListView.
Básicamente lo que hay que hacer es esto:
Dim di As New DirectoryInfo(Directorio)
Dim fics() As FileInfo
fics = di.GetFiles(Filtro, SearchOption.AllDirectories)
Es decir, crear un objeto del tipo DirectoryInfo con el
nombre del directorio
a examinar, llamar al método GetFiles al que le indico el
filtro o
especificación de lo que quiero buscar, por ejemplo *.scr, le
digo si quiero
tener en cuenta todos los directorios (como es el caso del código anterior) y
eso me devuelve un array del tipo FileInfo que es lo que usaré
para asignar los
valores en el ListView.
El problema empezaba en que Windows Vista es muy "proteccionista" con el
disco C, (que es donde estaba buscando por los salvapantallas), y hay muchos
directorios que dan error cuando se quiere acceder a ellos y no se está haciendo
como "administrador" con todos los privilegios.
El segundo problema es que si se iban encontrando cosas, lo lógico sería que
se fuesen mostrando en la lista eso que se fuese hallando, porque entre otras
cosas, parece como menos aburrido tener que estar esperando sin ver nada...
Así que... pensé en el dicho ese de "divide y vencerás", que no se porqué lo
pensé ya que en realidad no tenía que dividir nada... bueno, sí, un poco el
trabajo de buscar en los directorios.
Además de que si "dividía" el trabajo, en este caso, buscando de forma
independiente en cada directorio, cada vez que se produjera una excepción de
"falta" de permisos, podría dejar de examinar ese directorio y pasar al
siguiente.
Al principio también iba mostrando un mensaje de error cada vez que se
producía ese fallo de acceso, así que... pensé (sí, recuerda que algunas veces
también pienso), que lo mejor sería poner una opción en el formulario de
búsqueda por si quería que me avisara de cada fallo o simplemente lo ignorara y
siguiera con lo que estaba haciendo.
Y con todo esto y un buen "batido", salió esta utilidad a la que he llamado
gsBuscar... ¿te imaginas porqué? Pues eso.
Este es el formulario en modo de diseño:
Figura 1. El formulario en modo diseño
He creado valores de configuración para almacenar los datos habituales:
posición y tamaño de la ventana, el filtro de búsqueda, el directorio en el que
se hizo la última búsqueda, si se deben incluir los subdirectorios y si se deben
ignorar los avisos de error.
La parte de inicio del formulario lo único que hace es asignar esos valores a
los controles correspondientes y el botón Examinar lo que hace
es usar el
componente FolderBrowserDialog para seleccionar el directorio
en el que se
empezará la búsqueda.
El "grueso" del trabajo está en el código del botón Buscar y
en un método que
se encarga de ir buscando directorio por directorio, de forma que si se produce
un error, simplemente se pasa al siguiente (en realidad se sale del método y el
bucle principal se encarga de comprobar si debe seguir buscando o no).
Lo que acabo de comentar de que el "bucle principal se encarga de comprobar
si debe seguir buscando o no" es porque el botón de Buscar
también sirve para
cancelar la búsqueda, ya que no es plan de que no queramos que se siga buscando
y tengamos que esperar una eternidad a que termine.
Empecemos viendo el código del botón Buscar.
El inicio de la búsqueda y comprobación si se debe cancelar
Veamos primero el código y después te explico lo que hago.
Private Sub btnBuscar_Click(ByVal sender As Object, _
ByVal e As EventArgs) _
Handles btnBuscar.Click
' Buscar de forma recursiva (si es necesario)
Static yaEstoy As Boolean
If yaEstoy Then
cancelar = True
Me.btnBuscar.Text = "Cancelando..."
Application.DoEvents()
Exit Sub
End If
yaEstoy = True
With My.Settings
.Location = Me.Location
.Size = Me.Size
.Directorio = Me.txtDir.Text
.Filtro = Me.txtFiltro.Text
.conSubDir = Me.chkConSubDir.Checked
.IgnorarError = Me.chkIgnorarError.Checked
.Save()
Dim di As New DirectoryInfo(.Directorio)
Me.lvFics.Items.Clear()
Me.LabelInfo.Text = "Buscando los ficheros..."
Me.Cursor = Cursors.AppStarting
Me.btnBuscar.Text = "Buscando..."
Me.Refresh()
recorrerDir(di)
End With
Me.Cursor = Cursors.Default
Me.LabelInfo.Text = "Se han hallado " & Me.lvFics.Items.Count & " ficheros"
Me.btnBuscar.Text = "Buscar"
Me.Refresh()
cancelar = False
yaEstoy = False
End Sub
Lo de la variable declarada Static (que en C# no se puede
hacer, pero se puede simular) es para que
esa variable mantenga el valor entre distintas llamadas a este método.
Inicialmente tendrá un valor False, pero en cuanto haya
empezado la búsqueda
tendrá un valor verdadero que es lo que se comprueba en el IF:
si "ya estoy" es
que se quiere cancelar, así que, asigno un valor verdadero a la variable
cancelar que al estar definida a nivel de la clase será visible en
todos los métodos y se podrá usar como "flag" para saber si se debe dejar de
buscar. Cambio el texto del botón para que se sepa que nos hemos enterado de que
se quiere cancelar y llamo al "fatídico" DoEvents para que se
sigan procesando
los mensajes de Windows y así de camino se refresca el formulario y la ventana
principal no se queda "congelada".
Si no estaba ya en este método es que acabo de entrar, así que, asigno a las
variables de configuración los valores de los controles y lo guardo, no sea que
me de por querer "cancelar" el proceso, y en ese caso no se guardarían los
datos.
Una vez hecho esto es cuando empieza el espectáculo.
Creo un objeto del tipo DirectoryInfo con el nombre del
directorio por el que
se empezará la búsqueda.
Limpio el contenido de lo que ya tuviera el ListView.
Cambio los textos del botón y muestro un aviso de que ya estoy empezando a
buscar.
Lo siguiente es llamar al método recorrerDir al que le paso
como argumento el
valor del directorio por el que empezaré a buscar. Como veremos ahora, en ese
método es donde en realidad se hace todo el trabajo.
Cuando termine de procesarse lo que haya en ese método querrá decir que la
búsqueda ha finalizado, por tanto se muestra la información correspondiente y se
vuelven a asignar los valores "adecuados" tanto al botón como a las variables
que controlan el funcionamiento de todo esto.
Ahora veamos el código del método recorrerDir.
El método que hace todo el trabajo (o casi)
Te digo lo mismo que antes, veamos el código y después te explico lo que hace
ese código.
Private Sub recorrerDir(ByVal di As DirectoryInfo)
' Recorrer los ficheros de este directorio
' añadir al listview si se encuentra alguno
Dim fics() As FileInfo
Dim dirs() As DirectoryInfo
Application.DoEvents()
If cancelar Then Exit Sub
Me.LabelInfo.Text = di.Name & "..."
Me.LabelInfo.Refresh()
With My.Settings
Try
fics = di.GetFiles(.Filtro, SearchOption.TopDirectoryOnly)
With Me.lvFics
For Each fi As FileInfo In fics
Dim lvi As ListViewItem = .Items.Add(fi.Name)
lvi.SubItems.Add(fi.DirectoryName)
Next
.Refresh()
End With
If .conSubDir Then
dirs = di.GetDirectories()
For Each dir As DirectoryInfo In dirs
recorrerDir(dir)
Next
End If
Catch ex As Exception
If .IgnorarError Then
Exit Sub
End If
If MessageBox.Show("Error: " & ex.Message & vbCrLf & _
"¿Quieres cancelar o continuar?", _
"Buscar en directorios", _
MessageBoxButtons.OKCancel, _
MessageBoxIcon.Exclamation _
) = DialogResult.Cancel Then
cancelar = True
Application.DoEvents()
End If
End Try
End With
End Sub
Lo primero que hago en este método, (aparte de declarar las variables que se
van a usar), es comprobar si se ha cancelado.
La llamada a DoEvents es
importante por dos razones, pero la más importante es que hará que nuestra
ventana no se quede congelada y así de camino se evita el mensajillo ese de "not
responding" que el Windows suele añadirle al título de la ventana cuando la
aplicación se
cuelga... que en realidad no es que se cuelgue, sino que deja de responder a los
mensajes de Windows y el propio Windows se cree que ya está "liquidada".
La otra razón es que así el valor de "cancelar" se evalúa de
inmediato
y en caso de que sea verdadero, pues... salimos del método.
Después asigno el nombre del directorio que se está examinando a la etiqueta
de "información" y la refresco para que el texto se vea, si no la refrescara, no
se vería hasta que se llamara nuevamente a DoEvents.
Lo siguiente es asignar al array fics los ficheros que
cumplan la especificación de búsqueda que hemos indicado. Fíjate que en el
segundo parámetro del método GetFiles uso el valor
TopDirectoryOnly que lo que le indica al método es que no devuelva los
ficheros de los subdirectorios que tenga este directorio. Esto es así porque si
se deben comprobar los subdirectorios se hace más abajo.
Si se han encontrado ficheros en este directorio, se añaden al
ListView,
asignando el nombre a la primera columna y el directorio a la segunda.
Después compruebo si se deben examinar los subdirectorios, de ser así, se
asigna a la variable dirs los subdirectorios de este
directorio, algo que se
obtiene con el método GetDirectories de la clase
DirectoryInfo.
Y llamo de forma recursiva a este mismo método pasándole el valor de cada uno
de esos subdirectorios.
Ese pequeño detalle es el que hace que esta utilidad sea más útil, ya que así
se irá examinando cada directorio por separado y se irán mostrando los
ficheros que se vayan encontrando.
En caso de que se produzca un error al acceder al método GetFiles
o GetDirectories, se ejecutará el código que hay en el bloque
Catch, en ese bloque
se comprueba si se debe ignorar el error, en ese caso, simplemente salimos del
método y si se debe mostrar el mensaje de error se hace de forma que el usuario
tenga la oportunidad de cancelar. Este hecho se comprueba viendo si el valor
devuelto por MessageBox.Show es DialogResult.Cancel,
en cuyo caso se asigna un
valor verdadero a la variable cancelar para que la próxima vez que se tenga que
examinar un directorio se sepa que no hay que seguir haciéndolo.
Y esto es todo... o casi, solo falta la parte de comprobar si se hace doble
clic en un elemento del ListView y en ese caso abrir el
Explorer de Windows. Este es el código:
Private Sub lvFics_DoubleClick(ByVal sender As Object, _
ByVal e As EventArgs) _
Handles lvFics.DoubleClick
With lvFics
If .SelectedIndices.Count > 0 Then
' Abrir la ventana con el directorio del fichero indicado
Dim dir As String = .SelectedItems(0).SubItems(1).Text
Process.Start("explorer.exe", dir)
End If
End With
End Sub
Aquí lo que se hace es lanzar un proceso con el explorer.exe al que le
pasamos como argumento el directorio que tiene que "explorar".
Ahora si que está todo.
Más abajo tienes el código completo para Visual Basic 2005
y para Visual C# 2005.
Nota:
En el código de C# tienes la forma de simular la variable estática (Static)
de VB, que en realidad consiste en crear una variable a nivel de la clase.
También tienes el código de cómo
simular el objeto
My.Settings y
My.Application.Info, al menos para las cosas que se
utilizan en esta utilidad.
Espero que te sea de utilidad y que lo "encuentres" bien.
Nos vemos.
Guillermo
Espacios de nombres usados en el código de este artículo:
Microsoft.VisualBasic
System.Windows.Forms
System.IO
System.Diagnostics
En el de C# también se usa y no se usa Microsoft.VisualBasic:
System.Reflection