
Option Explicit
DefLng A-Z
'DECLARACIONES
' Este es el tipo que se pasa a la función del API SHBroseForFolder
Type BROWSEINFO
hWndOwner As Long 'ventana propietaria del dialogo de buscar carpetas
pidlRoot As Long 'puntero al ItemID de la carpeta raíz
pszDisplayName As String 'el nombre mostrado del objeto
lpszTitle As String 'el titulo de la ventana de dialogo
uFlags As Integer 'modificadores - ver abajo
lpfn As Long 'direccion de una funcion "callback" (opcional)
lParam As Long 'para el "callback", no utilizado
iImage As Long 'para el "callback", no utilizado
End Type
Declare Function SHGetPathFromIDList Lib "shell32.dll" Alias "SHGetPathFromIDListA" (ByVal pidl As Long, ByVal pszPath As String) As Long
Declare Function SHBrowseForFolder Lib "Shell32" Alias "SHBrowseForFolderA" (lpbi As BROWSEINFO) As Long
Const BIF_RETURNONLYFSDIRS As Integer = 1 'Devolver sólo directorios del Sistema de Ficheros
'--------------------------------------------------------------------------------------
' Muestra un diálogo de buscar carpetas y devuelve el path a la carpeta escogida
' o una cadena vacía si la operación se canceló. Nótese que este procedimiento sólo
' devuelve carpetas del sistema de ficheros, no carpetas virtuales como Mi Ordenador o
' el Panel de Control
'--------------------------------------------------------------------------------------
Public Function BrowseForFolder(ByVal f_HWnd As Long, Optional lpTitle As Variant) As String
On Error Resume Next
Dim lpiidl As Long, lResult as Long
Dim lpbi As BROWSEINFO
Dim lpszBuf As String
Dim lpszNameSpace As String
lpszBuf = String$(255, Chr$(0))
lpszNameSpace = String$(255, Chr$(0))
'fijar los valores iniciales
With lpbi
.hWndOwner = f_HWnd 'el propietario del diálogo (para operación modal o no modal)
.pidlRoot = vbNullString 'comenzar a partir del Escritorio
.lpszTitle = lpTitle 'el texto por encima del árbol de carpetas (NO el "caption" del diálogo)
.pszDisplayName = lpszBuf 'contendrá al volver el nombre del objeto seleccionado
.uFlags = BIF_RETURNONLYFSDIRS 'devolver sólo carpetas del sistema de ficheros
.lpfn = vbNullString 'no hay función de "callback"
.lParam = 0& 'para el "callback", no utilizado
.iImage = 0& 'para el "callback", no utilizado
End With
' Mostrar el diálogo de buscar carpetas y obtener el puntero al ItemID asociado a la carpeta escogida
lpiidl = SHBrowseForFolder(lpbi)
' Si el usuario canceló el diálogo o ocurrió un error, devolver una cadena vacía
If lpiidl = 0 Then BrowseForFolder = "" : Exit Function
' Obtener el path del objeto seleccionado a partir del itemID
lResult = SHGetPathFromIDList(lpiidl, lpszNameSpace)
If lResult = 1 Then 'la función devuelve 1 si tuvo éxito, 0 si hubo algún fallo
' Devolver el path a la carpeta, quitando los caracteres nulos extras
BrowseForFolder = Left$(lpszNameSpace, InStr(lpszNameSpace, Chr$(0)))
End If
End Function
Para usar la función, añada un cuadro de texto y un botón en un form vacío y añada el siguiente código en el evento Click del botón:
Sub Command1_Click()
Dim ShellPath as String
ShellPath = BrowseForFolder(Me.hWnd, "Escoja una carpeta")
If ShellPath <> "" Then
Text1.Text = ShellPath
Else
MsgBox "¡Operación cancelada!"
End If
End Sub
La función SHBrowsForFolder devuelve un puntero a una estructura ITEMIDLIST, que el entorno de Windows utiliza para recuperar el path completo de la carpeta seleccionada. Puesto que la estructura ITEMIDLIST contiene un único miembro, mkID, es posible declarar el valor devuelto simplemente como long y pasarlo a SHGetPathFromIDList.
La función SHBrowseForFolder permite a la aplicación controlar el comportamiento del diálogo mediante el parámetro de "callback" (el miembro lpbi.lpfn), a través del cual la aplicación llamante puede controlar cosas como el texto de estado y la activación / desactivación del botón OK en el diálogo. Si se usa VB5, puede escribirse una función que implemente esta característica utilizando la función AddressOf. Si está interesado,
puede encontrar la implementación completa, junto con instrucciones sobre la función en la documentación de Win32 en el CD de la MSDN, o en el CD "Books Online" del Visual C++.
El miembro .pidlRoot de la estructura lpbi acepta valores del mismo tipo devuelto por SHBrowseForFolder: un puntero a una lista de ItemID. Puede modificarse el ejemplo para guardar el ItemID devuelto, y utilizar la última carpeta seleccionada como path inicial en operaciones sucesivas.
Cabe destacar también que el miembro .lpszTitle es una cadena que se muestra sobre la ventana del árbol de carpetas en el diálogo, pero no es el propio "caption" del diálogo, que es siempre "Buscar Carpeta".
La última consideración es el parámetro f_hWnd. Éste es un handle a la ventana llamante (Me.hWnd), y se pasa a SHBrowseForFolder. Puede pasarse un cero (handle nulo), y lo que sucederá será que el diálogo se muestra de forma "no modal". Sin embargo, si se pasa un handle a ventana válido, el diálogo se comportará exactamente como los formularios de VB cuando se utiliza Form.Show vbModal; es decir, la ventana llamante no podrá accederse hasta que se cierre el diálogo. La implementación no modal está orientada a diálogos controlados mediante "callback". Se podría, por ejemplo, cambiar la ventana de la aplicación dependiendo de qué carpeta el usuario está seleccionando en el diálogo, mientras la ventana del form llamante sigue disponible para la entrada de datos.
Una cosa más: la documentación del SDK dice que la aplicación llamante es responsable de liberar el puntero ITEMIDLIST; sin embargo, para programar esto habría que llamar a la función IMalloc del shell, que devuelve un puntero a un handle asignado a un mecanismo de reserva de procesos ("task allocator"). Como VB no soporta punteros a interfaces (al menos, no los que pueden llamarse desde C++ mediante la indirección "->"), no hay por tanto forma de acceder al mecanismo de reserva de procesos. Por tanto, el puntero a ITEMIDLIST queda en el limbo una vez que la función retorna. Si bien esto no es ciertamente un buen estilo de programación, he encontrado que incluso un uso intensivo de la función en un programa no ocasionaba efectos detectables en el sistema o en otras aplicaciones. Por supuesto, esto podría variar según el entorno en el que se ejecute una aplicación, así que conviene experimentar antes de utilizar la función en aplicaciones ampliamente distribuidas. Si funciona en su caso, quizás no sea un precio demasiado alto que pagar por un medio de buscar de carpetas interesante (¡y gratuito!). Así pues, a partir de ahora, tiene a su disposición una forma sencilla de permitir al usuario que seleccione un directorio en su PC o en otro ordenador de la red, sin necesidad de utilizar forms o controles adicionales.

