Sie sind auf Seite 1von 395

API de Windows (1)

Funciones y ejemplos:

1. SendMessage: la que siempre hay que tener a mano


2. SetWindowWord: crear ventanas flotantes
3. Manejo de ventanas...
4. GetVolumeInformation: leer el volumen de un disco (32 bits)
5. GetDriveType: comprobar el tipo de unidad
6. Dejar una ventana siempre visible
7. Usar Sleep en lugar de DoEvents
8. Manejo del Registro
9. Dilogos comunes del API
10. Iconos en la barra de tarea
11. Marcador de telfonos de Win95
12. Sleep parece que no sirve para sustituir a DoEvents...
13. Usar GetTickCount en lugar de Timer
Ejemplo de GetTickCount()

14. Ficheros de declaraciones del API (16 y 32 bits)


15. Leer la etiqueta del volumen y el nmero de serie (slo 32 bits)
16. La lnea actual y el nmero de lneas de un text-box
17. Uso de PostMessage en lugar de SendMessage

1.- SendMessage: la que siempre hay que tener a mano


'Declaracin del API de 16 bits
Declare Function SendMessage Lib "User" _
(ByVal hWnd As Integer, ByVal wMsg As Integer, _
ByVal wParam As Integer, lParam As Any) As Long
'Declaracin del API de 32 bits.
Declare Function SendMessage Lib "User32" Alias
"SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Long) As Long
'Utilidades para un men de edicin:
'
'Declaracin de las constantes
Global Const WM_USER = &H400
Global Const EM_GETSEL = WM_USER + 0
Global Const EM_SETSEL = WM_USER + 1
Global Const EM_REPLACESEL = WM_USER + 18
Global Const EM_UNDO = WM_USER + 23
Const EM_LINEFROMCHAR = WM_USER + 25
Const EM_GETLINECOUNT = WM_USER + 10
'
Global Const WM_CUT = &H300
Global Const WM_COPY = &H301
Global Const WM_PASTE = &H302
Global Const WM_CLEAR = &H303
'
'Deshacer:
'Nota: si se hace de esta forma,
'no es necesario usar una variable para asignar el
valor devuelto.
If SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
EM_UNDO, 0, ByVal 0&) Then
End If
'tambin: x =
SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
EM_UNDO, 0, ByVal 0&)

'Copiar:
If SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
WM_COPY, 0, ByVal 0&) Then
End If
'Cortar:
If SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
WM_CUT, 0, ByVal 0&) Then
End If
'Borrar:
If SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
WM_CLEAR, 0, ByVal 0&) Then
End If
'Pegar:
If SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
WM_PASTE, 0, ByVal 0&) Then
End If
'Seleccionar Todo:
If SendMessage(Screen.ActiveForm.ActiveControl.hWnd,
EM_SETSEL, 0, ByVal &HFFFF0000) Then
End If

'Crear un TextBox con 64 KB en lugar de 32


Global Const WM_USER = &H400
Global Const EM_LIMITTEXT = WM_USER + 21
Dim LTmp As long
LTmp = SendMessage(Text1.hWnd, EM_LIMITTEXT, 0, ByVal
0&)

2.- SetWindowWord: crear ventanas flotantes


Declare Function SetWindowWord Lib "User" (ByVal hWnd As
Integer, ByVal nIndex As Integer, ByVal wNewWord As
Integer) As Integer
Declare Function SetWindowWord Lib "User32" Alias
"SetWindowWord" (ByVal hwnd As Long, ByVal nIndex As
Long, ByVal wNewWord As Long) As Long

'Crear una ventana flotante al estilo de los tool-bar


'Cuando se minimiza la ventana padre, tambin lo hace
sta.
Const SWW_hParent = -8
'En Form_Load (suponiendo que la ventana padre es Form1)
If SetWindowWord(hWnd, SWW_hParent, form1.hWnd) Then
End If

3.- Manejo de ventanas...


'Declaracin de Funciones para tomar las listas de
tareas
Declare Function GetWindow Lib "user" (ByVal hWnd As
Integer, ByVal wCmd As Integer) As Integer
Declare Function GetWindowText Lib "user" (ByVal hWnd As
Integer, ByVal lpString As String, ByVal nMaxCount As
Integer) As Integer
Declare Function GetWindowTextLength Lib "user" (ByVal
hWnd As Integer) As Integer
Declare Function IsWindowVisible Lib "User" (ByVal hWnd
As Integer) As Integer
'Declaraciones para 32 bits
Declare Function GetWindow Lib "user32" Alias
"GetWindow" (ByVal hwnd As Long, ByVal wCmd As Long) As
Long
Declare Function GetWindowText Lib "user32" Alias
"GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As
String, ByVal cch As Long) As Long
Declare Function GetWindowTextLength Lib "user32" Alias
"GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Declare Function IsWindowVisible Lib "user32" (ByVal
hwnd As Long) As Long
'Constantes para GetWindow
Const GW_HWNDFIRST = 0
Const GW_HWNDLAST = 1
Const GW_HWNDNEXT = 2
Const GW_HWNDPREV = 3
Const GW_OWNER = 4
Const GW_CHILD = 5

4.bits)

GetVolumeInformation: volumen de un disco (slo 32

Declare Function GetVolumeInformation Lib "Kernel32"


Alias "GetVolumeInformationA" _
(ByVal lpRootPathName As String, ByVal
lpVolumeNameBuffer As String, _
ByVal nVolumeNameSize As Long, lpVolumeSerialNumber As
Long, _
lpMaximumComponentLength As Long, lpFileSystemFlags As
Long, _
ByVal lpFileSystemNameBuffer As String, ByVal
nFileSystemNameSize As Long) As Long
Ejemplo para leer el volumen de un disco, esta funcin se puede usar para
catalogar los CD's musicales!
Dim lVSN As Long, n As Long, s1 As String, s2 As String
s1=String$(255,Chr$(0))
s2=String$(255,Chr$(0))
l= GetVolumeInformation("unidad", s1, Len(s1), lVSN, 0,
0, s2, Len(s2))
'lVSN tendr el valor del Volume Serial Number (nmero
de serie del volumen)
Si "unidad" es el CD-ROM y tenemos un disco de msica, podemos usar el
VSN para hacer un catlogo de CD's ya que cada CD tiene un nmero
diferente.

5.- GetDriveType: comprobar el tipo de unidad


Para comprobar si es un CD-ROM (o CD-musical):
'Valores de retorno de GetDriveType
Public Const DRIVE_REMOVABLE = 2
Public Const DRIVE_FIXED = 3
Public Const DRIVE_REMOTE = 4

'Estos tipos no estn en el fichero de las declaraciones


del API de 16 bits
Public Const DRIVE_CDROM = 5
Public Const DRIVE_RAMDISK = 6
'
Declare Function GetDriveType Lib "Kernel" (ByVal nDrive
As Integer) As Integer
Declare Function GetDriveType Lib "Kernel32" Alias
"GetDriveTypeA" (ByVal nDrive As String) As Long
Dim lDrive As Long
Dim szRoot As String
szRoot="D:\" 'Poner aqu la unidad del CD-ROM o la que
queramos comprobar
lDrive= GetDriveType(szRoot)
If lDrive = DRIVE_CDROM Then
'Es un CD-ROM/Compact-Disc
End If

6.- Dejar una ventana siempre visible


De nuevo usaremos el API de Windows: SetWindowPos
'Declaracin para usar ventanas siempre visibles
'Versin para 16 bits
Declare Function SetWindowPos Lib "User" (ByVal hWnd As
Integer, ByVal hWndInsertAfter As Integer, ByVal X As
Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal
cy As Integer, ByVal wFlags As Integer) As Integer
'Versin para 32 bits
Declare Function SetWindowPos Lib "User32" Alias
"SetWindowPos" (ByVal hwnd As Long, ByVal
hWndInsertAfter As Long, ByVal x As Long, ByVal y As
Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags
As Long) As Long
' SetWindowPos Flags
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
'Const SWP_NOZORDER = &H4
'Const SWP_NOREDRAW = &H8

Const SWP_NOACTIVATE = &H10


'Const SWP_DRAWFRAME = &H20
Const SWP_SHOWWINDOW = &H40
'Const SWP_HIDEWINDOW = &H80
'Const SWP_NOCOPYBITS = &H100
'Const SWP_NOREPOSITION = &H200
Const SWP_FLAGS = SWP_NOMOVE Or SWP_NOSIZE Or
SWP_SHOWWINDOW Or SWP_NOACTIVATE
'Cdigo para poner en Form_Load
'De esta forma no es necesario usar una variable para
asignar el valor devuelto:
If SetWindowPos(hWnd, -1, 0, 0, 0, 0, SWP_FLAGS) Then
End if

7.- Usar Sleep en lugar de DoEvents


Por si alguno no lo sabe, DoEvents se usa cuando queremos que otros
programas/procesos de Windows sigan funcionando, de forma que nuestro
programa no se apodere de todo el tiempo de la CPU. Por ejemplo cuando
hacemos un bucle que puede durar "mucho", al ejecutar DoEvents,
Windows permite que otros programas sigan funcionando normalmente.
Es aconsejable siempre usar DoEvents ( o Sleep 0&) en los bucles largos.
Yo tambin lo uso cuando quiero que se "refresque" la informacin de un
control. Cuantas veces has asignado a un Label un nuevo Caption y no lo
ha mostrado?, prueba a poner DoEvents despus de la asignacin y vers
como se muestra enseguida. (oye, esto debera aparecer en los trucos!)
Este truco est sacado de Tips & Tricks, from Visual Basic Web Magazine.
Segn el autor la funcin DoEvents hace lo siguiente:
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Con lo cual gasta tiempo comprobandos otros mensajes en el mismo
proceso. Este comportamiento no tiene valor en un sistema operativo
multitarea. Sleep lo hace de forma ms eficiente.
La declaracin de Sleep es:

Public Declare Sub Sleep Lib "kernel32" Alias "Sleep"


(ByVal dwMilliseconds As Long)
Y se puede llamar de la siguiente forma:

Sleep 0&

8.- Manejo del Registro del Sistema


Aqu os pongo algunos ejemplos para usar el Registro con el API de 32 bits.
Creo que tambin vale para 16 bits, no lo he probado, pero slo habr que
cambiar la declaracin de las funciones. Por si vale, pondr tambin las
declaraciones de 16 bits. Pero que conste que no las he probado.
Si quieres un ejemplo con todas estas funciones, echale un vistazo al cdigo
del programa gsExecute, que est en gsExec.zip (19 KB) La explicacin de
cmo funciona este programa la encontrars en Programas de Visual Basic.
Normalmente, para obtener los programas asociados a una extensin, slo
es necesario usar la funcin: RegQueryValue. La siguiente funcin de
ejemplo, es la que uso para obtener informacin de una clave del registro:
Public Const HKEY_CLASSES_ROOT = &H80000000
Declare Function RegQueryValue Lib "advapi32.dll" Alias
"RegQueryValueA" _
(ByVal hKey As Long, ByVal lpSubKey As String, ByVal
lpValue As String, _
lpcbValue As Long) As Long
'Busca una entrada en el registro
Private Function QueryRegBase(ByVal Entry As String,
Optional vKey) As String
Dim buf As String
Dim buflen As Long
Dim hKey As Long
'Si no se especifica la clave del Registro, usar
HKEY_CLASSES_ROOT
If IsMissing(vKey) Then
hKey = HKEY_CLASSES_ROOT
Else
hKey = CLng(vKey)

End If
On Local Error Resume Next
buf = Space$(300)
buflen = Len(buf)
'Buscar la entrada especificada y devolver el valor
asignado
If RegQueryValue(hKey, Entry, buf, buflen) = 0 Then
If buflen > 1 Then
'El formato devuelto es ASCIIZ, as que
quitar el ltimo caracter
QueryRegBase = Left$(buf, buflen - 1)
Else
QueryRegBase = ""
End If
Else
QueryRegBase = ""
End If
'Desactivar la deteccin de errores
On Local Error GoTo 0
End Function
Para usarla, por ejemplo para saber el programa asociado para abrir una
determinada extensin, de que programa se obtiene el icono y que nmero
de icono es:
NOTA: Para usar este ejemplo, hay que tener un control List2 en el Form y
la rutina mostrada antes.
Private Sub BuscarExtensionID(sExt As String)
Dim lRet As Long
Dim sKey As String
Dim sValue As String
Dim hKey As Long
Dim sExe As String
Dim sIcon As String
Dim lIcon As Long
Dim sProgId As String
Dim i As Integer
Caption = "Mostrar asociaciones de la clave: " &

sExt

List2.Visible = True

List2.Clear
List2.AddItem "Valores del Registro para " & sExt
'
'Buscar en el registro la extensin...
sProgId = QueryRegBase(sExt)
If Len(sProgId) Then
List2.AddItem "Clave: " & sProgId
sKey = sProgId & "\DefaultIcon"
List2.AddItem sKey
sValue = QueryRegBase(sKey)
If Len(sValue) Then
i = InStr(sValue, ",")
If i Then
sIcon = Left$(sValue, i - 1)
lIcon = Val(Mid$(sValue, i + 1))
Else

'No tiene programa para Defaulticon


sIcon = sValue
lIcon = 0
sValue = ""

End If
End If
List2.AddItem "

Icono de: " & sIcon

List2.AddItem "

Icono n: " & lIcon

'
'Obtener el programa asociado por defecto para
Abrir
'no quiere decir que este sea el que se ejecute cuando
se haga doble-click
sKey = sProgId & "\Shell\Open\Command"
sValue = QueryRegBase(sKey)
If Len(sValue) Then
i = InStr(sValue, ".")
If i Then
i = InStr(i, sValue, " ")
If i Then
sExe = Trim$(Left$(sValue, i - 1))
Else
sExe = Trim$(sValue)
End If
Else

sExe = Trim$(sValue)
End If
End If
List2.AddItem sKey
List2.AddItem "

Programa asociado: " & sExe

End If
End Sub
Ejemplo para crear claves en el Registro:
Para no alargar demasiado este fichero, aqu slo estn las declaraciones de
las funciones; en los listados del programa gsExecute, hay ejemplos de
cmo crear y borrar claves para asociar/desasociar un programa a una
extensin determinada.
'Claves del Registro
Public Const HKEY_CLASSES_ROOT = &H80000000
Public Const HKEY_CURRENT_USER = &H80000001
Public Const HKEY_LOCAL_MACHINE = &H80000002
Public Const HKEY_USERS = &H80000003
'
'Para los valores devueltos por las funciones de manejo
del Registro
Public Const ERROR_SUCCESS = 0&
Public Const ERROR_NO_MORE_ITEMS = 259&
'
' Tipos de datos Reg...
Public Const REG_SZ = 1
'
'Declaraciones del API de Windows para 32 bits
Declare Function RegQueryValue Lib "advapi32.dll" Alias
"RegQueryValueA" (ByVal hKey As Long, ByVal lpSubKey As
String, ByVal lpValue As String, lpcbValue As Long) As
Long
Declare Function RegEnumKey Lib "advapi32" Alias
"RegEnumKeyA" (ByVal hKey As Long, ByVal iSubKey As
Long, ByVal lpszName As String, ByVal cchName As Long)
As Long
Declare Function RegOpenKey Lib "advapi32" Alias
"RegOpenKeyA" (ByVal hKey As Long, ByVal lpszSubKey As
String, phkResult As Long) As Long
Declare Function RegCloseKey Lib "advapi32" (ByVal hKey
As Long) As Long

Declare Function RegCreateKey Lib "advapi32.dll" Alias


"RegCreateKeyA" (ByVal hKey As Long, ByVal lpSubKey As
String, phkResult As Long) As Long
Declare Function RegSetValue Lib "advapi32.dll" Alias
"RegSetValueA" (ByVal hKey As Long, ByVal lpSubKey As
String, ByVal dwType As Long, ByVal lpData As String,
ByVal cbData As Long) As Long
Declare Function RegDeleteKey Lib "advapi32.dll" Alias
"RegDeleteKeyA" (ByVal hKey As Long, ByVal lpSubKey As
String) As Long
'Declaraciones para el API de 16 bits
Declare Function RegQueryValue Lib "shell.dll" (ByVal
hKey As Long, ByVal lpSubKey As String, ByVal lpValue As
String, lpcbValue As Long) As Long
Declare Function RegEnumKey Lib "shell.dll" (ByVal hKey
As Long, ByVal iSubKey As Long, ByVal lpszName As
String, ByVal cchName As Long) As Long
Declare Function RegOpenKey Lib "shell.dll" (ByVal hKey
As Long, ByVal lpszSubKey As String, phkResult As Long)
As Long
Declare Function RegCloseKey Lib "shell.dll" (ByVal hKey
As Long) As Long
Declare Function RegCreateKey Lib "shell.dll" (ByVal
hKey As Long, ByVal lpSubKey As String, phkResult As
Long) As Long
Declare Function RegSetValue Lib "shell.dll" (ByVal hKey
As Long, ByVal lpSubKey As String, ByVal dwType As Long,
ByVal lpData As String, ByVal cbData As Long) As Long
Declare Function RegDeleteKey Lib "shell.dll" (ByVal
hKey As Long, ByVal lpSubKey As String) As Long

Una nota de precaucin:


Si vas a trabajar con el registro del sistema, te recomiendo que antes hagas
copia del mismo. En el CD de Windows 95, hay una utilidad: ERU.exe que
copia los archivos del Sistema, as como Autoexec, etc. Si no tienes este
programa, copia los archivos System.dat y User.dat que estn el directorio
de Windows.

9.- Dilogos comunes usando el API de Windows (16 y 32 bits)


Las funciones para manejar los dilogos comunes del API de Windows, son
las siguientes:
Nota: En 16 bits no estn todas las que son, es que no tengo ahora a mano
el fichero con las declaraciones para seleccionar el color y las fuentes. Si las

necesitas, no dudes en pedirlas, las buscar. en algn sitio tengo que


tenerlas. 8-)
'Declaraciones para el API de 16 bits
'Abrir y guardar
Declare Function GetOpenFileName Lib "commdlg.dll"
(lpofn As tagOpenFileName) As Integer
Declare Function GetSaveFileName Lib "commdlg.dll"
(lpofn As tagOpenFileName) As Integer
'Buscar y reemplazar (an no he podido ponerlas en
marcha???)
Declare Function FindText Lib "commdlg.dll" (lpFR As
tagFindReplace) As Integer
Declare Function ReplaceText Lib "commdlg.dll" (lpFR As
tagFindReplace) As Integer
'Para la impresora
Declare Function PrintDlg Lib "commdlg.dll" (tagPD As
tagPrintDlg) As Integer
'
'Declaraciones para 32 bits
'Abrir y guardar
Declare Function GetOpenFileName Lib "comdlg32.dll"
Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME)
As Long
Declare Function GetSaveFileName Lib "comdlg32.dll"
Alias "GetSaveFileNameA" (pOpenfilename As OPENFILENAME)
As Long
Declare Function GetFileTitle Lib "comdlg32.dll" Alias
"GetFileTitleA" (ByVal lpszFile As String, ByVal
lpszTitle As String, ByVal cbBuf As Integer) As Integer
'Buscar y reemplazar
Declare Function FindText Lib "comdlg32.dll" Alias
"FindTextA " (pFindreplace As FINDREPLACE) As Long
Declare Function ReplaceText Lib "comdlg32.dll" Alias
"ReplaceTextA" (pFindreplace As FINDREPLACE) As Long
'Para la impresora
Declare Function PrintDlg Lib "comdlg32.dll" Alias
"PrintDlgA" (pPrintdlg As PRINTDLG) As Long
Declare Function PageSetupDlg Lib "comdlg32.dll" Alias
"PageSetupDlgA" (pPagesetupdlg As PAGESETUPDLG) As Long
'Para los colores
Declare Function ChooseColor Lib "comdlg32.dll" Alias
"ChooseColorA" (pChoosecolor As CHOOSECOLOR) As Long
'Las fuentes
Declare Function ChooseFont Lib "comdlg32.dll" Alias
"ChooseFontA" (pChoosefont As CHOOSEFONT) As Long

No incluyo ejemplos ni las declaraciones de los tipos, por ser demasiado


"grandes". Pero las incluyo en un listado con ejemplos para abrir, etc.,
aunque con las funciones para 16 bits, ya que desde que uso el VB para 32
bits, suelo hacerlo con el control que trae. Si quieres ver ejemplos usando
el control de dilogos comunes, pasate por la pgina de trucos.
Listado con las declaraciones para dilogos comunes usando el API de
Windows (cmdlgapi.zip 5.012 bytes)

10.- Mostrar un icono en la barra de tareas


Gracias a Joe LeVasseur por enviar este ejemplo de cmo crear un icono en
la barra de tareas.
Baja el listado de ejemplo (EjemplBT.zip 6.717 bytes)
Aqu pongo parte del cdigo, para los que slo quieren echar un vistazo:
'--------------Private Type TIPONOTIFICARICONO
cbSize As Long
hwnd As Long
uId As Long
uFlags As Long
ucallbackMessage As Long
hIcon As Long
szTip As String * 64
End Type
'-----------------Private Const NIM_ADD = &H0
Private Const NIM_MODIFY = &H1
Private Const NIM_DELETE = &H2
Private Const WM_MOUSEMOVE = &H200
Private Const NIF_MESSAGE = &H1
Private Const NIF_ICON = &H2
Private Const NIF_TIP = &H4
Private Const WM_LBUTTONDBLCLK = &H203
Private Const WM_LBUTTONDOWN = &H201
Private Const WM_LBUTTONUP = &H202
Private Const WM_RBUTTONDBLCLK = &H206
Private Const WM_RBUTTONDOWN = &H204
Private Const WM_RBUTTONUP = &H205

'-------------------Private Declare Function Shell_NotifyIcon Lib "shell32"


_
_

Alias "Shell_NotifyIconA" (ByVal dwMessage As Long,


pnid As TIPONOTIFICARICONO) As Boolean

'-------------------Private Declare Function WinExec& Lib "kernel32" _


(ByVal lpCmdLine As String, ByVal nCmdShow As Long)
'-------------------Dim t As TIPONOTIFICARICONO

Private Sub Form_Load()


If App.PrevInstance Then
mnuAcerca_Click
Unload Me
End
End If
'--------------------------------t.cbSize = Len(t)
t.hwnd = picGancho.hwnd
t.uId = 1&
t.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
t.ucallbackMessage = WM_MOUSEMOVE
t.hIcon = Me.Icon
'--------------------------------t.szTip = "Ejemplo de barra de tareas..." & Chr$(0)
' Es un string de "C" ( \0 )
Shell_NotifyIcon NIM_ADD, t
Me.Hide
App.TaskVisible = False
End Sub

11.- Cmo usar el marcador telefnico de Windows 95


Gracias de nuevo a Joe LeVasseur por enviar este ejemplo.
Aqu lo que se muestra es slo la forma de usarlo.

Private Declare Function tapiRequestMakeCall& Lib


"TAPI32.DLL" _
(ByVal DestAddress&, ByVal AppName$, ByVal CalledParty$,
ByVal Comment$)
Private Sub Command1_Click()
Dim ValDev&, Numero$, NombreProg$, Quien$
Numero = "123-4567"
NombreProg = "Mi Programa"
Quien = "Pepe"
ValDev = tapiRequestMakeCall(Numero,
NombreProg,Quien,"")
End Sub

12.- Sleep parece que no sirve para sustituir a DoEvents


He probado a usar Sleep en lugar de DoEvents, segn se explica en el truco
7, y no funciona.
Al menos como yo espero que lo haga. Es decir, sustituir DoEvents por
Sleep 0& no hace que el proceso continue, al menos en la misma aplicacin.
He intentado hacer una prueba, para comprobar la funcin GetTickCount, y
no sala del bucle; incluso cambiando Sleep 0& por Sleep 1&. Alguien sabe
por qu?

13.- Usar GetTickCount() en lugar de Timer


Esta funcin si la he probado. El efecto es similar a usar Timer, para saber
los segundos transcurridos desde la medianoche. La diferencia principal, es
que Timer devuelve un valor en segundos y GetTickCount() lo devuelve en
milsimas de segundos. Por tanto para algunos clculos, es ms precisa la
funcin del API.
Como nota adicional, decir que en el API de 16 bits, GetTickCount() es igual
que GetCurrentTime()
'La declaracin de esta funcin para 16 y 32 bits:
#If Win32 Then
Declare Function GetTickCount Lib "kernel32" () As

Long

#Else
Declare Function GetTickCount Lib "User" () As Long
#End If

Por ejemplo, podemos usarla para saber la diferencia en el tiempo de


ejecucin de una serie de instrucciones, un bucle, etc.
Dim T1 As Long
Dim T2 As Long
Dim L As Long
T1 = GetTickCount()
For L = 1 to 320000
DoEvents
Next
T2 = GetTickCount()
Print "Duracin: "; T2 - T1 ; " milisegundos."

13.1.- Ejemplo de GetTickCount()


He hecho este ejemplo, para ver si haba ms exactitud en la funcin del
API que en con el TimerControl, por si ayudaba a Pedro, ver Proyectos,
pero no he notado ninguna. He repetido el bucle hasta 20.000 veces y
nada, no ha mostrado diferencias. Lo siento Pedro.
Baja el listado en formato zip:

(eje_Tick.zip 2.390 bytes)

Para usarlo, hay que crear un Form con los siguientes controles:

'
'Prueba de Timer,

(21:00 14/Ene/96)

Option Explicit
'Declaracin del API
#If Win32 Then
Private Declare Function GetTickCount Lib "Kernel32"
() As Long
#Else
Private Declare Function GetTickCount Lib "User" ()
As Long
#End If
'Para saber que accin debe tomar el botn
Dim Contando As Boolean
'Variables para el GetTickCount
Dim g1 As Long
Dim g2 As Long
Dim g3 As Long
'Variables para el Timer1
Dim t1 As Long
Dim t2 As Long
'valor para el timer
Dim vTimer As Long
'Flag para cancelar el bucle
Dim Cancelar As Boolean
'valor mximo de items a mostrar
Dim MaxBucle As Long
Private Sub cmdIniciar_Click()
Contar
End Sub
Private Sub cmdSalir_Click()
Cancelar = True
DoEvents
Contando = True
Contar
Unload Me
End Sub
Private Sub Form_Load()
'

Timer1.Interval = 1000
Timer1.Enabled = False
Text1 = "1000"
MaxBucle = 20000
End Sub
Private Sub Text1_Change()
'
Dim vTmp
Static YaEstoy As Boolean
'No entrar mientras se procesa...
If YaEstoy Then Exit Sub
YaEstoy = True
vTmp = Val(Text1) \ 10
If vTmp > VScroll1.Min Then
vTmp = VScroll1.Min
End If
If vTmp < VScroll1.Max Then
vTmp = VScroll1.Max
End If
Text1 = CStr(vTmp * 10)
VScroll1.Value = vTmp
YaEstoy = False
End Sub
Private Sub Timer1_Timer()
'nmero de segundos transcurridos
t2 = Timer - t1
Mostrar
DoEvents
End Sub
Private Sub VScroll1_Change()
Static YaEstoy As Boolean

Dim vTmp As Variant


If YaEstoy Then Exit Sub
YaEstoy = True
vTmp = VScroll1.Value * 10
Text1 = CStr(vTmp)
YaEstoy = False
End Sub
Private Sub Contar()
'Si ya est contando, dejar de contar...
If Contando Then
Cancelar = True
DoEvents
Timer1.Enabled = False
cmdIniciar.Caption = "Iniciar"
Contando = False
Text1.Enabled = True
Else
'Empezar la cuenta...
Label2(0) = ""
Label2(1) = ""
cmdIniciar.Caption = "Detener"
Cancelar = False
Contando = True
Text1.Enabled = False
DoEvents
t1 = Timer
Timer1.Enabled = True
Timer1.Interval = Val(Text1)
OtraCosa
End If
End Sub
Private Sub OtraCosa()
'Este procedimiento es para hacer algo...
'aunque no valga para nada

Dim i As Long
List1.Clear
'Inicializar el temporizador "manual"
g1 = GetTickCount()
g3 = 0
Do
i = i + 1
List1.AddItem CStr(i)
List1.ListIndex = List1.ListCount - 1
gTimer
If Cancelar Then Exit Do
If i = MaxBucle Then
Exit Do
End If
Loop
'Detener la accin...
Contando = True
Contar
'Actualizar los resultados
Mostrar
End Sub
Private Sub gTimer()
g2 = GetTickCount() - g1
'Este if, es para que se muestre cada segundo
If g2 > (g3 + 1) * 1000 Then
g3 = g3 + 1
Mostrar
End If
DoEvents
End Sub
Private Sub Mostrar()
Label2(0) = t2
Label2(1) = g3
End Sub
'

14.- Ficheros con las declaraciones del API de Windows, para VB


(16 y 32 bits)
Estos ficheros estn en el directorio FTP de mi Web, pero no puedes entrar
con un programa FTP, simplemente linka en el que te interese para poder
bajrtelo.
Son los siguientes:

API de 32 bits (win32api.zip 146 KB)

API de 16 bits para Windows 3.1 (win31api.zip 33,9 KB)

API de 16 bits para Windows 3.0 (win30api.zip 25,6 KB)

API de 16 bits extensiones para Windows 3.1 (Win31ext.zip 7,86 KB)

15.- Leer la etiqueta y el nmero de serie de un disco. (Slo 32


bits)
La funcin que se usa para esto, es GetVolumeInformation, que est en el
punto 4, pero lo que ahora pongo, es un ejemplo de cmo usarla.
El ejemplo es un form con una caja de texto en la que se introduce la
unidad (directorio raz, realmente), de la que queremos mostrar la
informacin.
Como no es un listado muy grande, lo pongo al completo.
'-------------------------------------------------------------------------'Form de prueba para leer la etiqueta y el nmero de
serie de un disco.
'
(18/Feb/97)
'-------------------------------------------------------------------------Option Explicit
'Declaracin de la funcin, slo est en el API de 32
bits
'
Private Declare Function GetVolumeInformation Lib
"Kernel32" _
Alias "GetVolumeInformationA" (ByVal lpRootPathName
As String, _

ByVal
lpVolumeNameBuffer As String, _
ByVal
nVolumeNameSize As Long, _
lpVolumeSerialNumber
As Long, _
lpMaximumComponentLength As Long, _
lpFileSystemFlags As
Long, _
ByVal
lpFileSystemNameBuffer As String, _
ByVal
nFileSystemNameSize As Long) As Long

Private Sub Command1_Click()


'Accin
Dim lVSN As Long, n As Long, s1 As String, s2 As
String
Dim unidad As String
Dim sTmp As String
On Local Error Resume Next
'Se debe especificar el directorio raiz
unidad = Trim$(Text1)
'Reservar espacio para las cadenas que se pasarn al
API
s1 = String$(255, Chr$(0))
s2 = String$(255, Chr$(0))
n = GetVolumeInformation(unidad, s1, Len(s1), lVSN,
0, 0, s2, Len(s2))
's1 ser la etiqueta del volumen
'lVSN tendr el valor del Volume Serial Number
(nmero de serie del volumen)
's2 el tipo de archivos: FAT, etc.
'Convertirlo a hexadecimal para mostrarlo como en el

Dir.

sTmp = Hex$(lVSN)

Label3(0) = s1
Label3(1) = Left$(sTmp, 4) & "-" & Right$(sTmp, 4)
Label3(2) = s2
End Sub

Private Sub Command2_Click()


Unload Me
End
End Sub

Private Sub Form_Unload(Cancel As Integer)


'Asegurarnos de "liberar" la memoria.
Set Form1 = Nothing
End Sub
Ahora un "retrato" del Form:

16.- La lnea actual y el nmero de lneas de un text-box


Otras cosas ms que se pueden hacer con SendMessage.
La declaracin de esta funcin del API, para 16 y 32 bits, est en el punto 1
Const WM_USER = 1024
Const EM_GETLINECOUNT = WM_USER + 10
Const EM_LINEFROMCHAR = WM_USER + 25
TotalLineas = SendMessage(Text1.hWnd, EM_GETLINECOUNT,
0, 0&)
LineaActual = SendMessage(Text1.hWnd, EM_LINEFROMCHAR,
-1, 0&) + 1

17.- Uso de PostMessage en lugar de SendMessage


En la lista de distribucin VB-ES, le una respuesta sobre que es preferible,
en 32 bits, usar PostMessage en lugar de SendMessage.
Quiero aclarar que el valor devuelto por la funcin PostMessage, es si ha
podido poner el mensaje en la cola o no.
Por tanto, si usas SendMessage para recibir un valor, el ejemplo anterior es
un caso, no se te ocurra cambiarla por PostMessage.
En los dems casos, en los que simplemente queremos enviar un mensaje a
la cola de Windows y no necesitamos esperar a que la operacin termine, si
podemos usar PostMessage, ya que esta funcin trabaja de forma
"asncrona" y devolver el control a VB antes que SendMessage, que
trabaja de forma "sncrona" y hasta que no acabe "su tarea" no vuelve a
casa.
La declaracin de PostMessage para el API de 16 y 32 bits:

'Declaracin del API de 32 bits


Declare Function PostMessage Lib "User32" Alias
"PostMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, _
Long

ByVal wParam As Long, ByVal lParam As Long) As

'Declaracin del API de 16 bits


Declare Function PostMessage Lib "User" _
(ByVal hWnd As Integer, ByVal wMsg As Integer, _
Integer

ByVal wParam As Integer, lParam As Any) As

API de Windows (2)


Funciones y ejemplos:
1. Buscar en un ComboBox o ListBox usando el API

2. SHFormatDrive: Formatear un disco usando el API (y GetDriveType


para saber que tipo de unidad es)

3. Sobre los recursos en 32bits (un cdigo de Joe LeVasseur)


4. Saber el tipo de una unidad de disco y si es un CDROM (16 y 32 bits)
5. Averiguar el espacio libre de una unidad de disco (32 bits)
6. Nombre del usuario actual de Windows (32 bits)
7. Nmero de lneas, posicin del primer caracter de una lnea y
longitud (en un TextBox)

8. Bitmaps en los mens, usando API claro!


9. Ejecutar cualquier programa usando el API (un truco de Joe
LeVasseur)

10. Cambiar el fondo del escritorio de Windows (WallPaper)


11. Usando MSGBLAST para manejar mensajes de Windows (VB3/VB4-16
y VB4-32)

12. Reiniciar Windows (listados para 16 y 32 bits)


13. Cmo evitar el uso de CTRL+ALT+SUPR y ALT+TAB? (slo Win95)

14. Cmo enviar archivos a la papelera de reciclaje? (VB-32 bits)


15. Cmo desplegar y contraer el contenido de un ComboBox? (16 y 32
bits)

16. Esperar a que un programa termine (incluso si es de MS-DOS) (32


bits)

17. Comprobar si existe un fichero, usando el API, claro. (FileExist)


18. Reiniciar Windows (2 parte) revisado para Windows NT
19. Averiguar el espacio libre de una unidad de disco (16 bits)
20. Tocar un archivo de forma indefinida y repetitiva (slo WAVs)
21. Un ejemplo, usando API, de cmo tocar de forma indefinida un
fichero MIDI

22. Saber el nombre de nuestro equipo (32 bits)

1.- Buscar en un ComboBox o ListBox usando el API


La funcin que se encarga de esta tarea, como casi siempre, es
SendMessage y se deben especificar las siguientes constantes como el
mensaje que queremos enviar, segn lo que queramos hacer:
Const CB_FINDSTRINGEXACT = &H158
completa en un ComboBox

'Buscar cadena

Const LB_FINDSTRINGEXACT = &H1A2


completa en un ListBox

'Buscar cadena

Const CB_FINDSTRING = &H14C


desde el principio en un ComboBox

'Buscar cadena

Const LB_FINDSTRING = &H18F


desde el principio en un ListBox

'Buscar cadena

La declaracin de la funcin SendMessage, debe quedar de esta manera,


fijate que el parmetro lParam est definido como Any, de esta forma,
aceptar cualquier tipo de dato, Long o ByVal...
#If Win32 Then
Declare Function SendMessage Lib "User32" Alias
"SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long
#Else

Declare Function SendMessage Lib "User" _


(ByVal hWnd As Integer, ByVal wMsg As Integer, _
ByVal wParam As Integer, lParam As Any) As Long
#End If
El siguiente ejemplo es una funcin que inserta un nuevo elemento en una
lista o un combo, si es que no existe, claro.
Public Function ActualizarLista(sTexto As String, cList
As Control) As Long
'Esta funcin comprobar si el texto indicado existe
en la lista
'Si no es as, lo aadir
'El valor devuelto, ser la posicin dentro de la
lista -1 si hay "fallos"
'
'Para buscar en el List/combo usaremos una llamada
al API
'(si ya hay una forma de hacerlo, para que rehacerla?)
'
Const CB_FINDSTRINGEXACT = &H158
para los combos

'Mensaje

Const LB_FINDSTRINGEXACT = &H1A2


para las Listas

'Mensaje

Dim L As Long
If cList.ListCount = 0 Then
'Seguro que no est, as que aadirla
L = -1
Else
'Si el control es un Combo
If TypeOf cList Is ComboBox Then
L = SendMessage(cList.hWnd,
CB_FINDSTRINGEXACT, -1, ByVal sTexto)
'Si el control es un list
ElseIf TypeOf cList Is ListBox Then
L = SendMessage(cList.hWnd,
LB_FINDSTRINGEXACT, -1, ByVal sTexto)
Else
'no es un control List o Combo, salir
ActualizarLista = -1
Exit Function

End If
End If
'Si no est, aadirla
If L = -1 Then
cList.AddItem sTexto
L = ActualizarLista(sTexto, cList)
End If
ActualizarLista = L
End Function
Este otro ejemplo es para efectuar una bsqueda en el Combo/List, al estilo
de la ayuda de Windows, es una versin a la aparecida en los trucos, pero
usando SendMessage.
Para usarlo en un ListBox, debes indicar la constante LB_FINDSTRING, en
lugar de CB_FINDSTRING
(Este "truco" est sacado del MSDN Library: Tip 115: Performing Smart
Searches in Combo Box Controls)
Private Sub Combo1_KeyPress(KeyAscii As Integer)
Dim CB As Long
Dim FindString As String
Const CB_ERR = (-1)
Const CB_FINDSTRING = &H14C
If KeyAscii < 32 Or KeyAscii > 127 Then Exit Sub
If Combo1.SelLength = 0 Then
FindString = Combo1.Text & Chr$(KeyAscii)
Else
FindString = Left$(Combo1.Text, Combo1.SelStart)
& Chr$(KeyAscii)
End If
CB = SendMessage(Combo1.hWnd, CB_FINDSTRING, -1,
ByVal FindString)
If CB <> CB_ERR Then
Combo1.ListIndex = CB
Combo1.SelStart = Len(FindString)

Combo1.SelLength = Len(Combo1.Text) Combo1.SelStart


End If
KeyAscii = 0
End Sub

2.- SHFormatDrive: Formatear un disco usando el API,


GetDriveType para saber que tipo de unidad
Esta funcin es la que Joe LeVasseur ha usado para su utilidad de formatear
y copiar discos.
La declaracin es:
Declare Function SHFormatDrive Lib "shell32" _
(ByVal hwnd As Long, ByVal Drive As Long, ByVal
fmtID As Long, _
ByVal options As Long) As Long
La forma de usarla:
Dim DriveLetter$, DriveNumber&, DriveType&
Dim RetVal&, RetFromMsg%
DriveLetter = UCase(Drive1.Drive)
DriveNumber = (Asc(DriveLetter) - 65) ' Cambiar la
letra a nmero: A=0
DriveType = GetDriveType(DriveLetter)
'If DriveType = 2 Then

'Disquetes, etc

RetVal = SHFormatDrive(Me.hwnd, DriveNumber, 0&, 0&)


La declaracin del API para GetDriveType:
Declare Function GetDriveType Lib "kernel32" Alias
"GetDriveTypeA" _
(ByVal nDrive As String) As Long

3.- Sobre los recursos en 32bits (un cdigo de Joe LeVasseur)


Pues eso, el amigo Joe me ha enviado una funcin "wrapera" que sirve para
mostrar los recursos en aplicaciones de 32 bits.
Aqu te pongo las declaracin de la funcin y un ejemplo de cmo usarla.
Gracias Joe.
'-------' RSRC32.DLL es el DLL 32Bit que use Win95/NT para
"Thunking"
' del API antiguo. Windows lo use con RSRCMTR.EXE .
' (Otro API sin documentacion...) JTL (;-)
'--------Private Declare Function RecursosDeSistema Lib
"rsrc32.dll" _
Alias "_MyGetFreeSystemResources32@4" (ByVal restype As
Integer) As Integer
'--------Const GFSR_SYSTEMRESOURCES = &H0
Const GFSR_GDIRESOURCES = &H1
Const GFSR_USERRESOURCES = &H2
Dim iSystem%, iGDI%, iUser%
iSystem = RecursosDeSistema(GFSR_SYSTEMRESOURCES)
iGDI = RecursosDeSistema(GFSR_GDIRESOURCES)
iUser = RecursosDeSistema(GFSR_USERRESOURCES)
lblRecursos(3) = iSystem & "%"
lblRecursos(4) = iGDI & "%"
lblRecursos(5) = iUser & "%"

4.- Saber el tipo de una unidad de disco y si es un CDROM (16 y


32 bits)
Con esta funcin del API se puede averiguar el tipo de unidad, en la versin
de 32 bits, la cosa est bastante clara y segn el valor devuelto podemos
saber bastante de la unidad que estamos tratando.
Pero en 16 bits slo podemos obtener informacin de que es un disco
removible (disquete), fijo (discos duros) o unidad de red (puede ser eso
una unidad de red o bien un CD-ROM)
El motivo de esta pgina es aportar una librera DLL compilada en Delphi y
aportada por Robert Biglio, con la que podemos saber desde 16 bits si la
unidad que estamos comprobando es un CDROM o no.
Adems se muestra el ejemplo para usar GetDriveType tanto en 16 como en

32 bits; parte del cdigo usado, me lo envi Joe LeVasseur, aunque ya


estaba publicado en las pginas, pero para que sirva de agradecimiento por
ayudar a encontrar una solucin (tambin envi otra DLL para hacer la
misma funcin que la de Robert).
Gracias a Robert y Joe y tambin a Nora Mariduea que es la causante de
esta "bsqueda y captura" por una DLL para saber cual es la unidad de CDROM.
Espero que os pueda ser de alguna utilidad.
Pulsa en este link para bajar los listados, (getCDROM.zip 3 KB)

Ejemplo para comprobar si una unidad es un CD-ROM


'------------------------------------------------------------'Form de ejemplo para mostrar los tipos de unidades instaladas
'y accesibles (en caso de unidades de red)
'------------------------------------------------------------Option Explicit
#If Win32 Then
'Para 32 bits
Private Declare Function GetDriveType Lib "kernel32" Alias
_
"GetDriveTypeA" (ByVal nDrive As String) As Long
#Else
'Para 16 bits
'Funcin del API, pero no detecta si es un CD
Private Declare Function GetDriveType Lib "Kernel" _
(ByVal nDrive As Integer) As Integer
'Funcin de Robert Biglio
Private Declare Function EsCD Lib "CD16.DLL" _
(ByVal nDrive As Byte) As Integer
#End If
Function BuscarCDROM() As String
Dim NumDisco As Byte
Dim StrDisco$
Const DRIVE_CDROM = 5
For NumDisco = 0 To 25
StrDisco = Chr(NumDisco + 65) & ":\"
'Devuelve la ltima unidad de CDROM

#If Win32 Then


If GetDriveType(StrDisco) = DRIVE_CDROM Then
#Else
If EsCD(NumDisco) Then
#End If
BuscarCDROM = StrDisco
'Quitar el comentario si se quiere mostrar
'la primera unidad de CDROM
'Exit For
End If
Next
End Function
Private Sub LlenarLista()
Dim NumDisco%, StrDisco$
Dim ret&
Dim sTipos(0 To 6) As String
' Para los valores de retorno de GetDriveType (32bits)
sTipos(0) = " No Instalado"
sTipos(1) = " No Instalado"
sTipos(2) = " Extraible"
sTipos(3) = " Fijo"
sTipos(4) = " Remoto"
sTipos(5) = " CDROM"
sTipos(6) = " RAMDISK"
For NumDisco = 0 To 25
StrDisco = Chr(NumDisco + 65) & ":\"
#If Win32 Then
ret = GetDriveType(StrDisco)
#Else
ret = GetDriveType(NumDisco)
#End If
If ret > 1 Then
List1.AddItem Format(NumDisco, "00") & " " &
StrDisco & " --> " & ret & sTipos(ret)
End If
Next
End Sub

Private Sub Command1_Click()


Text1.Text = BuscarCDROM()
End Sub
Private Sub Form_Load()
#If Win32 Then
Caption = "Versin para 32 bits"
#Else
Caption = "Versin para 16 bits"
#End If
LlenarLista
End Sub

El Form de Ejemplo para 16 y 32 bits en funcionamiento

Esta es la versin de 16 bits (fijate en la unidad H)

En la versin de 32bits se sabe que H es un CDROM

5.- Averiguar el espacio libre de una unidad de disco (32 bits)


Esta es otra funcin del API de 32 bits, la pongo aqu a resultas de una
respuesta de las news sobre el API (en ingls)
Est en una funcin a la que hay que pasarle como parmetro el nombre de
la unidad y devuelve el tamao disponible.
'Declaracin del API de 32 bits.
Declare Function GetDiskFreeSpace Lib "kernel32" Alias
"GetDiskFreeSpaceA" _
(ByVal lpRootPathName As String, lpSectorsPerCluster As
Long, lpBytesPerSector As Long, _
lpNumberOfFreeClusters As Long, lpTotalNumberOfClusters
As Long) As Long
Private Function EspacioLibre(ByVal lpRootPathName As
String)
'lpRootPathName= Directorio raiz de la unidad a examinar
'Valores devueltos por la funcin:
'lpSectorsPerCluster = sectores por cluster
'lpBytesPerSector = bytes por sector
'lpNumberOfFreeClusters = nmero de clusters libres
'lpTotalNumberOfClusters = nmero de clusters en el
disco
Dim lpSectorsPerCluster As Long
Dim lpBytesPerSector As Long
Dim lpNumberOfFreeClusters As Long
Dim lpTotalNumberOfClusters As Long
Dim ret&
Dim TotalBytes As Long
ret = GetDiskFreeSpace(lpRootPathName,
lpSectorsPerCluster, _
lpBytesPerSector, lpNumberOfFreeClusters,
lpTotalNumberOfClusters)
TotalBytes = lpTotalNumberOfClusters *
lpSectorsPerCluster * lpBytesPerSector
EspacioLibre = Format(TotalBytes, "###,###,###")
End Function

Nota: Para averiguar el espacio libre en unidades de ms de 2GB chale un


vistazo a esto:
El espacio de las unidades grandes (ms de 2GB)

6.- Nombre del usuario actual de Windows (32bits) (8/Jul)


Otra rutinilla del API de Windows, en esta ocasin para saber el nombre del
usuario actual. Es decir el que ha empezado la sesin de Windows. Lo he
probado con Windows 95, pero con NT debera funcionar pero no est
comprobado.
El cdigo mostrado supone que tienes la declaracin en un mdulo BAS, en
caso de que lo uses en un FRM o CLS, debers poner delante Private.
Si quieres bajar los listados y un form de comprobacin, pulsa este link
(usuario.zip 1.38 KB)
'API para obtener el usuario actual
Declare Function GetUserName Lib "advapi32.dll" Alias
"GetUserNameA" _
(ByVal lpbuffer As String, nSize As Long) As Long
'Esta funcin devuelve el nombre del Usuario
Public Function UsuarioActual() As String
Dim sBuffer As String
Dim lSize As Long
Dim sUsuario As String
sBuffer = Space$(260)
lSize = Len(sBuffer)
Call GetUserName(sBuffer, lSize)
If lSize > 0 Then
sUsuario = Left$(sBuffer, lSize)
'Quitarle el CHR$(0) del final...
lSize = InStr(sUsuario, Chr$(0))
If lSize Then
sUsuario = Left$(sUsuario, lSize - 1)
End If
Else
sUsuario = ""
End If
UsuarioActual = sUsuario

End Function

7.- Nmero de lneas, posicin del primer caracter de una lnea y


longitud (en un TextBox)
Esta es una variante de la ya presentada anteriormente. Pero adems
permite saber la longitud en caracteres de una lnea.
Lo he usado en la rutina genrica de impresin.
Una de las cosas que algunos habis consultado, a m y en otros sitios, es
cmo poder obtener cada una de las lneas de un control TextBox Multiline,
creo que para el RichTextBox no servira, pero es cuestin de comprobarlo.
Como esta seccin/apartado est dedicada al API, como comprobars es
precisamente la funcin SendMessage la que se usa para conseguir nuestro
propsito. La declaracin la tienes en la otra pgina del API, as que aqu
slo voy a poner la forma de usarla y el valor de las constantes para cada
una de estas tres cosas.
'Las constantes:
Const EM_GETLINECOUNT = &HBA

'Nmero de lneas

Const EM_LINEINDEX = &HBB


primer caracter de la lnea actual

'Posicin del

Const EM_LINELENGTH = &HC1


lnea

'Longitud de una

'Nmero de lneas del TextBox:


NumLineas = SendMessage(unTextBox.hWnd, EM_GETLINECOUNT,
0, 0&)
'Posicin del primer carcter de la lnea X:
L1 = SendMessage(unTextBox.hWnd, EM_LINEINDEX, X, 0&) +
1
'Longitud, en caracteres, de la lnea que empieza por el
caracter L1:
L2 = SendMessage(unTextBox.hWnd, EM_LINELENGTH, L1, 0&)
'Forma de usarlo todo junto:
NumLineas = SendMessage(unTextBox.hWnd, EM_GETLINECOUNT,
0, 0&)
For X = 0 To NumLineas - 1
L1 = SendMessage(unTextBox.hWnd, EM_LINEINDEX, X,
0&) + 1
0&)

L2 = SendMessage(unTextBox.hWnd, EM_LINELENGTH, L1,


'Contenido de la lnea X:
Linea$ = Mid$(unTextBox.Text, L1, L2)

'...
Next

8.- Bitmaps en los mens, usando API claro!


Este ejemplo lo mandaron a la lista de VB-ESP y lo pongo en este link para
el que quiera usarlo, es para VB5.
Segn parece es de Alexander Forbes.
Si tienes VB3 o VB4-16 bits, puedes usar el ejemplo de Jordi Garcia
Busquets (API-Men).
Baja los listados de ejemplo. (IcoMenu.zip 2.85 KB)

9.- Ejecutar cualquier programa usando el API (un truco de Joe


LeVasseur)
Este es un cdigo del amigo Joe LeVasseur y sirve para ejecutar cualquier
programa, archivo asociado, lo que quieras.
Aqu pongo el cdigo y el comentario del colega Joe.
ShellExecute es como hacer doble-click
sobre un archivo en el Explorer.
Un saludo desde Connecticut, EEUU
Joe LeVasseur
PS - Para VB4/5 32bit
********************************************************
***
Joe LeVasseur lvasseur@tiac.net

a0@null.net

Microsoft Dev MVP- Visual Basic


"To none will we sell, to none deny or delay,
right or justice." Magna Carta

(June 15, 1215)

********************************************************
**
Option Explicit
Private Declare Function ShellExecute Lib "shell32.dll"
Alias _
"ShellExecuteA" (ByVal hwnd As Long, ByVal
lpOperation As String, _

ByVal lpFile As String, ByVal lpParameters As


String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long)
As Long
Private Sub Command1_Click()
Dim lValDev

As Long

lValDev = ShellExecute(Me.hwnd, "Open",


"c:\splash.mid", _
"", "", 1)
End Sub

10.- Cambiar el fondo del escritorio de Windows (WallPaper)


Con este cdigo podrs cambiar la imagen del fondo de Windows, est
tomado de los Tips de Microsoft:
Tip 211: Changing or Removing the Desktop Wallpaper in Visual Basic 4.0
Este es el cdigo a usar:
Private Declare Function SystemParametersInfo Lib
"user32" Alias "SystemParametersInfoA" _
(ByVal uAction As Long, ByVal uParam As Long, _
ByVal lpvParam As String, ByVal fuWinIni As Long) As
Long
Const SPIF_UPDATEINIFILE = &H1
Const SPI_SETDESKWALLPAPER = 20
Const SPIF_SENDWININICHANGE = &H2
Private Sub Command1_Click()
Dim X As Long
X = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0&,
"(None)", _
SPIF_UPDATEINIFILE Or SPIF_SENDWININICHANGE)
MsgBox "Wallpaper was removed"
End Sub
Private Sub Command2_Click()

Dim FileName As String


Dim X As Long
'Usa aqu el bitmap que quieres usar
FileName = "c:\windows\pinstripe.bmp"
X = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0&,
FileName, _
SPIF_UPDATEINIFILE Or SPIF_SENDWININICHANGE)
MsgBox "Wallpaper was changed"
End Sub

11.- Usando MSGBLAST para manejar mensajes de Windows


(VB3/VB4-16 y VB4-32)
Unos listados de ejemplo para mostrar un mensaje en algn tipo de StatusBar cuando nos movemos por los items de un men.
Los listados de ejemplo son para VB3 (o VB4-16 bits) y para VB4-32 bits.
Pulsa estos links para bajarlos: Ejemplo de 16 bits (1.89 KB) - Ejemplo de
32 bits (2.21 KB)
Para el ejemplo de 16 bits necesitas el Msgblast.VBX que puedes bajarte del
ejemplo de Jordi Garca Busquets: (27.5 KB)
Para el de 32 bits necesitas bajarte e instalar el MsgBlast.OCX (caduca a los
30 das de instalarlo), el archivo est comprimido y ocupa un mega aprox.,
por tanto lo he "troceado" con el Cut-It (23.4 KB), el cual necesitars para
poder unirlos:
Estos links bajarn cada uno de los trozos: Trozo 1, Trozo 2, Trozo 3, Tozo
4, Trozo 5, el de configuracin.
Si tienes problemas con el archivo de configuracin, copia esto y lo pegas
con el nombre: msgBlastOCX.cIt
D:\WareWithAll\Controls\Samples\MenuStuf\msgBlastOCX.zip
1051351

;Tamao original del archivo

23/09/97 0:48:04
5

;Nmero de trozos

Archivo troceado el 23/09/1997 02:10:53


224

;Tamao mximo de cada trozo

32

;KB por bucle

Nota: Tambin puedes unir los trozos de esta forma:


copy /B
msgBlastOCX.C01+msgBlastOCX.C02+msgBlastOCX.C03+msgBlastOCX.C0
4+msgBlastOCX.C05 msgBlastOCX.zip

12.- Reiniciar Windows (listados para 16 y 32 bits)


Esta es la versin para 16 bits: (VB3...)
'---------------------------------------------------------' gsIniW (Reiniciar Windows)

Versin 16 bits

'
' (c) Guillermo Som Cerezo

(18/May/95)

'
' Utilidad para reiniciar windows.
' Muestra tambin la memoria y recursos libres. ( 1/Sep/96)
'
'---------------------------------------------------------Option Explicit
Declare Function ExitWindows Lib "User" (ByVal ReStartCode As Long, ByVal
DosReturnCode As Integer) As Integer
'Obtener la memoria y recursos libres

( 1/Sep/96)

Declare Function GetFreeSpace Lib "Kernel" (ByVal wFlags As Integer) As Long


Declare Function GetFreeSystemResources Lib "User" (ByVal fuSysResource As
Integer) As Integer
Const GFSR_SYSTEMRESOURCES = &H0
Sub Main()
Dim Memoria&, m$
Memoria& = GetFreeSpace(0)
m$ = "Recursos libres: " & GetFreeSystemResources(GFSR_SYSTEMRESOURCES)
& "%"
m$ = m$ & " - Memoria libre: " & Format$(Memoria& \ 1024, "###,###,###")
& " KB"
If MsgBox(m$ & vbCrLf & vbCrLf & "Quieres reiniciar Windows?", 4 + 16 +
256, "Reiniciar Windows") = 6 Then
Memoria& = ExitWindows(66, 0)
End If
End

End Sub

Esta es la versin para 32 bits: (VB4...)


Option Explicit
'-------------------------------------------------' ReIniWin (Reiniciar Windows)

( 8/Nov/95)

' (tengo una versin de Mayo'94)


'-------------------------------------------------#If Win32 Then
'Para usar con ExitWindowsEx
Public Const EWX_LOGOFF = 0

'Termina la sesin actual

'

Public Const EWX_SHUTDOWN = 1

'Finaliza Windows

'

Public Const EWX_REBOOT = 2

'Reinicia el equipo

'
Public Const EWX_FORCE = 4
responde

'Fuerza a terminar una aplicaci que no

Public Declare Function ExitWindowsEx Lib "user32" (ByVal uFlags As


Long, ByVal dwReserved As Long) As Long
'ExitWindows termina la sesin actual e inicia una nueva
'(es decir reiniciar windows)
'
Public Declare Function ExitWindows Lib "user32" (ByVal dwReserved As
Long, ByVal uReturnCode As Long) As Long
#Else
Public Declare Function ExitWindows Lib "user" (ByVal ReStartCode As
Long, ByVal DosReturnCode As Integer) As Integer
#End If
Public Sub Main()
Dim msg As String
Beep
msg = "Este programa reiniciar Windows."
If MsgBox(msg & vbCrLf & vbCrLf & "Seguro que quieres reiniciar
Windows?", 4 + 16 + 256, " ATENCIN !") = 6 Then
'ReStart Windows
#If Win32 Then
If ExitWindowsEx(EWX_LOGOFF, 0&) Then
#Else
If ExitWindows(66, 0) Then
#End If
End If
Else
End

End If
End Sub

Esta versin es para 16 bits y tiene unas cuantas cosas interesantes, creo...
Entre otras cosas, vuelvo a recordar que slo la he probado en Windows 3.1x.
En este programa se cambia el "grfico" del control box de la ventana, esto en Windows95 no
tiene sentido, pero en Windows 3.1x si que lo tiene, ya no muestra el icono de la aplicacin.
De todas formas, tambin es til para poder usar el dibujo que quieras usar.
Acabo de probarlo, desde VB3 y me he dado cuenta que ya por esta poca tena en los
botones (no en los grficos, sino en las etiquetas) unos efectos esos de que se activa cuando
pasas por encima de ellos...
Tambin tiene, entre otras cosas, el mtodo que usaba para saber el Ejecutable asociado con
una extensin determinada, es que en aquellos tiempos no haba caido en mis manos ningn
cdigo que lo hiciera, as que lo hice "por la cuenta de la vieja", mira el cdigo de
BuscarExtensin y vers porqu lo digo.
A lo mejor todo esto te parece complicado, pero... es cuestin de tomar la parte que te
interese. ;-)
Veamos la pantalla de inicio:

La pantalla de ejecutar tareas:

Una captura en ejecucin:

Ahora el cdigo:
Ya que es un poco largo, te recomiendo que "bajes" el archivo ZIP (gswexit.zip 13.8 KB)
Aqu slo voy a poner un par de cosillas.
Realmente, despus de ver el cdigo, mi intencin con este programa fue el de distribuirlo de
forma gratuita, lo que ocurre es que nunca llegu a hacerlo, echale un vistazo a lo que
pretenda ser el Acerca de... (pero que nunca fu)
Insisto: esto era para el Windows 3.1x

'
Sub mnuGuille_Click ()
'Este ser el About...
'La idea de hacer esta utilidad, surgi de poder
'reiniciar Windows, sin tener que salir de Windows.
'Ya que cuando se est programando, es necesario dejar
'Windows como estaba al cargar, sin libreras que se
'quedan 'colgadas' cuando no terminas correctamente el
'programa y otras malas pasadas, sin ser 'desequilibradoras'.
'Despus fu aadiendo opciones, tal como guardar una
'lista de los ltimos programas ejecutados; porque
'tambin es desesperante el tener que probar un msmo
'programa y por no crear un icono, estar siempre 'Examinando'
'la localizacin del msmo. As como querer ejecutar
'los programas que vas 'descomprimiendo' en un directorio
'temporal, para ir viendo si son interesantes...
'No s si este programa llegar a 'distribuirlo', pero
'que conste que cada da que pasa voy cambiando cosas,
'segn la informacin que voy 'acumulando'.
'Informacin que viene de los programas Share, etc.
'
'Si tienes alguna rutina o truco del Api de Windows

'que se pueda usar con VB, te agradecera que me la


'mandaras, siempre es bueno 'saber' ms.

Gracias.

End Sub
'Esta es la parte que dibuja el bitmap en el sitio del Men de Control
'No recuerdo de dnde saque esta informacin:
Sub DrawBitmapNCArea (hWindow As Integer, hbmp As Integer, cxLeft As
Integer, cyTop As Integer)
Dim hdc%, hdcMem%
Dim bmp As BITMAP
Dim hbmpOld%
Dim lprect As RECT
Dim hinst%
Dim rc&
hinst = GetWindowWord(hWindow, GWW_HINSTANCE)
hdc = GetWindowDC(hWindow)
hdcMem = CreateCompatibleDC(hdc)
rc = APIGetObject(hbmp, Len(bmp), bmp)
hbmpOld = SelectObject(hdcMem, hbmp)
rc = BitBlt(hdc, cxLeft, cyTop, bmp.bmWidth, bmp.bmHeight, hdcMem, 0, 0,
SRCCOPY)
rc = SelectObject(hdcMem, hbmpOld)
rc = DeleteDC(hdcMem)
rc = ReleaseDC(hWnd, hdc)
End Sub
'Buscar el ejecutable asociado a una extensin (del archivo WIN.INI)
Function BuscarExtension (QueExt As String) As String
'---------------------------------------------------------'Buscar una extensin en el fichero WIN.INI

( 7/Nov/94)

'---------------------------------------------------------Dim WinDir As String


Dim nf As Integer
Dim stmp As String
Dim EnExtensions As Integer
Dim i As Integer
WinDir = String$(255, 0)

nf = GetWindowsDirectory(WinDir, Len(WinDir))
WinDir = Left$(WinDir, nf)
nf = FreeFile
Open WinDir + "\WIN.INI" For Input As nf
Do While Not EOF(nf)
Line Input #nf, stmp
If EnExtensions Then
stmp = UCase$(LTrim$(stmp))
'comprobar si no es otro grupo
If Left$(stmp, 1) = "[" Then
EnExtensions = False
stmp = ""
Exit Do
End If
If Left$(stmp, 3) = QueExt Then
'Extensin hallada
'Quitar los caracteres no vlidos
i = InStr(stmp, "^")
If i Then
stmp = Left$(stmp, i - 1)
End If
i = InStr(stmp, "=")
If i Then
stmp = LTrim$(Mid$(stmp, i + 1))
End If
Exit Do
End If
End If
If InStr(stmp, "[Extensions]") Then
EnExtensions = True
End If
Loop
Close nf
'Si no est definida esa extensin, usar el bloc
If EnExtensions Then
BuscarExtension = stmp
Else
BuscarExtension = "notepad"
End If
End Function

'La parte que se encarga de cada una de las opciones:


Sub Image1_DblClick (Index As Integer)
'Esto estaba antes en Click
Dim sDOS As String
Dim i As Integer
Select Case Index
Case 1

'Salir de Windows

ExitWin 3
Case 2

'Reiniciar Windows

ExitWin 2
Case 3

'Reiniciar el ordenador

ExitWin 1
Case 4

'Salir temporalmente al DOS

'Comprobar cul es el COMSPEC y ejecutarlo.


sDOS = Trim$(Environ$("COMSPEC"))
If Len(sDOS) Then
'Ejecutar el archivo indicado en COMSPEC
i = Shell(sDOS)
Else
'Si no hay COMSPEC, ejecutar COMMAND.COM
i = Shell("command.com")
End If
Case 5

'Ejecutar un programa...

Form2.Show 1
Case 6

'Terminar el programa

( 8/Nov/94)

Unload Me
End Select
End Sub

13.- Cmo evitar el uso de CTRL+ALT+SUPR y ALT+TAB? (slo


en Win95) (5/Oct)
Pues usando el API, como casi siempre.
Este truco/comentario, est sacado de las Knowledge Base de Microsoft:
HOWTO: Block CTRL+ALT+DEL and ALT+TAB in Windows 95 - Article ID:
Q161133
As que si quieres el original en ingls ya sabes dnde buscarlo. Resumiendo
lo que dicen es que slo es posible hacerlo en Windows 95 y que
seguramente en futuras versiones no estar soportado. Adems de las
habituales precauciones, ya que si no se pueden usar estas teclas y "casca"

el programa... no te digo lo que tendrs que hacer...


Aqu est la declaracin de la funcin que lo permite y un poco de ejemplo
para poder hacerla funcionar.
'Declaracin de la funcin:
Private Const SPI_SCREENSAVERRUNNING = 97&
Private Declare Function SystemParametersInfo Lib
"User32" Alias "SystemParametersInfoA" _
(ByVal uAction As Long, ByVal uParam As Long, _
lpvParam As Any, ByVal fuWinIni As Long) As Long
'Para deshabilitar estas teclas:
Dim lngRet As Long
Dim blnOld As Boolean
lngRet =
SystemParametersInfo(SPI_SCREENSAVERRUNNING, _
True, blnOld, 0&)
'Para volver a habilitarlas:
Dim lngRet As Long
Dim blnOld As Boolean
lngRet =
SystemParametersInfo(SPI_SCREENSAVERRUNNING, _
False, blnOld, 0&)
Como recomendacin final: asegrate que en el Form_Unload que tengas,
haga una llamada a la rutina que vuelve a habilitar estas teclas, as todo
volver a estar como debiera.

14.- Cmo enviar archivos a la papelera de reciclaje? (VB-32


bits)
Nota del 26/May/2004: Para Windows XP mira las declaraciones de esta
otra pgina.
Otro truco sacado de las Knowledge Base de Microsoft:
HOWTO: Use the Windows 95 Copy and Recycle Functions in VB - Article
ID: Q165919
Tambin: Tip 176: Sending Files to the Recycle Bin in Visual Basic 4.0
De este segundo es este listado, que he modificado a mis preferencias,
sobre todo en el tema de la ubicacin de las declaraciones y eso, poca cosa,
lo s, pero algo es algo.

Voy a preparar un ejemplo/utilidad para poder usar las funciones del API
para copiar/mover/borrar, etc. que es lo que ms o menos se explica en el
primero de los dos artculos mencionados, pero no lo veo lo suficientemente
claro como para que sea fcilmente "usable" y entendible... (a lo mejor es
que soy demasiado torpe...)
'Crea un mdulo BAS e inserta estas declaraciones:
Type SHFILEOPSTRUCT
hWnd As Long
wFunc As Long
pFrom As String
pTo As String
fFlags As Integer ' As Long para que funcione en
Windows XP con VB6
fAborted As Boolean
hNameMaps As Long
sProgress As String
End Type
Public Const FO_DELETE = &H3
Public Const FOF_ALLOWUNDO = &H40
Declare Function SHFileOperation Lib "shell32.dll" Alias
"SHFileOperationA" _
(lpFileOp As SHFILEOPSTRUCT) As Long
Public Function ShellDelete(ParamArray vntFileName() As
Variant) As Long
Dim I As Integer
Dim sFileNames As String
Dim SHFileOp As SHFILEOPSTRUCT
For I = LBound(vntFileName) To UBound(vntFileName)
sFileNames = sFileNames & vntFileName(I) & vbNullChar
Next
sFileNames = sFileNames & vbNullChar
With SHFileOp
.wFunc = FO_DELETE
.pFrom = sFileNames
.fFlags = FOF_ALLOWUNDO

End With
ShellDelete = SHFileOperation(SHFileOp)
End Function
'En un formulario, inserta un botn Command y escribe
esto:
Private Sub Command1_Click()
Dim FileToKill As String
'Escribe aqu el archivo a borrar
FileToKill = "c:\test*.txt"
ShellDelete FileToKill
MsgBox "File(s) deleted"
End Sub
Para probarlo, simplemente pulsa F5, debers tener el archivo que quieres
borrar... sino cmo lo vas a borrar? 8-)

15.- Cmo desplegar y contraer el contenido de un ComboBox?


(16 y 32 bits)
Seguimos con los trucos que estoy "recogiendo" de las Knowledge Base de
Microsoft. En este caso es para hacer que se despliegue o se contraiga el
contenido de un ComboBox. Nuevamente gracias a nuestra amiga
SendMessage.
Este es el listado de ejemplo y las declaraciones.
#If Win32 Then
'Declaraciones para 32 bits
Declare Function SendMessage Lib "user32" Alias
"SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long
Const CB_SHOWDROPDOWN = &H14F
#Else
'Declaraciones para 16 bits

Declare Function SendMessage Lib "User" _


(ByVal hWnd As Integer, ByVal wMsg As Integer, _
ByVal wParam As Integer, lParam As Any) As Long
Const WM_USER = &H400
Const CB_SHOWDROPDOWN = (WM_USER + 15)
#End If
'Para que se despliegue:
Call SendMessage(Combo1.hWnd, CB_SHOWDROPDOWN, 1, 0&)
'Para que se contraiga:
Call SendMessage(Combo1.hWnd, CB_SHOWDROPDOWN, 0, 0&)

16.- Esperar a que un programa termine (incluso si es de MSDOS) (32 bits)


Una forma muchsimo ms simplificada de la rutina que usaba hasta ahora,
por supuesto, gracias al API.
Esta forma de hacerlo slo es para 32 bits, para 16 bits lo puedes ver en:
Especial Shell
La ventana se mostrar minimizada y sin foco.
No recuerdo si esta rutina la saqu de la Knowledge Base, lo que si es
seguro es que fue del CD del MSDN-Library.
NOTA: Si se va a enviar un comando del DOS, se debe usar con:
Command /C <orden a ejecutar|fichero.bat>
Sino, no se cerrar la ventana y el proceso no terminar, al menos algunas
veces.
'Las declaraciones
Private Declare Function OpenProcess Lib "kernel32" _
(ByVal dwDesiredAccess As Long, ByVal bInheritHandle
As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function GetExitCodeProcess Lib
"kernel32" _
(ByVal hProcess As Long, lpExitCode As Long) As Long

Private Declare Sub Sleep Lib "kernel32" (ByVal


dwMilliseconds As Long)
Const STILL_ACTIVE = &H103
Const PROCESS_QUERY_INFORMATION = &H400
'Un procedimiento para ejecutar y esperar a que termine
Private Sub ExecCmdNoFocus(ByVal CmdLine As String)
'Esperar a que un proceso termine,
'la ventana se mostrar minimizada sin foco
Dim hProcess As Long
Dim RetVal As Long
'The next line launches CmdLine as icon,
'captures process ID
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,
False, _
Shell(CmdLine, vbMinimizedNoFocus))
Do
'Get the status of the process
GetExitCodeProcess hProcess, RetVal
'Sleep command recommended as well
'as DoEvents
DoEvents
Sleep 100
'Loop while the process is active
Loop While RetVal = STILL_ACTIVE
End Sub

17.- Comprobar si existe un fichero, usando el API, claro.


(FileExist) (32 bits)
Realmente no es necesaria, ya que es fcil de hacer con el DIR$, pero es
bastante, al menos debera serlo, ms rpida que la susodicha instruccin
del VB.
Aqu tienes las declaraciones del API y la funcin.
'Tipos, constantes y funciones para FileExist
Const MAX_PATH = 260
Const INVALID_HANDLE_VALUE = -1

Private Type FILETIME


dwLowDateTime

As Long

dwHighDateTime

As Long

End Type
Private Type WIN32_FIND_DATA
dwFileAttributes

As Long

ftCreationTime

As FILETIME

ftLastAccessTime

As FILETIME

ftLastWriteTime

As FILETIME

nFileSizeHigh

As Long

nFileSizeLow

As Long

dwReserved0

As Long

dwReserved1

As Long

cFileName

As String * MAX_PATH

cAlternate

As String * 14

End Type
Private Declare Function FindFirstFile Lib "kernel32"
Alias "FindFirstFileA" _
(ByVal lpFileName As String, lpFindFileData As
WIN32_FIND_DATA) As Long
Private Declare Function FindClose Lib "kernel32" _
(ByVal hFindFile As Long) As Long
Public Function FileExist(ByVal sFile As String) As
Boolean
'comprobar si existe este fichero
Dim WFD As WIN32_FIND_DATA
Dim hFindFile As Long
hFindFile = FindFirstFile(sFile, WFD)
'Si no se ha encontrado
If hFindFile = INVALID_HANDLE_VALUE Then
FileExist = False
Else
FileExist = True
'Cerrar el handle de FindFirst
hFindFile = FindClose(hFindFile)

End If
End Function
'Para usarla:
If FileExist("lo que quieras comprobar") Then
'El archivo existe
Else
'El archivo no existe
End If

18.- Reiniciar Windows (2 parte) revisado para Windows NT


Aqu incluyo un poco de ms informacin sobre como reiniciar Windows, en
esta ocasin est comprobado en el NT, gracias a Ignacio Sanz
iiss@hotmail.com, por haber hecho la comprobacin.
Este cdigo de ejemplo est sacado de la Microsoft Knowledge Base, es el
artculo titulado: HOWTO: Shutdown Windows NT and Windows 95 from
Visual Basic Code. Que tampoco me gusta marcarme "faroles".

'El cdigo
' Tipos definidos
Private Type LUID
UsedPart As Long
IgnoredForNowHigh32BitPart As Long
End Type
Private Type TOKEN_PRIVILEGES
PrivilegeCount As Long
TheLuid As LUID
Attributes As Long
End Type
' Las constantes
Private Const EWX_SHUTDOWN As Long = 1
Private Const EWX_FORCE As Long = 4
Private Const EWX_REBOOT = 2

' Las funciones del API


Private Declare Function ExitWindowsEx Lib
"user32" ( _
ByVal dwOptions As Long, ByVal dwReserved As
Long) As Long
Private Declare Function GetCurrentProcess Lib
"kernel32" () As Long
Private Declare Function OpenProcessToken Lib
"advapi32" _
(ByVal ProcessHandle As Long, ByVal DesiredAccess As
Long, _
TokenHandle As Long) As Long
Private Declare Function LookupPrivilegeValue Lib
"advapi32" Alias "LookupPrivilegeValueA" _
(ByVal lpSystemName As String, ByVal lpName As String,
lpLuid As LUID) As Long
Private Declare Function AdjustTokenPrivileges Lib
"advapi32" _
(ByVal TokenHandle As Long, ByVal DisableAllPrivileges
As Long, _
NewState As TOKEN_PRIVILEGES, ByVal
BufferLength As Long, _
PreviousState As TOKEN_PRIVILEGES, ReturnLength
As Long) As Long
' Cdigo para poner en el formulario
Private Sub AdjustToken()
Const TOKEN_ADJUST_PRIVILEGES = &H20
Const TOKEN_QUERY = &H8
Const SE_PRIVILEGE_ENABLED = &H2
Dim hdlProcessHandle As Long
Dim hdlTokenHandle As Long
Dim tmpLuid As LUID
Dim tkp As TOKEN_PRIVILEGES
Dim tkpNewButIgnored As TOKEN_PRIVILEGES
Dim lBufferNeeded As Long
hdlProcessHandle = GetCurrentProcess()
OpenProcessToken hdlProcessHandle,
(TOKEN_ADJUST_PRIVILEGES Or _
TOKEN_QUERY), hdlTokenHandle

' Get the LUID for shutdown privilege.


tmpLuid

LookupPrivilegeValue "", "SeShutdownPrivilege",

tkp.PrivilegeCount = 1

' One privilege to

set
tkp.TheLuid = tmpLuid
tkp.Attributes = SE_PRIVILEGE_ENABLED
' Enable the shutdown privilege in the access
token of this
' process.
AdjustTokenPrivileges hdlTokenHandle, False,
tkp, _
Len(tkpNewButIgnored), tkpNewButIgnored,
lBufferNeeded
End Sub
Private Sub cmdForceShutdown_Click()
AdjustToken
ExitWindowsEx (EWX_SHUTDOWN Or EWX_FORCE Or
EWX_REBOOT), &HFFFF
End Sub

19.- Averiguar el espacio libre de una unidad de disco (16 bits)


Esto ya est en el listado del Sentinel (en la pgina de Gratisware) y est
sacado/adaptado del Setup que se incluye con el VB.
La funcin usada est en la librera StKit416.dll
'Funcin para leer el espacio libre de los discos (del
Setup1 de Visual Basic
Private Declare Function DiskSpaceFree Lib
"StKit416.dll" () As Long
Function EspacioLibre(D As String) As Currency
Dim TSpace As Long
TSpace = GetDiskSpaceFree(D)

'Devuelve el valor em Megas


EspacioLibre = TSpace / (1024& * 1024&)
End Function
'---------------------------------------------------------' FUNCTION: GetDiskSpaceFree
' Get the amount of free disk space for the specified
drive
'
' IN: [strDrive] - drive to check space for
'
' Returns: Amount of free disk space, or -1 if an
error occurs
'---------------------------------------------------------'
Function GetDiskSpaceFree(ByVal strDrive As String) As
Long
Dim strCurDrive As String
Dim lDiskFree As Long
Const gstrCOLON = ":"
On Local Error Resume Next
'
'Save the current drive
'
strCurDrive = Left$(CurDir$, 2)
'
'Fixup drive so it includes only a drive letter
and a colon
'
If InStr(strDrive, gstrCOLON) = 0 Or Len(strDrive)
> 2 Then
strDrive = Left$(strDrive, 1) & gstrCOLON
End If
'
'Change to the drive we want to check space for.
The DiskSpaceFree() API
'works on the current drive only.
'
ChDrive strDrive

'
'If we couldn't change to the request drive, it's
an error, otherwise return
'the amount of disk space free
'
If Err <> 0 Or (strDrive <> Left$(CurDir$, 2))

Then

lDiskFree = -1
Else
lDiskFree = DiskSpaceFree()
If Err <> 0 Then
couldn't be found

'If Setup Toolkit's DLL

lDiskFree = -1
End If
End If
'
GetDiskSpaceFree = lDiskFree
'
'Cleanup by setting the current drive back to the
original
'
ChDrive strCurDrive
Err = 0
End Function

20.-Tocar un archivo de forma indefinida y repetitiva


(10/Abr/98)
Para conseguir esto de que un fichero de sonido se toque de
forma "asncrona" y que no acabe nunca, es decir que siga
tocando cuando se acabe, se logra con unos parmetros que se le
da a la funcin del API que hace sonar un fichero WAV.
NOTA: Sobre los MIDs estoy intentando encontrar la forma de
que toquen de forma repetitiva...
Aqu tienes las declaraciones y los valores de las constantes a
usar:
Declare Function sndPlaySound Lib "winmm.dll" Alias
"sndPlaySoundA" _

(ByVal lpszSoundName As String, ByVal uFlags


As Long) As Long
Const SND_ASYNC = &H1
'modo asncrono. La
funcin retorna una vez iniciada la msica (sonido
en background).
Const SND_LOOP = &H8
repetidamente hasta

'La msica seguir sonando

'que la funcin
sndPlaySound sea llamada de nuevo con un valor nulo
para NombreWav (NULL).
'Para tocar un WAV de forma repetitiva, lo llamas
as:
Call sndPlaySound(Archivo, SND_ASYNC + SND_LOOP)
'Para detener lo que se est tocando
Call sndPlaySound(ByVal "", 0)
21.- Un ejemplo, usando API, de cmo tocar de
forma indefinida un fichero MIDI (10/Abr/98)
Aunque ya he puesto un tip en la seccin del API, aunque slo para WAVs, te muestro aqu
cmo hacer que tambin ser repita un fichero de sonido del tipo MIDI, de camino, en el
ejemplo tambin se incluye ese ejemplo, para que puedas probarlo, ya que entre otras cosas,
lo que si se puede hacer es tocar un fichero WAV y un MIDI al mismo tiempo... lo mismo
ocurrira con una pista de un CD de audio... Aunque este tema es ms laborioso por aquello
de que hay que controlar la pista que se quiere tocar y esas cosas... as que en este ejemplo
slo se contemplan los WAVs y MIDIs.
El ejemplo:
Este ejemplo permite seleccionar dos ficheros de sonido, relamente no permite seleccionar,
sino que debes especificar los que quieres usar.
Tiene dos botones para poder tocar cada uno de ellos, siempre que sean de diferentes tipos,
ya que no se permite tocar dos WAVs al mismo tiempo...
Slo habr un botn de detener, para parar cualquier fichero que se est tocando y para que
se puedan especificar los nombres de esos ficheros a usar, se usan dos TEXTBOXES.
Veamos una "instantnea" del form en tiempo de diseo:

Para los ficheros de tipo WAVs, no hay conplicacin, ya que usando sndPlaySound con los
parmetros: SND_ASYNC + SND_LOOP, se consigue lo deseado.
Sin embargo para los MIDIs, hay que saber cuando termina para volver a tocarlo de nuevo,
esto se soluciona aadiendo un control timer al form y comprobar si se ha terminado de tocar
el fichero, en caso de que sea as, empezar de nuevo... y as hasta que lo detengamos.
Este sera el cdigo del Timer1_Timer:
Private Sub Timer1_Timer()
Dim mciStatusParms As MCI_STATUS_PARMS
'Slo para el MIDI:
If IdMIDI Then
'Obtenemos el estado del dispositivo
mciStatusParms.dwItem = MCI_STATUS_MODE
Call mciSendCommand(IdMIDI, MCI_STATUS, MCI_STATUS_ITEM, _
mciStatusParms)
'Si no se est reproduciendo
If mciStatusParms.dwReturn = MCI_MODE_STOP Then
'Empezar de nuevo
'Posicionar el puntero al principio
Call mciSendCommand(IdMIDI, MCI_SEEK, _
MCI_SEEK_TO_START, 0)
'Empezar de nuevo
Call mciSendCommand(IdMIDI, MCI_PLAY, 0, 0)
End If
End If
End Sub
Es decir, se comprueba el estado del dispositivo, si el modo que devuelve es que est parado,
se posiciona el "puntero" de la cancin al principio y se le indica que vuelva a empezar a
tocarlo.
Cuando nos hartemos de que toque... lo paramos y ya est. Esto mismo hay que hacerlo al
cerrar el FORM...
Veamos el cdigo para detener el MIDI:
'Detener el MIDI
Call mciSendCommand(IdMIDI, MCI_STOP, 0, 0)
Call mciSendCommand(IdMIDI, MCI_CLOSE, 0, 0)
IdMIDI = 0

Timer1.Enabled = False
Para detenerlo, le mandamos un comando STOP y de camino cerramos el dispositivo.
Esto lo podemos tener en un procedimiento, para que lo podamos usar de forma genrica, lo
mismo desde un botn que detenga la msica como desde el evento UNLOAD del form.
Lo nico que necesitaramos es usar un SUB que haga lo mismo, es decir: PARE y CIERRE.
Veamos ahora el cdigo para que la cancin empiece a tocar:
'
Private Sub TocarMIDI(ByVal sMID As String)
'Tocar un Mid usando mciSendCommand
'
Dim mciOpenParms As MCI_OPEN_PARMS
If IdMIDI <> 0 Then
Call mciSendCommand(IdMIDI, MCI_CLOSE, 0, 0)
IdMIDI = 0
End If
With mciOpenParms
.lpstrElementName = sMID
End With
Call mciSendCommand(0, MCI_OPEN, _
MCI_OPEN_ELEMENT, _
mciOpenParms)
IdMIDI = mciOpenParms.wDeviceID
Call mciSendCommand(IdMIDI, MCI_PLAY, 0, 0)
Timer1.Enabled = True
End Sub
Aqu se comprueba si ya se est usando otro MIDI, en caso de que sea as, se detiene la
anterior y se empieza con la nueva.
Ahora bien... cmo sabemos si es un fichero MID o uno WAV?
Pues con una simple comparacin... si la extensin es MID, se tocar como MIDI, si es WAV,
como WAV...
Vamos a ver el cdigo completo del botn tocar:
Private Sub cmdTocar_Click(Index As Integer)
Dim Archivo As String

On Local Error Resume Next


Archivo = Text1(Index)
'Para tocar un fichero se la llamas as:
If InStr(Archivo, ".wav") Then
Call sndPlaySound(Archivo, SND_ASYNC + SND_LOOP)
ElseIf InStr(Archivo, ".mid") Then
TocarMIDI Archivo
End If
Err = 0
End Sub
Aqu lo nico que se hace es saber que fichero queremos tocar, comprobar si la extensin es
WAV, en cuyo caso se llama a la funcin sndPlaySound con los susodichos parmetros
ASYNC y LOOP.
El SND_LOOP es el que se encarga de que siempre est haciendo "bucles", por otra parte
SND_ASYNC lo que hace es que no se espere a que el fichero termine de tocar para que se
pueda hacer algo despus de empezar a tocarlo. Es decir: tocar de forma asncrona, osease
que no haya que esperar a que termine... 'ta claro?
En caso de ser un MIDI, se llama al procedimiento que hemos visto anteriormente.
Este es el cdigo completo del botn parar:
Private Sub cmdParar_Click()
'Para detener lo que se est tocando
Call sndPlaySound(ByVal "", 0)
'Detener el MIDI
Call mciSendCommand(IdMIDI, MCI_STOP, 0, 0)
Call mciSendCommand(IdMIDI, MCI_CLOSE, 0, 0)
IdMIDI = 0
Timer1.Enabled = False
End Sub
Para detener un fichero WAV, se llama a la funcin SndPlaySound con una cadena vaca y se
para la msica.
Para detener un MIDI, tambin se podra usar la funcin: mciSendString con el comando
"close all" y se parara el MIDI que estuviese sonando:

Call mciSendString("close all", 0, 0, 0)

22.- Saber el nombre de nuestro equipo (32 bits)


(14/Abr/98)
Usa el siguiente cdigo para averiguar el nombre de tu
equipo...
Declare Function GetComputerName Lib "kernel32"
Alias "GetComputerNameA" _
(ByVal lpBuffer As String, nSize As Long) As
Long
Public Const MAX_COMPUTERNAME_LENGTH = 255
Public Function ComputerName() As String
'Devuelve el nombre del equipo actual
Dim sComputerName As String
Dim ComputerNameLength As Long
sComputerName = String(MAX_COMPUTERNAME_LENGTH +
1, 0)
ComputerNameLength = MAX_COMPUTERNAME_LENGTH
Call GetComputerName(sComputerName,
ComputerNameLength)
ComputerName = Mid(sComputerName, 1,
ComputerNameLength)
End Function

API de Windows (3)


Algunas funciones interesantes del API
de Windows
Iniciado el 18-Abr-1998
Actualizado el 29-Oct-2002
Funciones y ejemplos:

1. El espacio de las unidades grandes (ms de 2GB)


2. ScrollBars en controles sin ScrollBars
3. Generar nmeros nicos para cada equipo
4. Posicionar un MsgBox usando AddressOf
5. Cambiar la resolucin de la pantalla (y el nmero de colores)
6. Subclasificar ventanas para interceptar mensajes (ejemplo para los
de seleccin de mens)

7. Saber el directorio de Windows (ya estaba, pero no tena link)


8. Seleccionar un directorio, usando SHBrowseForFolder
9. Deshabilitar los botones (y el men system) de un form Normal o
MDI

10. Una clase para saber los directorios del Sistema (usando el Registro)
11. Una API para saber los directorios del Sistema
(SHGetSpecialFolderPath)

12. Saber si un form se muestra Modal o Normal


13. Ejecutar un programa y redirigir la salida estndar al programa de
Visual Basic

14. timeGetTime, un temporizador ms preciso que GetTickCount


15. cQueryReg: una clase para manipular el registro del sistema
16. Conectarse usando Acceso Telefnico a Redes (ejemplo usando la
clase cQueryReg)

17. Enumerar las claves o valores de una clave del registro de Windows
(ejemplo usando la clase cQueryReg)

18. Enumerar los usuarios de nuestro equipo (profiles)


19. Registrar Hot-Keys para nuestra aplicacin (para activarla, por
ejemplo)

20. Manejar ficheros INIs: leer, guardar, borrar, leer secciones enteras,
leer todas las secciones (06/Mar)

21. Copiar, Mover y Eliminar ficheros usando el API de Windows


(SHFileOperation) (11/May)

22. Seleccionar carpetas e incluso ficheros, usando SHBrowseForFolder


(13/May)

23. cQueryReg: Revisin de la clase para manejar el registro del sistema


(12/Jun/99)

24. Conectarse a unidad de red (23/Jun/99)


25. Clase para manipular el volumen de la tarjeta de sonido (09/Jul/99)
26. Formularios transparentes en Windows 2000 (Layered Windows)
(24/Abr/00)

27. Posicionarse al principio o final de un MSFlexGrid (19/Ago/00)


28. cLocaleInfo: clase para obtener la configuracin regional de
Windows (23/Mar/01, 29/Oct/02)

29. GetLogicalDrives y GetLogicalDriveStrings, funciones para saber las


unidades lgicas de nuestro equipo (17/Abr/01)
(y las que estn disponibles)

30. GetPrinterJobs: Saber el nmero de trabajos pendientes de


imprimir (09/Jun/01)

31. Deshabilitar el botn cerrar de un formulario (20/Jun/01)

1.- El espacio de las unidades grandes (ms de 2GB) (18/Abr)


Como habrs comprobado, si has usado la funcin del API
GetDiskFreeSpace, el valor que se consigue est bien para unidades de
hasta 2 gigas, pero si usas unidades ms grandes... esa funcin se queda
pequea. Puedes ver un ejemplo de cmo usarla en Averiguar el espacio
libre de una unidad de disco (32 bits), aunque la funcin que puse en lugar
de devolver el espacio libre, devuelve el espacio total del disco.
El problema que uno se encuentra con esa funcin es que el valor devuelto
es siempre de 2GB incluso para discos con ms espacio... pero ese
"problemilla" se puede solucionar usando otra funcin del API. La
declaracin de esa funcin para usar con VB no viene en el fichero
WINAPI32.txt, pero he creado una declaracin para que se pueda usar.
Esta es la declaracin que viene en el fichero WINBASE.H
WINBASEAPI
BOOL
WINAPI

GetDiskFreeSpaceExA(
LPCSTR lpDirectoryName,
PULARGE_INTEGER lpFreeBytesAvailableToCaller,
PULARGE_INTEGER lpTotalNumberOfBytes,
PULARGE_INTEGER lpTotalNumberOfFreeBytes
);
El problema se me present a la hora de convertir el tipo ese
PULARGE_INTEGER, busqu en las definiciones y me encontr con esto en
el fichero WINNT.H:
#if defined(MIDL_PASS)
typedef struct _ULARGE_INTEGER {
#else // MIDL_PASS
typedef union _ULARGE_INTEGER {
struct {
DWORD LowPart;
DWORD HighPart;
};
struct {
DWORD LowPart;
DWORD HighPart;
} u;
#endif //MIDL_PASS
DWORDLONG QuadPart;
} ULARGE_INTEGER;
typedef ULARGE_INTEGER *PULARGE_INTEGER;
La verdad es que no saba que hacer con esto, salvo usar un tipo definido
con dos longs, pero antes de empezar a "probar", rebusqu en los CDs que
tengo del MSDN lo que haba sobre esta funcin, la verdad es que no
encontr demasiado, por no decir nada... salvo un comentario a cmo
poder usar el tipo Currency para valores grandes, ya que es casi un tipo
entero de 8 bits, slo que el resultado obtenido habr que multiplicarlo por
10000, ya que ese tipo de datos contiene un nmero con 4 decimales.
Prob y... funcion! y aqu est la declaracin a usar en Visual Basic:
Private Declare Function GetDiskFreeSpaceEx Lib "kernel32" Alias
"GetDiskFreeSpaceExA" _
(ByVal lpRootPathName As String, _

lpFreeBytesAvailableToCaller As Currency, _
lpTotalNumberOfBytes As Currency, _
lpTotalNumberOfFreeBytes As Currency) As Long
Para usarla, hay que multiplicar por 10000 el valor de los bytes devueltos.
Aqu tienes una funcin que devuelve el espacio total, as como el espacio
libre, todo ello en bytes:
En esta funcin el nombre del path puede estar representado por un
nombre UNC: \\Equipo\recurso\
Private Function EspacioLibreEx(ByVal lpRootPathName As String)
'lpRootPathName= Directorio raiz de la unidad a examinar
'Valores devueltos por la funcin:
Dim ret As Long
Dim lpFreeBytesAvailableToCaller As Currency
Dim lpTotalNumberOfBytes As Currency
Dim lpTotalNumberOfFreeBytes As Currency
'
Dim TotalBytes As Currency
Dim TotalFreeBytes As Currency
ret = GetDiskFreeSpaceEx(lpRootPathName, _
lpFreeBytesAvailableToCaller, _
lpTotalNumberOfBytes, _
lpTotalNumberOfFreeBytes)
TotalBytes = lpTotalNumberOfBytes * 10000
TotalFreeBytes = lpTotalNumberOfFreeBytes * 10000
EspacioLibreEx = Format(TotalBytes, "###,###,###") & " / "
&_
Format(TotalFreeBytes, "###,###,###")
End Function

2.- Scroll Bars Como habilitar, dehabilitar, ver y/o ocultar


Fecha: 5/May/98 (03/Mayo/1998)
Autor: Jose Montaner 'Satlite'

Este ejemplo muestra como manejar con el API, las barras de scroll en aquellos controles que
no disponen de la propiedad "ScrollBars"
Utiliza la funcin sBarVisible para ocultar/mostrar las barras de desplazamiento de un control, y
la funcin sBarEnabled para habilitarlas/dehabilitarlas.
El siguiente cdigo esta pensado para incluirse como mdulo BAS dentro un proyecto de VB 32
bits
Option Explicit
'Funciones para el manejo de las barras de desplazamiento
'en controloes que no dispones de la propiedad "ScrollBars"
'
'sBarFunciones.bas 2 de Mayo de 1.998 VB5
'Por Jose Montaner 'Satelite' casa2001@apdo.com
' Constantes de barra de desplazamiento
Public Enum eBar
Horizontal = 0
Vertical = 1
Automatico = 2
Ambas = 3
End Enum
Private Declare Function EnableScrollBar Lib "user32" _
(ByVal hwnd As Long, ByVal wSBflags As Long, ByVal wArrows As Long) As Long
Private Declare Function ShowScrollBar Lib "user32" _
(ByVal hwnd As Long, ByVal wBar As Long, ByVal bShow As Long) As Long

Public Sub sBarVisible(ByVal cntrl As Variant, ByVal barras As eBar, ByVal visible As
Boolean)
'Muestra/Oculta barras de desplazamiento
'cntrl: Control que deseamos atacar
'barras: Indica que barras queremos mostrar/ocultar
'Visiable: True/False
Dim ret As Long
ret = ShowScrollBar(cntrl.hwnd, barras, visible)
End Sub
Public Sub sBarEnabled(ByVal cntrl As Variant, ByVal barras As eBar, ByVal enabled
As Boolean)
'Habilita/Deshabilita
'cntrl: Control que deseamos atacar
'barras: Indica que barras queremos habilitar/deshabilitar
'Enabled: True/False
Dim ret As Long
ret = EnableScrollBar(cntrl.hwnd, barras, IIf(enabled, 0, &H3))
End Sub
3.- Generar un nmero nico para cada equipo...

Nota del 25/Dic/2003:


Segn parece este cdigo slo "era" vlido para Windows
95/98, en los Windows 2000/XP y creo que tambin en
Windows 98SE no funciona como aqu se explica... lo siento
si esto te hubiese servido para proteger un poco tus
programas.

De lo que se trata es de generar un nmero nico para, por ejemplo, usarlo


como forma de proteccin de nuestro programa, aunque aqu no se va a
decir cmo hacerlo, ya que entonces le estara dando pistas a posibles
"crackeadores" de mis programas "no gratuitos"...
El cdigo usado para conseguir este nmero nico es el mismo que usan la
mayora de las aplicaciones de Microsoft y otros...
Es la funcin que se usa para crear las claves esas que estn en el registro
de Windows (CLSID) y que el VB5 usa internamente para generar una clave
nica cuando creamos un nuevo componente ActiveX.
No te preocupes que no me voy a enrollar demasiado, al menos en el
aspecto tcnico, ya que no es plan de enrollarse...
La funcin que se encarga de crear ese nmero nico la he "encapsulado"
en una clase y se usa como cualquier objeto, es decir declaras el tipo y
llamas al mtodo que te interesa, que en este caso es una funcin que
devuelve el valor formateado al estilo de como lo vemos en el registro.
El valor devuelto es una cadena en la que, como mnimo, las ltimas 8
cifras es siempre la misma para cada equipo, por tanto usando esas 8 cifras
tienes identificado el equipo ya que siempre, lo he comprobado en unos 10
equipos, es diferente para cada equipo.
Nota: Aunque en un principio hagas varias pruebas y las 16 ltimas cifras
no cambien, por las pruebas que he hecho, slo las ocho ltimas son fijas, e
incluso las cuatro anteriores al ltimo nmero, que aunque tengan "letras",
son nmeros Hexadecimales.
Mas Notas: (25/Jun/98)
Si al equipo en cuestin se le agregan discos duros o se le cambia/aade/quita la
tarjeta de red u otras tarjetas, puede que el nmero vare... incluso esos que
parecen fijos. No lo he llegado a comprobar a fondo, pero es lo que he odo por ah.
Segn tengo entendido, si el equipo tiene una tarjeta de red, influye en esa
cifra, pero si no la tiene, tambin hace que sea nico; lo que quiero decir
con este comentario es que puede que ese nmero cambie si se cambia la
tarjeta de red o se instala en un equipo que no tenga. No todo iba a ser
perfecto...
Vamos a ver el cdigo de la clase y despus te pondr un ejemplo de cmo
usarla.
Nota: He dejado los comentarios originales en ingls, ya sabes que no me
gusta apuntarme puntos que no me corresponden...
El cdigo de la clase que genera los nmeros nicos:

'----------------------------------------------------------------'Clase para generar GUID


(20/Abr/98)
'
'Cdigo extraido de la Knowledge Base de Microsoft:
'HOWTO: Use CoCreateGUID API to Generate a GUID with VB
'Article ID: Q176790
'----------------------------------------------------------------Option Explicit
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Private Declare Function CoCreateGuid Lib "OLE32.DLL" _
(pGuid As GUID) As Long
Const S_OK = 0 ' return value from CoCreateGuid
Public Function GetGUID() As String
Dim lResult As Long
Dim lguid As GUID
Dim MyguidString As String
Dim MyGuidString1 As String
Dim MyGuidString2 As String
Dim MyGuidString3 As String
Dim DataLen As Integer
Dim StringLen As Integer
Dim i As Integer
On Error GoTo error_olemsg
lResult = CoCreateGuid(lguid)
If lResult = S_OK Then

MyGuidString1 = Hex$(lguid.Data1)
StringLen = Len(MyGuidString1)
DataLen = Len(lguid.Data1)
MyGuidString1 = LeadingZeros(2 * DataLen,
StringLen) _
& MyGuidString1 'First 4 bytes (8 hex

digits)

MyGuidString2 = Hex$(lguid.Data2)
StringLen = Len(MyGuidString2)
DataLen = Len(lguid.Data2)
MyGuidString2 = LeadingZeros(2 * DataLen,
StringLen) _
& Trim$(MyGuidString2) 'Next 2 bytes (4 hex

digits)

MyGuidString3 = Hex$(lguid.Data3)
StringLen = Len(MyGuidString3)
DataLen = Len(lguid.Data3)
MyGuidString3 = LeadingZeros(2 * DataLen,
StringLen) _
& Trim$(MyGuidString3) 'Next 2 bytes (4 hex

digits)

MyguidString = MyGuidString1 & "-" &


MyGuidString2 & "-" & _
MyGuidString3 & "-"
For i = 0 To 7
MyguidString = MyguidString & _
Format$(Hex$(lguid.Data4(i)), "00")
Next
'MyGuidString contains last 8 bytes of Guid (16
hex digits)
GetGUID = MyguidString
Else
GetGUID = "00000000-0000-0000-0000000000000000"
' return zeros if function unsuccessful
End If
Exit Function

error_olemsg:
MsgBox "Error " & Str(Err) & ": " & Error$(Err)
GetGUID = "00000000-0000-0000-0000000000000000"
Exit Function
End Function
Private Function LeadingZeros(ExpectedLen As Integer,
ActualLen As Integer) As String
LeadingZeros = String$(ExpectedLen - ActualLen, "0")
End Function
Cmo usar esta clase?
Para usar esta clase, hazlo de la siguiente forma:
Private Sub cmdGenGUID_Click()
'Creamos una instancia de la clase
Dim tGuid As New cGUID
'Asignamos el nmero generado, en este ejemplo lo
asignamos a un Label
Label2 = tGuid.GetGUID
'Destruimos la referencia al objeto
Set tGuid = Nothing
End Sub

4.- Posicionar un MsgBox


Navegando por el CD de Microsoft TechNet (tambin puedes navegar por Internet
en el sitio de Microsoft), me encontr con un artculo titulado: HOWTO: Position a
MsgBox Using a Windows Hook Procedure (PSS ID Number: Q180936), la verdad es
que pensaba que la posicin de los MsgBox era "fija", es decir siempre en el centro
de la pantalla, (salvo en algunos equipos que ocasionalmente suele mostrarse en
otras posiciones), pues bien, esto es posible hacerlo con el VB5, con la ayuda del
API y de AddressOf.
Vamos a ver cmo hacerlo.
El ejemplo, tomado y "arreglado" del artculo antes mencionado, mostrar un form
con dos botones, al pulsar en uno de ellos, se mostrar un MsgBox en la esquina
superior izquierda y al pulsar en el segundo botn, se mostrar en el centro del
formulario, independientemente de dnde est situado el form.

Para crear el proyecto de prueba:


Crea un nuevo proyecto, aade dos commandButtons y aade un mdulo BAS, ya
que el AddressOf necesita que las funciones o procedimientos estn en un mdulo
BAS.
Aade el siguiente cdigo al mdulo BAS:
Nota: He dejado los comentarios originales en ingls, ya que no necesitan
demasiada traduccin... espero...
'-----------------------------------------------------------------'Ejemplo para posicionar un MsgBox

(15/Jun/98)

'
'Microsoft TechNet Knowledge Base, PSS ID Number: Q180936
'HOWTO: Position a MsgBox Using a Windows Hook Procedure
'
'Guillermo 'guille Som, 1998
'-----------------------------------------------------------------Option Explicit
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Long) As Long
Public Declare Function GetWindowLong Lib "user32" Alias
"GetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long) As Long
Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Public Declare Function SetWindowsHookEx Lib "user32" Alias
"SetWindowsHookExA" _
(ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Public Declare Function SetWindowPos Lib "user32" _
(ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function GetWindowRect Lib "user32" _
(ByVal hwnd As Long, lpRect As RECT) As Long

Public Const GWL_HINSTANCE = (-6)


Public Const SWP_NOSIZE = &H1
Public Const SWP_NOZORDER = &H4
Public Const SWP_NOACTIVATE = &H10
Public Const HCBT_ACTIVATE = 5
Public Const WH_CBT = 5
Public hHook As Long

Function WinProc1(ByVal lMsg As Long, ByVal wParam As Long, ByVal


lParam As Long) As Long
If lMsg = HCBT_ACTIVATE Then
'Show the MsgBox at a fixed location (0,0)
SetWindowPos wParam, 0, 0, 0, 0, 0, _
SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
'Release the CBT hook
UnhookWindowsHookEx hHook
End If
WinProc1 = False
End Function

Function WinProc2(ByVal lMsg As Long, ByVal wParam As Long, ByVal


lParam As Long) As Long
Dim rectForm As RECT, rectMsg As RECT
Dim x As Long, y As Long
'On HCBT_ACTIVATE, show the MsgBox centered over Form1
If lMsg = HCBT_ACTIVATE Then
'Get the coordinates of the form and the message box so that
'you can determine where the center of the form is located
GetWindowRect Form1.hwnd, rectForm
GetWindowRect wParam, rectMsg
x = (rectForm.Left + (rectForm.Right - rectForm.Left) / 2) _

((rectMsg.Right - rectMsg.Left) / 2)
y = (rectForm.Top + (rectForm.Bottom - rectForm.Top) / 2) - _
((rectMsg.Bottom - rectMsg.Top) / 2)
'Position the msgbox
SetWindowPos wParam, 0, x, y, 0, 0, _
SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
'Release the CBT hook
UnhookWindowsHookEx hHook
End If
WinProc2 = False
End Function
Este es el cdigo que hay que aadir al formulario:
'
'-----------------------------------------------------------------'Ejemplo para posicionar un MsgBox

(15/Jun/98)

'
'Microsoft TechNet Knowledge Base, PSS ID Number: Q180936
'HOWTO: Position a MsgBox Using a Windows Hook Procedure
'
'Guillermo 'guille Som, 1998
'-----------------------------------------------------------------Option Explicit
Private Sub Command1_Click()
Dim hInst As Long
Dim Thread As Long
'Set up the CBT hook
hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc1, hInst,
Thread)
'Display the message box
MsgBox "This message box has been positioned at (0,0)."
End Sub
Private Sub Command2_Click()

Dim hInst As Long


Dim Thread As Long
'Set up the CBT hook
hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc2, hInst,
Thread)
'Display the message box
MsgBox "This message box is centered over Form1."
End Sub

5.- Cambiar la resolucin de la pantalla (y el nmero de colores) (25/Jun)


Esto es algo que lo tena desde hace tiempo pendiente de poner, pero que al recibir
una consulta sobre el tema, me hizo desempolvar la rutina (como ves an hay
gente atrevida que me hace consultas, espero que no cunda el ejemplo)
La cuestin es que he convertido lo que tena de la MSDN Library y lo he puesto en
un form para que sea operativo y permita seleccionar de una lista las resoluciones
disponibles, incluso los bits usados para el color, ya sabes: 8 bits son 256 colores.
El cdigo de ejemplo, as como las declaraciones de las funciones y tipos del API las
encontrars en este link.
Funciones del API usadas:
EnumDisplaySettings
ChangeDisplaySettings

6.- Subclasificando ventanas


Es que me he "picado" un poco con esto de interceptar los mensajes de
Windos y he empezado a "indagar" en este tema, que antes slo se poda
hacer con controles OCX especiales para esta tarea al estilo del MsgBlaster
y otros parecidos, pero que ahora es posible con cdigo de Visual Basic
gracias a AddressOf y al API de Windows, por supuesto.
Ni que decir tiene que slo con el VB5 y superior.

El ejemplo este que vamos a ver es para mostrar en un label un mensaje


cada vez que seleccionemos un item de un men.
Los valores y el "conocimiento" de los valores que hay que usar, los he
tomado de un ejemplo que la gente de Softcircuits tienen (o tenan) en sus
pginas y que acompaaban a un control OCX para "subclasificar" los
mensajes de Windows.
La parte importante de este ejemplo est en el mdulo bas, que lo he
hecho genrico para que se pueda usar con cualquier formulario y para los
mensajes que queramos, ya que esos mensajes se comprueban en el propio
formulario.
En los ejemplos que he visto por la red, normalmente los mensajes
recibidos se procesan en el procedimiento que "intercepta" los mensajes de
Windows, pero lo he ampliado para no tener que ir creando un mdulo BAS
para cada caso particular.
El nico requisito es tener un procedimiento pblico en el form que se
llame: miMSG y que reciba los parmetros para saber que es lo que
Windows nos quiere decir: uMSG, wParam y lParam.
Nota:
En el mdulo que uso en este ejemplo, (el primero que hago de
forma ms o menos genrica), slo se puede controlar un
formulario, es decir, si en el mismo proyecto tienes varios
formularios en los que quieres "interceptar" los mensajes recibidos
de Windows, con este mdulo BAS no lo puedes hacer, salvo que
dejes de interceptar en uno y lo hagas en otro, (ms abajo explico
cmo podras hacerlo), aunque ya estoy "por la labor" de crear un
mdulo genrico que permita manejar varios forms e incluso saber
que mensajes "quiere" procesar el formulario.
Pero esto ser en otra ocasin, ahora estoy "estudiando" el tema.

Ahora vamos a ver cmo funciona todo esto, aunque antes un par de
consejos y un poco de explicacin.
Antes de empezar a "recibir" o procesar los mensajes recibidos, hay que
indicarle a Windows que empiece la funcin, esto se hace mediante un
"gancho" (Hook que llaman los ingleses a esto); esto siempre hay que
hacerlo antes de nada y para indicarle a Windows que deje de estar
enganchados a nosotros, debemos quitar ese gancho.
Para estas dos tareas, el mdulo BAS tiene dos procedimientos: HookForm
y unHookForm, (ahora explicar cmo usarlos).
Lo importante de todo esto es que se debe llamar a unHookForm cuando no
necesitemos que Windows nos enve los mensajes. Si estamos probando en
el IDE es importantsimo quitar el gancho, sino el VB se quedar ms
colgado que... (busca tu un ejemplo, no lo voy a hacer yo todo...), as que
si te pones a experimentar con esto de la subclasificacin y esas cosas, es
recomendable que le des a guardar de continuo, sobre todo antes de darle
a F5 y tambin te recomendara que compilaras de forma completa, bien
pulsando Ctrl+F5 o bien modificando el modo de compilacin en el men
Herramientas/Opciones solapa General y en el cuadro Compilar quitar la
marca en Compilar bajo peticin, si tienes la versin inglesa del VB, ser:
Tools/Options/General/Compile on Demand
Con esto lo que consigues es que si hay alguna variable no declarada o algo
"raro", lo detecte antes de ejecutar la aplicacin.

Ahora te voy a explicar un poco cmo funcionan los procedimientos


HookForm y unHookForm que he incluido en el mdulo BAS.
El HookForm recibe un form como parmetro, esto lo hago para tener una
referencia al form de llamada, con idea de que se pueda llamar como te de
la gana y no tener que forzarte a nada en particular, salvo la de crear un
procedimiento pblico que se llame miMSG, tal como he explicado antes.
En este procedimiento se comprueba si antes se ha asignado el valor de la
variable que "apunta" al form, de ser as, se llama a unHookForm con idea
de quitar el "gancho" anterior, esto lo he puesto por si quisieras tener varios
forms subclasificados, bueno realmente no podras, ya que slo se permite
uno a la vez, si quisieras tener varios, tendras que crear varios
procedimientos Hook y unHook y otros tantos para "interceptar" los
mensajes de Windows, pero por simplicidad vamos a dar por hecho de que
slo se subclasifica un form a la vez. Y a pesar de que esto se de por hecho,
el procedimiento se encarga de comprobar de que as sea.
Una vez que no necesitemos recibir los mensajes de Windows, debemos
llamar a unHookForm, esto se suele hacer al cerrar el formulario, lo he
puesto en el QueryUnload para que no haya ms problemas de los que
pudiera haber.
En el procedimiento miMSG del form es donde debemos "evaluar" que
mensajes de los recibidos queremos "procesar", en el ejemplo de hoy es
WM_MENUSELECT, en otras ocasiones iremos viendo otros, todo depender
de la informacin que pueda "captar" por la red o por los artculos del
MSDN, aunque si no quieres esperar, puedes darte una vueltecilla por la
MSDN Library que est en la red y buscar artculos referentes a este tema.
Vamos a ver los listados y a pasar a la accin.
En primer lugar el del mdulo BAS:
'----------------------------------------------------------------'Mdulo para subclasificacin (subclassing)
(26/Jun/98)
'
'
'Guillermo 'guille' Som, 1998
'----------------------------------------------------------------Option Explicit
'Para almacenar el form de llamada y el hWnd del form
Private elForm As Form
Private elhWnd As Long
Public PrevWndProc As Long
Public Const GWL_WNDPROC As Long = (-4&)

Public Declare Function CallWindowProc Lib "user32"


Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, _
ByVal MSG As Long, ByVal wParam As Long, ByVal
lParam As Long) As Long
Public Declare Function SetWindowLong Lib "user32" Alias
"SetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long

Public Function WndProc(ByVal hWnd As Long, ByVal uMSG


As Long, ByVal wParam As Long, ByVal lParam As Long) As
Long
WndProc = CallWindowProc(PrevWndProc, hWnd, uMSG,
wParam, lParam)
'Los mensajes de Windows llegarn aqu
'Lo que hay que hacer es "capturar" los que se
necesiten,
'en este caso se devuelven los mensajes al form,
usando para
'ello un procedimiento pblico llamado miMSG con los
'siguientes parmetros:
'ByVal uMSG As Long, ByVal wParam As Long, ByVal
lParam As Long
'la copia del form se har al crear el Hook, es
importante que
'slo se subclasifiquen ventanas cuando no halla
ninguna activa
'(de esto se encarga HookForm y unHookForm)
'
'Nos aseguramos que el form an est disponible
If Not elForm Is Nothing Then
elForm.miMSG uMSG, wParam, lParam
End If
End Function
Public Sub HookForm(ByVal unForm As Form)
'unForm ser el form de llamada,
'para llamar a este procedimiento: HookForm Me
'

'Si an exista una subclasificacin


If Not elForm Is Nothing Then
unHookForm
End If
Set elForm = unForm
elhWnd = unForm.hWnd
PrevWndProc = SetWindowLong(elhWnd, GWL_WNDPROC,
AddressOf WndProc)
'Es importante recordar que se debe llamar a
unHookForm antes
'de cerrar el form... sobre todo si se usa en el IDE
End Sub
Public Sub unHookForm()
Dim Ret As Long
'Para llamar a este procedimiento: unHookForm
'
'Siempre se debe llamar primero a HookForm y despus
se llama
'a este otro para dejar de interceptar los mensajes
de Windows
'Si haces pruebas en el IDE, no te olvides de llamar
a este
'procedimiento, cerrando la aplicacin con el botn
"Stop"
'no se llamar a este procedimiento.
'
'Si el valor de elhWnd es cero es que no se ha usado
If elhWnd <> 0 Then
Ret = SetWindowLong(elhWnd, GWL_WNDPROC,
PrevWndProc)
End If
'Quitamos la referencia al form
Set elForm = Nothing
'Asignamos el valor cero a elhWnd
elhWnd = 0
End Sub
Ahora vamos a ver el cdigo usado en el Form:
El form, adems de los mens indicados (ver los Case xx de miMSG),
tendr un Label para mostrar el mensaje de la seleccin realizada.

'----------------------------------------------------------------'Prueba de subclasificacin
(26/Jun/98)
'Se comprobarn los mensajes enviados por cambio en la
seleccin
'de los mens
'
'Guillermo 'guille' Som, 1998
'----------------------------------------------------------------Option Explicit
'Este es el mensaje enviado por Windows cuando se
selecciona
'un men
Const WM_MENUSELECT As Long = &H11F&
'Para el men del sistema
Const SC_RESTORE

As Long = &HF120&

Const SC_MOVE

As Long = &HF010&

Const SC_SIZE

As Long = &HF000&

Const SC_MINIMIZE As Long = &HF020&


Const SC_MAXIMIZE As Long = &HF030&
Const SC_CLOSE

As Long = &HF060&

Private Sub Form_Load()


'Iniciar la subclasificacin
HookForm Me
End Sub
Private Sub Form_QueryUnload(Cancel As Integer,
UnloadMode As Integer)
'Terminar la subclasificacin
unHookForm
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set Form1 = Nothing
End Sub

Public Sub miMSG(ByVal uMSG As Long, ByVal wParam As


Long, ByVal lParam As Long)
Dim sMsg As String
'Aqu llegarn los mensajes que se quieren
interceptar
'usar con Select Case o varios If... ElseIf...
If uMSG = WM_MENUSELECT Then
Select Case wParam And &HFFFF&
'Estos son los valores definidos por VB
'Siempre empiezan por 1
'Case 1
'

sMsg = "El men de ficheros"

Case 2
sMsg = "Has seleccionado Abrir..."
Case 3
sMsg = "Has seleccionado Guardar"
Case 4
sMsg = "Has seleccionado Guardar

como..."

Case 6
sMsg = "Has seleccionado Imprimir..."
Case 8
sMsg = "Has seleccionado Salir"
'
'Case 9
'

sMsg = "El men de edicin"

Case 10
sMsg = "Has seleccionado Deshacer"
Case 12
sMsg = "Has seleccionado Copiar"
Case 13
sMsg = "Has seleccionado Cortar"
Case 14
sMsg = "Has seleccionado Pegar"
Case 16
todo..."

sMsg = "Has seleccionado Seleccionar


'Estos corresponden al men del sistema
Case SC_RESTORE

sMsg = "Restaurar la ventana"


Case SC_MOVE
sMsg = "Mover la ventana con el teclado"
Case SC_SIZE
sMsg = "Cambiar el tamao de la ventana
con el teclado"
Case SC_MINIMIZE
sMsg = "Minimizar la ventana"
Case SC_MAXIMIZE
sMsg = "Maximizar la ventana"
Case SC_CLOSE
sMsg = "Cerrar esta ventana y de paso el

programa"

Case Else
sMsg = ""
End Select
'Mostrar el mensaje en el Label
Label1 = sMsg
End If
End Sub

7.- Saber el directorio de Windows


La funcin GetWindowsDirectory devuelve el directorio de Windows:
Declare Function GetWindowsDirectory Lib "Kernel32" Alias
"GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
Dim WinDir As String
Dim Cadena As String
Dim ret As Long
Cadena = String$(300, Chr$(0))
ret = GetWindowsDirectory(Cadena, Len(Cadena))
WinDir = Left$(Cadena, ret)
'Esta sera la forma "lgica" de obtener
el valor
'Pero podemos "rizar el rizo" y hacerlo de esta otra:
WinDir = Left$(Cadena, Instr(Cadena, Chr$(0)) - 1)
Fijate en que he puesto dos formas de asignar a WinDir el directorio de Windows.
La primera es la que se debera hacer, ya que el valor devuelto por la funcin
GetWindowsDirectory es la longitud de la cadena resultante.
Mientras que la segunda es una forma genrica de obtener las cadenas cuando es
una funcin escrita en C la que la devuelve. Y es por la razn de que las cadenas en
C, normalmente, siguen el formato ASCIIZ, es decir una cadena acabada en el
carcter de cdigo ASCII 0 (cero).

Lo que quiero decir con todo este rollo, es que debemos ser generosos con la
longitud de las cadenas pasadas a funciones del API, si sabemos que son directorios
o archivos los valores que van a devolver, con una longitud de 260, sera ms que
suficiente, ya que este valor es el mximo soportado por los nombres largo. Este
valor, est definido en la constante MAX_PATH del SDK del API de Windows.
Por cierto en el fichero Win32API.TXT est mal, ya que indica que son 32
caracteres, cualdo deberan ser 260.

8.- Seleccionar un directorio, usando SHBrowseForFolder (4/Jul)


Una funcin del API para seleccionar slo un directorio.
En la pgina de los controles ActiveX tienes un control (y el cdigo) del
"selector de directorios" que yo me fabriqu y que, aunque no es
estndard, al menos me gusta ms que este)
Aqu tienes las declaraciones (las puedes usar en un mdulo BAS o en un
formulario)
'
Const MAX_PATH = 255
Private Enum eBIF
BIF_RETURNONLYFSDIRS = &H1
sistema

'Slo directorios del

BIF_DONTGOBELOWDOMAIN = &H2
de red

'No incluir carpetas

BIF_STATUSTEXT = &H4
BIF_RETURNFSANCESTORS = &H8
BIF_BROWSEFORCOMPUTER = &H1000
BIF_BROWSEFORPRINTER = &H2000

'Buscar PCs
'Buscar impresoras

End Enum
Private Type BrowseInfo
hwndOwner
pIDLRoot
empezar a mostrar
pszDisplayName

As Long
As Long
As Long

lpszTitle

As Long

ulFlags

As Long

lpfnCallback

As Long

lParam

As Long

iImage

As Long

End Type

'Especifica dnde se

Private Declare Function SHBrowseForFolder Lib "shell32.dll" _


(lpbi As BrowseInfo) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" _
(ByVal hMem As Long)
Private Declare Function lstrcat Lib "kernel32.dll" Alias "lstrcatA" _
(ByVal lpString1 As String, ByVal lpString2 As String) As Long
Private Declare Function SHGetPathFromIDList Lib "shell32.dll" _
(ByVal pidList As Long, ByVal lpBuffer As String) As Long

'Si se quiere usar en un form, cambiar el public por private


Public Function BrowseForFolder(ByVal hwndOwner As Long, ByVal
sPrompt As String, Optional ByVal vFlags As eBIF) As String
'
Dim iNull As Integer
Dim lpIDList As Long
Dim lResult As Long
Dim sPath As String
Dim udtBI As BrowseInfo
Dim lFlags As Long
If Not IsMissing(vFlags) Then
lFlags = CInt(vFlags)
End If
With udtBI
.hwndOwner = hwndOwner
.lpszTitle = lstrcat(sPrompt, "")
.ulFlags = lFlags Or BIF_RETURNONLYFSDIRS
End With
lpIDList = SHBrowseForFolder(udtBI)
If lpIDList Then
sPath = String$(MAX_PATH, 0)

lResult = SHGetPathFromIDList(lpIDList, sPath)


Call CoTaskMemFree(lpIDList)
iNull = InStr(sPath, vbNullChar)
If iNull Then
sPath = Left$(sPath, iNull - 1)
End If
Else
'Se ha pulsado en cancelar
sPath = ""
End If
BrowseForFolder = sPath
End Function
Para usarlo:
'Para usarlo desde un Form:
Label1 = BrowseForFolder(Me.hWnd, "Selecciona un directorio")

9.- Deshabilitar los botones (y el men system) de un form


Normal o MDI (10/Jul)
Pues eso, ya que hay gente que se pregunta el cmo y aqu tienes la
respuesta.
En el cdigo de ejemplo se incluye la forma de quitar TODAS las opciones,
incluyendo maximizar, restaurar, etc. y tambin cmo quitar slo lo que te
interesa.
Tambin incluyo el cdigo para el VB4 de 16 bits.
Este cdigo vale tanto para forms normales como para MDI-Form, adems
si quitas el men "Mover", no podrs mover el formulario de sitio.
Hay ms constantes, que puedes encontrar en el fichero de las
declaraciones del API que se incluyen con el VB.
En la seccin de las declaraciones del MDI-Form escribe lo siguiente:
'-----------------------------------------------------------------'Prueba para quitar opciones del men System de un MDIForm
'
'Para VB4:
'

(27/Oct/97)

'Guillermo 'guille' Som, 1997-98


'-----------------------------------------------------------------Option Explicit
#If Win32 Then
'Para 32 bits (VB4 y VB5)
Private Declare Function GetSystemMenu Lib "user32" _
(ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function DeleteMenu Lib "user32" _
(ByVal hMenu As Long, ByVal nPosition As Long, _
ByVal wFlags As Long) As Long
#Else
'Para 16 bits (VB4 y VB3)
Private Declare Function GetSystemMenu Lib "user" (ByVal hWnd
%, ByVal bRevert%) As Integer
Private Declare Function DeleteMenu Lib "user" (ByVal hMenu%,
ByVal iditem%, ByVal wFlags%) As Integer
#End If
'Constantes
Const SC_SIZE = &HF000
Const SC_MOVE = &HF010
Const SC_MINIMIZE = &HF020
Const SC_MAXIMIZE = &HF030
Const SC_CLOSE = &HF060
Const SC_RESTORE = &HF120
Const MF_SEPARATOR = &H800
Const MF_BYPOSITION = &H400
Const MF_BYCOMMAND = &H0

Private Sub MDIForm_Load()


#If Win32 Then
Dim hWnd&, hMenu&, Success&

#Else
Dim hWnd%, hMenu%, Success%
#End If
Dim i%
hWnd = Me.hWnd
hMenu = GetSystemMenu(hWnd, 0)
'Quitar todos (va de 0 a 8)
For i = 8 To 0 Step -1
Success = DeleteMenu(hMenu, i, MF_BYPOSITION)
Next
Exit Sub
'Usa esto para quitar los mens que te interesen:
Success = DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND)
'Success = DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND)
Success = DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND)
Success = DeleteMenu(hMenu, SC_MINIMIZE,
MF_BYCOMMAND)
'Success = DeleteMenu(hMenu, SC_MAXIMIZE,
MF_BYCOMMAND)
'Success = DeleteMenu(hMenu, SC_RESTORE,
MF_BYCOMMAND)
End Sub

10.- Clase para obtener los distintos directorios del Sistema


cOSFolders
Con esta clase se pueden obtener los distintos directorios de Windows, adems de los
"tradicionales" como el directorio de Windows y System, se puede saber el directorio de
Archivos de Programa, la localizacin del Escritorio, Men de Inicio, etc.
Las claves que se usan para obtener esta informacin se encuentra en el registro del Sistema.
Estas claves son:
HKEY_USERS\.Default\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell
Folders
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion
La forma de obtener los distintos directorios es usando el mtodo (o funcin) GetFolder de
esta clase; a este mtodo hay que pasarle un parmetro que ser bien un nmero (ndice

dentro de una coleccin con los valores) o bien una cadena con el nombre del directorio que
queremos; los valores de estas cadenas son los nombres usados en el registro de Windows
para nombrarlos.
Por ejemplo para obtener el directorio en el que est el Men de Inicio habr que usar como
"cadena" Start Menu.
En el form de prueba, se usa un combo para contener los diferentes valores que se pueden
usar para conseguir ese directorio.
Adems de obtener los nombres de los directorios, esta clase tiene una funcin/mtodo que
devuelve un valor de una entrada del registro, (del tipo cadena, DWORD o Binary), no voy a
explicar cmo usarla porque estoy preparando otra clase ms genrica para manipular los
datos del registro, por ahora slo tengo la parte de obtener los valores, pero si todo va bien,
tambin se podrn crear claves y (espero) cambiar los valores de las claves.
Aqu tienes los listados del form de prueba y los de la clase. Espero que te sea til.
Si quieres los listados, aqu los tienes: (OSFolders.zip 5.55 KB)

El formulario:
'-----------------------------------------------------------------'

(13/Ago/98)

'Prueba de uso de la clase para devolver los directorios del S.O.


'
'Guillermo 'guille' Som, 1998
'-----------------------------------------------------------------Option Explicit
Dim tOSF As cOSFolders

Private Sub cmdMostrar_Click()


Dim sKey As String
Dim sValue As String
Dim i As Long
Dim sData As String
Label1(2) = ""
i = Combo1.ListIndex
sKey = Combo1.List(i)
sData = tOSF.GetFolder(sKey)
Label1(2) = sData

End Sub
Private Sub Combo1_Click()
cmdMostrar_Click
End Sub
Private Sub Form_Load()
Set tOSF = New cOSFolders
Dim col As New Collection
Dim i As Long
'Devolver slo las claves de los directorios
Set col = tOSF.ShellFolders(bSoloClaves:=True)
With Combo1
.Clear
For i = 1 To col.Count
.AddItem col(i)
Next
If .ListCount Then
.ListIndex = 0
End If
End With
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set tOSF = Nothing
End Sub

La Clase:
'
'-----------------------------------------------------------------'cOSFolders

(13/Ago/98)

'Clase para obtener valores del Registro del Sistema


'(versin reducida pra obtener los directorios del sistema)
'
'La informacin para crear las funciones estn tomadas de ejemplos
'y valores obtenidos en el cdigo del Setup1.vbp
'y de artculos incluidos en los CDs del MSDN Library.
'
'=== De algn sitio tena que sacar la informacin... ===

'
'Guillermo 'guille' Som, 1998
'email: mensaje@elguille.info / mensaje@elguille.info
'
'Funciones/Mtodos de la clase:
'

GetReg

devolver el valor de la clave indicada

'

GetFolder

devuelve el path del nombre-clave indicado

'

ShellFolders

devuelve una coleccin con los nombres de

'

los directorios disponibles (claves)

'-----------------------------------------------------------------Option Explicit
Dim colShellFolders As Collection
Dim colShellFoldersKey As Collection
Private Declare Function GetWindowsDirectory Lib "kernel32" Alias
"GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
Private Declare Function GetSystemDirectory Lib "kernel32" Alias
"GetSystemDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
' Registry manipulation API's (32-bit)
'Claves del Registro
Public Enum eHKEY
HKEY_CLASSES_ROOT = &H80000000
HKEY_CURRENT_USER = &H80000001
HKEY_LOCAL_MACHINE = &H80000002
HKEY_USERS = &H80000003
End Enum
'
Private Enum eHKEYError
ERROR_SUCCESS = 0

'Todo correcto, sin error

ERROR_MORE_DATA = 234&

'More data is available

ERROR_NO_MORE_ITEMS = 259&

'No more data is available

End Enum
'
'Los tipos de datos posibles
Private Enum eHKEYDataType

REG_NONE = 0&

'No value type

REG_SZ = 1&

'Unicode null terminated string

REG_BINARY = 3

'Free form binary

REG_DWORD = 4

'32-bit number

End Enum
'
Private Enum eREGSAM
KEY_QUERY_VALUE = &H1
End Enum
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Declare Function RegQueryInfoKey Lib "advapi32.dll" Alias
"RegQueryInfoKeyA" _
_

(ByVal hKey As Long, ByVal lpClass As String, lpcbClass As Long,

lpReserved As Long, lpcSubKeys As Long, lpcbMaxSubKeyLen As


Long, _
lpcbMaxClassLen As Long, lpcValues As Long, lpcbMaxValueNameLen
As Long, _
lpcbMaxValueLen As Long, lpcbSecurityDescriptor As Long, _
lpftLastWriteTime As FILETIME) As Long
Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias
"RegOpenKeyExA" _
(ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal ulOptions As Long, ByVal samDesired As Long, _
phkResult As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll" _
(ByVal hKey As Long) As Long
Private Declare Function RegEnumValue Lib "advapi32.dll" Alias
"RegEnumValueA" _
(ByVal hKey As Long, ByVal dwIndex As Long, _
ByVal lpValueName As String, lpcbValueName As Long, _
lpReserved As Long, lpType As Long, lpData As Any, _
lpcbData As Long) As Long
Private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias
"RegQueryValueExA" _
(ByVal hKey As Long, ByVal lpszValueName As String, _
ByVal dwReserved As Long, lpdwType As Long, _

lpbData As Any, cbData As Long) As Long

Public Function GetReg(ByVal sKey As String, Optional ByVal sValue


As String = "", Optional ByVal hKey As eHKEY = HKEY_CURRENT_USER,
Optional ByVal bAsString As Boolean = False) As Variant
'Obtener un valor de una entrada del registro
'
'Parmetros de entrada:
'

sKey

SubClave del registro

'

Se puede especificar el nombre de la clave raiz

'

que se convertir al valor adecuado

'

sValue

Nombre de la entrada que queremos obtener

'

hKey

Clave principal del registro

'

bAsString

Mostrarlo como una cadena, al estilo de RegEdit

'Devuelve:
'

el contenido de esa clave o un valor vaco

'
Dim ret As Long
Dim hKey2 As Long
Dim rDT As eHKEYDataType
Dim retDT As eHKEYDataType
Dim lSize As Long
Dim sData As String
Dim aData() As Byte
Dim lDWord As Long
Dim i As Long
Dim sTmp As String
'Nos aseguramos que hKey tenga el valor correcto
Select Case hKey
Case HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE,
HKEY_USERS
'nada que hacer, todo correcto
Case Else
'Asignamos el valor por defecto
hKey = HKEY_CLASSES_ROOT
End Select
hKey = ParseKey(sKey, hKey)

'Valores por defecto


ReDim aData(0)
lDWord = 0
sData = ""
'Abrir la clave indicada
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_QUERY_VALUE, hKey2)
'Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
'Leer esa entrada y obtener el tipo de dato, longitud, etc.
ret = RegQueryValueEx(hKey2, sValue, 0&, retDT, 0&, lSize)
'Si es un valor binario
If retDT = REG_BINARY Then
If lSize Then
ReDim aData(lSize)
'Leer los datos binarios
ret = RegQueryValueEx(hKey2, sValue, 0&, rDT,
aData(0), lSize)
End If
ElseIf retDT = REG_DWORD Then
ret = RegQueryValueEx(hKey2, sValue, 0&, rDT, lDWord,
lSize)
ElseIf retDT = REG_SZ Then
If lSize Then
sData = String$(lSize - 1, Chr$(0))
'Leer la cadena
'(el ByVal es porque est declarada como Any)---v
sData, lSize)

ret = RegQueryValueEx(hKey2, sValue, 0&, rDT, ByVal

End If
End If
'Cerrar la clave abierta
RegCloseKey hKey2
End If
'Devolver el valor ledo
If retDT = REG_BINARY Then
If bAsString Then
'Al estilo de como se muestra con RegEdit

For i = 0 To UBound(aData) - 1
sTmp = sTmp & Hex$(aData(i)) & " "
Next
GetReg = sTmp
Else
GetReg = aData
End If
ElseIf retDT = REG_DWORD Then
If bAsString Then
'Al estilo de como se muestra con RegEdit
GetReg = "0x" & Format$(Hex$(lDWord), "00000000") & " ("
& lDWord & ")"
Else
GetReg = lDWord
End If
ElseIf retDT = REG_SZ Then
GetReg = sData
End If
End Function
Private Function ParseKey(sKey As String, Optional ByVal hKey As
eHKEY = HKEY_CURRENT_USER) As eHKEY
Dim i As Long
Dim sRootKey As String
'Comprobar si se indica la clave principal en sKey
i = InStr(sKey, "HKEY_")
If i Then
i = InStr(sKey, "\")
If i Then
sRootKey = Left$(sKey, i - 1)
sKey = Mid$(sKey, i + 1)
Else
sRootKey = sKey
sKey = ""
End If
If Len(sRootKey) Then
Select Case sRootKey
Case "HKEY_CLASSES_ROOT"
hKey = HKEY_CLASSES_ROOT

Case "HKEY_CURRENT_USER"
hKey = HKEY_CURRENT_USER
Case "HKEY_LOCAL_MACHINE"
hKey = HKEY_LOCAL_MACHINE
Case "HKEY_USERS"
hKey = HKEY_USERS
Case Else
hKey = HKEY_CLASSES_ROOT
End Select
End If
End If
ParseKey = hKey
End Function
Private Function EnumValueString(ByVal hKey As Long, ByVal dwIndex
As Long, _
lpValueName As String, lpcbValueName As Long, _
lpReserved As Long, lpType As Long, lpData As String, _
lpcbData As Long) As Long
EnumValueString = RegEnumValue(hKey, dwIndex, _
lpValueName, lpcbValueName, _
lpReserved, lpType, ByVal lpData, _
lpcbData)
End Function
Private Function EnumValue(ByVal hKey As Long, ByVal dwIndex As
Long, _
lpValueName As String, lpcbValueName As Long, _
lpReserved As Long, lpType As Long, lpData As Byte, _
lpcbData As Long) As Long
EnumValue = RegEnumValue(hKey, dwIndex, _
lpValueName, lpcbValueName, _
lpReserved, lpType, lpData, _
lpcbData)
End Function

Private Function QueryInfoKey(ByVal hKey As Long,


lpcbMaxValueNameLen As Long) As Long
Dim lpftLastWriteTime As FILETIME
QueryInfoKey = RegQueryInfoKey(hKey, 0&, 0&, 0&, 0&, 0&, 0&, 0&,
_
lpcbMaxValueNameLen, 0&, 0&, lpftLastWriteTime)
End Function
Public Function ShellFolders(Optional bSoloClaves As Boolean =
False) As Variant
'Devolver las claves de la clave Shell Folders
Dim hKey As eHKEY
Dim Entry As String
Dim phkResult As Long
Dim maxBufLen As Long
Dim L As Long
Dim buf As String
Dim buflen As Long
Dim lRet As Long
Dim retDT As eHKEYDataType
Dim i As Long
Dim sValue As String
Dim iCount As Long
'Borrar el contenido de la coleccin
Set colShellFolders = Nothing
Set colShellFolders = New Collection
Set colShellFoldersKey = Nothing
Set colShellFoldersKey = New Collection
'==============================================================
'
'=== NOTA CACHONDA === por lo incomprensible...
' Es curioso, pero si utilizo estas intrucciones aqu
' el bucle For iCount=0 to 1 no acaba nunca
'
'==============================================================
'
'Para el directorio de windows

'buf = "WindowsDir"
'colShellFoldersKey.Add buf, buf
'colShellFolders.Add "Windows", buf
'
'Para el directorio de System
'buf = "SystemDir"
'colShellFoldersKey.Add buf, buf
'colShellFolders.Add "System", buf
'
'==============================================================
For iCount = 0 To 1
'Enumerar el contenido de Shell Folders
If iCount = 0 Then
hKey = HKEY_USERS
Entry =
".Default\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell
Folders"
Else
hKey = HKEY_LOCAL_MACHINE
Entry = "Software\Microsoft\Windows\CurrentVersion"
End If
If RegOpenKeyEx(hKey, Entry, 0&, KEY_QUERY_VALUE, phkResult)
= ERROR_SUCCESS Then
lRet = QueryInfoKey(phkResult, maxBufLen)
L = -1
Do
L = L + 1
buf = String$(maxBufLen + 1, 0)
buflen = Len(buf)
'Para enumerar los valores y las claves
retDT, 0&, i)

lRet = EnumValue(phkResult, L, buf, buflen, 0&,


If retDT = REG_SZ Then
sValue = String$(i - 1, 0)
buf = String$(maxBufLen + 1, 0)
buflen = Len(buf)

lRet = EnumValueString(phkResult, L, buf,


buflen, 0&, retDT, sValue, i)
buf = Left$(buf, buflen)
If InStr(buf, Chr$(0)) Then

buflen = InStr(buf, Chr$(0)) - 1


buf = Left$(buf, buflen)
End If
If Len(buf) Then
If iCount = 0 Then
colShellFoldersKey.Add buf, buf
colShellFolders.Add "HKEY_USERS\" &

Entry, buf
Else

If InStr(sValue, ":\") Then


colShellFoldersKey.Add buf, buf
colShellFolders.Add
"HKEY_LOCAL_MACHINE\" & Entry, buf
End If
End If
End If
End If
If lRet = ERROR_NO_MORE_ITEMS Then
Exit Do
End If
Loop
lRet = RegCloseKey(phkResult)
End If
Next
'Obtener el directorio de windows
buf = String$(300, Chr$(0))
lRet = GetWindowsDirectory(buf, Len(buf))
sValue = Left$(buf, lRet)
buf = "WindowsDir"
colShellFoldersKey.Add buf, buf
colShellFolders.Add sValue, buf
'Obtener el directorio de System
buf = String$(300, Chr$(0))
lRet = GetSystemDirectory(buf, Len(buf))
sValue = Left$(buf, lRet)
buf = "SystemDir"
colShellFoldersKey.Add buf, buf

colShellFolders.Add sValue, buf


If bSoloClaves Then
Set ShellFolders = colShellFoldersKey
Else
Set ShellFolders = colShellFolders
End If
End Function
Private Sub Class_Initialize()
Set colShellFolders = New Collection
Set colShellFoldersKey = New Collection
End Sub
Private Sub Class_Terminate()
Set colShellFolders = Nothing
Set colShellFoldersKey = Nothing
End Sub
Public Function GetFolder(ByVal vIndex As Variant) As String
'Devuelve el directorio de la clave indicada
Dim sKey As String
Dim sData As String
Dim lRet As Long
If colShellFolders.Count = 0 Then
Call ShellFolders
End If
On Local Error Resume Next
sKey = colShellFolders(vIndex)
sData = colShellFoldersKey(vIndex)
If sData = "WindowsDir" Then
'Devolver el directorio de windows
GetFolder = sKey
ElseIf sData = "SystemDir" Then
'Devolver el directorio de System
GetFolder = sKey

Else
GetFolder = GetReg(sKey, sData)
End If
If Err Then
GetFolder = ""
End If
Err = 0
On Local Error GoTo 0
End Function

11.- Una API para saber los directorios del Sistema (20/Ago)
Siguiendo con la racha de los ltimos das de saber dnde estn los
distintos directorios del sistema, no slo Windows ni el System, que esos ya
tienen sus correspondientes funciones del API, sino para otros directorios
como el de los Archivos de programa, el Men de Inicio, el Escritorio, los
Cookies, etc.
Esta funcin no la he visto documentada para usarla en Visual Basic, pero
gracias a Francisco Charte que respondiendo en las news dio su direccin de
internet (La Torre de Babel) en la que haba un pequeo ejemplo para
Delphi, pues me "calent" y la convert en VB y aqu est, con un pequeo
ejemplo en el que se dan todas las constantes para poder usarla con esta
funcin para obtener los distintos directorios.
En este "trozo" de espacio de esta pgina slo dar algunas de las
constantes y la forma general de obtener los directorios, pero en el ejemplo
estn todas las constantes que he encontrado en la documentacin de
Microsoft.
Si quieres los listados con el form de ejemplo, pulsa este link.
(SHGetSFP.zip 2.65 KB)
'La longitud mxima de un directorio puede ser 260
Const MAX_PATH = 260
'Algunas de las constantes
Const CSIDL_DESKTOP = 0
Const CSIDL_PROGRAMS = 2
Const CSIDL_STARTUP = 7
Const CSIDL_STARTMENU = 11
'La declaracin del API:

Private Declare Function SHGetSpecialFolderPath Lib "shell32.dll"


Alias "SHGetSpecialFolderPathA" _
(ByVal hWnd As Long, ByVal sPath As String, _
ByVal Folder As Long, ByVal Create As Long) As Long

'Para usarla: (en este ejemplo se mostrar el path del directorio de


programas del men de inicio)
Dim sPath As String
sPath = String$(MAX_PATH + 1, 0)
Call SHGetSpecialFolderPath(Me.hWnd, sPath,
CSIDL_PROGRAMS, False)
'Quitarle el CHR$(0) del final
sPath = Left$(sPath, InStr(sPath, Chr$(0)) - 1)
MsgBox "El directorio de los Archivos de programa est en: " &
vbCrLf & sPath
Nota del 11/May/2001:
Si quieres saber otros directorios, por ejemplo Archivos de programa,
Windows, etc. puedes usar la clase cQueryReg. Por ejemplo para saber el
directorio de Archivos de programa, tendras que hacer algo as:

'
Dim oQR As cQueryReg
Set oQR = New cQueryReg
Label1.Caption = oQR.GetFolder("ProgramFilesDir")
Que lo disfrutes!

12.- Saber si un form se muestra Modal o Normal (28/Ago)


Esto est sacado de un artculo para VB de 16 bits de la Knowledge Base de
Microsoft, la adaptacin ha sido fcil, ya que slo he tenido que cambiar la
declaracin del API.
El truco consiste en saber si el formulario que muestra modal o no modal a
un segundo formulario est habilitado o no.
Ya sabes que cuando se muestra un form de forma modal, los dems
formularios de la aplicacin estn deshabilitados hasta que se oculte o

cierre el formulario modal, por tanto se usa GetWindowLong y se


comprueba si el estilo del primer form est deshabilitado o no.
Veamos el cdigo de ejemplo.
Crea un proyecto con 2 forms, en el Form1, aade un botn (Command1) y
lo mismo en el Form2.
'FORM1
Option Explicit
Private Sub Command1_Click()
' Flip between "Modeless" and "Modal" display states.
Static ShowStyle As Long
Unload Form2
Form2.Show ShowStyle
ShowStyle = (ShowStyle + 1) Mod 2
If ShowStyle Then
Command1.Caption = "Mostrar Form2: Modal"
Else
Command1.Caption = "Mostrar Form2: No Modal"
End If
End Sub

Private Sub Form_Load()


Command1.Caption = "Mostrar Form2: No Modal"
End Sub
'FOM2
'
'How to Determine Display State of a VB Form, Modal or Modeless
'PSS ID Number: Q77316
'
Option Explicit
Const GWL_STYLE = (-16)
Const WS_DISABLED = &H8000000
'$Aadido por Guillermo 'guille' Som, 28/Ago/1998

Private Declare Function GetWindowLong Lib "user32" Alias


"GetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Sub Command1_Click()
Unload Me
End Sub
Private Sub Form_Paint()
Dim WinStyle As Long
' Get the Window Style for Form1.
WinStyle = GetWindowLong(Form1.hWnd, GWL_STYLE)
If WinStyle And WS_DISABLED Then
' The WS_DISABLED style is set on "FORM1" when "FORM2"
' is displayed with the Modal flag (Show 1).
Caption = "Se muestra: Modal"
Else
' The WS_DISABLED style is not set on "FORM1" when
"FORM2"
' is displayed with the Modeless flag (Show or Show 0).
Caption = "Se muestra: Modeless (no modal)"
End If
End Sub

13.- Ejecutar un programa y redirigir la salida estndar al programa


de Visual Basic
La intencin de este programa es ejecutar un programa MS-DOS, (que no
un comando como DIR, etc.), y redirigir la salida del mismo hacia VB, es
decir, lo que el programa en cuestin muestre en la pantalla del MS-DOS, se
mostrar en un TextBox y el programa a ejecutar se pasar en la lnea de
comandos.
Podemos usar, por ejemplo: MEM.exe, etc.
Veamos el cdigo de ejemplo, modificado de un artculo de la Knowledge
Base de Microsoft:
HOWTO: Create a Process for Reading and Writing to a Pipe (Q173085)
Entre las modificaciones, est la espera a que el proceso llamado (el
programa que se ejecuta), termine, ya que si no termina, no tomaremos
todos los datos, sobre todo si es una tarea que dure algunos segundos; en
principio hice una pausa de un par de segundos, pero despus, rebuscando

en el cdigo que ya tena, (los afortunados que tienen el cdigo del


gsBackUp tambin lo tienen), puse que esperara a que terminase el
proceso. WaitForSingleObject es la funcin que se encarga de eso.
Bueno, vale ya de parrafadas, aqu tienes el cdigo.
Para que funcione, debers insertar un TextBox Multiline y que tenga las
dos barras de desplazamiento (Scrollbars), adems de un commandbuton.
Seguir "investigando" o buscando informacin de cmo hacer lo contrario,
ya que lo he intentado y no he dado con la tecla, es decir que un programa
de Visual Basic pueda mostrar por el STDOUT lo que queramos.
Dejo parte del texto del artculo, aunque est en ingls, para que sepas de
que va el rollo este.

El cdigo a insertar en el formulario:


'
Option Explicit
'
'
'This example illustrates a Visual Basic application
starting
'another process with the purpose of redirecting that
process's
'standard IO handles.
'The Visual Basic application redirects the created
process's
'standard output handle to an anonymous pipe,
'then proceeds to read the output through the pipe.
'This sample just redirects STDOUT of the new process.
'
'To redirect other handles (STDIN and STDERR),
'create a pipe for each handle for which redirection is
desired.
'The Visual Basic application would read from the read
ends
'of the pipes for the redirected STDOUT and STDERR.
'If STDIN redirection was desired, the Visual Basic
application
'would write to the write end of the appropriate pipe.
'
'An example follows:
'

'
'

'A pipe for redirection of STDOUT

'

CreatePipe(hReadPipe1, hWritePipe1, sa, 0)

'
'

'A pipe for redirection of STDERR

'

CreatePipe(hReadPipe2, hWritePipe2, sa, 0)

'
'

'A pipe for redirection of STDIN

'

CreatePipe(hReadPipe3, hWritePipe3, sa, 0)

'
'
'Preparing to start the process with redirected
handles
'

start.hStdOutput = hWritePipe1

'

start.hStdError = hWritePipe2

'

start.hStdInput = hReadPipe3

'
'

'Reading output from the started process's STDOUT

'
ReadFile(hReadPipe1, mybuff1, 100, bytesread, ByVal
0&)
'
'

'Reading output from the started process's STDERR

'
ReadFile(hReadPipe2, mybuff2, 100, bytesread, ByVal
0&)
'
'

'Writing to the started process's STDIN

'
WriteFile(hWritePipe3, mybuff3, 100, byteswritten,
ByVal 0&)
'
Private Declare Function CreatePipe Lib "kernel32" ( _
phReadPipe As Long, _
phWritePipe As Long, _
lpPipeAttributes As Any, _
ByVal nSize As Long) As Long
Private Declare Function ReadFile Lib "kernel32" ( _
ByVal hFile As Long, _
ByVal lpBuffer As String, _
ByVal nNumberOfBytesToRead As Long, _
lpNumberOfBytesRead As Long, _
ByVal lpOverlapped As Any) As Long

Private Type SECURITY_ATTRIBUTES


nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type STARTUPINFO
cb As Long
lpReserved As Long
lpDesktop As Long
lpTitle As Long
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessId As Long
dwThreadID As Long
End Type
Private Declare Function CreateProcessA Lib "kernel32"
(ByVal _
lpApplicationName As Long, ByVal lpCommandLine As
String, _

lpProcessAttributes As Any, lpThreadAttributes As


Any, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags
As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory
As Long, _
lpStartupInfo As Any, lpProcessInformation As Any) As
Long
Private Declare Function WaitForSingleObject Lib
"kernel32" _
(ByVal hHandle As Long, ByVal dwMilliseconds As
Long) As Long
Private Declare Function CloseHandle Lib "kernel32"
(ByVal _
hObject As Long) As Long
Const SW_SHOWMINNOACTIVE = 7
Const STARTF_USESHOWWINDOW = &H1
Const INFINITE = -1&
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const STARTF_USESTDHANDLES = &H100&
Private Function ExecCmdPipe(ByVal CmdLine As String) As
String
'Ejecuta el comando indicado, espera a que termine
'y redirige la salida hacia VB
Dim proc As PROCESS_INFORMATION, ret As Long,
bSuccess As Long
Dim start As STARTUPINFO
Dim sa As SECURITY_ATTRIBUTES
Dim hReadPipe As Long, hWritePipe As Long
Dim bytesread As Long, mybuff As String
Dim i As Integer
Dim sReturnStr As String
'=== Longitud de la cadena, en teora 64 KB,
'

pero no en la prctica

'mybuff = String(64 * 1024, Chr$(65))

'
mybuff = String(10 * 1024, Chr$(65))
sa.nLength = Len(sa)
sa.bInheritHandle = 1&
sa.lpSecurityDescriptor = 0&
ret = CreatePipe(hReadPipe, hWritePipe, sa, 0)
If ret = 0 Then
'===Error
ExecCmd = "Error: CreatePipe failed. " &
Err.LastDllError
Exit Function
End If
start.cb = Len(start)
start.hStdOutput = hWritePipe
start.dwFlags = STARTF_USESTDHANDLES +
STARTF_USESHOWWINDOW
start.wShowWindow = SW_SHOWMINNOACTIVE
' Start the shelled application:
ret& = CreateProcessA(0&, CmdLine$, sa, sa, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
If ret <> 1 Then
'===Error
sReturnStr = "Error: CreateProcess failed. " &
Err.LastDllError
End If
' Wait for the shelled application to finish:
ret = WaitForSingleObject(proc.hProcess, INFINITE)
'En el original, slo leian 100 caracteres
bSuccess = ReadFile(hReadPipe, mybuff, Len(mybuff),
bytesread, 0&)
If bSuccess = 1 Then
sReturnStr = Left(mybuff, bytesread)
Else
'===Error
sReturnStr = "Error: ReadFile failed. " &
Err.LastDllError
End If
ret = CloseHandle(proc.hProcess)

ret = CloseHandle(proc.hThread)
ret = CloseHandle(hReadPipe)
ret = CloseHandle(hWritePipe)
ExecCmd = sReturnStr
End Function
Private Sub Command1_Click()
Text1 = ExecCmdPipe(Command$)
End Sub
Private Sub Form_Load()
'
Text1 = ""
'
Show
'Asigna al TextBox lo que se introduzca en la lnea
de comandos
Text1 = ExecCmdPipe(Command$)
Text1.Refresh
End Sub

14.- timeGetTime, un temporizador ms preciso que GetTickCount


(31/Ago)
Este temporizador, tambin llamado temporizador multimedia, tiene mayor
precisin que GetTickCount, a saber:
La precisin de GetTickCount es (aproximadamente)
de 10 milisegundos en Windows NT 3.5 o superior
de 16 ms. en Windows NT 3.1
de 55 ms. en Windows 95 o superior
Por el contrario, la precisin de timeGetTime es:
1 milisegundo en Windows 95, y de
5 milisegundos o ms, (es configurable), en Windows NT
Tanto uno como el otro, se basan en el tiempo transcurrido desde que se
inicio Windows y se vuelve a poner a cero cuando han transcurrido 2^32
milisegundos, es decir despus de 49.7 das.
Estas son las declaraciones del API para estas funciones:
Declare Function timeGetTime Lib "winmm.dll" () As Long

Declare Function GetTickCount Lib "kernel32.dll" () As Long


Si quieres ver un ejemplo de cmo usar estas funciones, pulsa en este link
que te llevar a la pgina con la clase cGetTimer, en la cual hay un ejemplo
en el que se usan los dos temporizadores.

15.- cQueryReg: una clase para manipular el registro del sistema


Esta es una revisin/ampliacin de la clase cQueryReg publicada el 18/Ago/98.
Lo que hay de nuevo en esta nueva versin es que se pueden enumerar las subclaves de una
clave indicada o los valores de una clave del registro.
Por tanto slo se mostrar el cdigo de estas nuevas funciones, adems voy a poner un par
de ejemplos de cmo usarla, uno de los ejemplos servir para saber las conexiones del
acceso telefnico a redes disponibles para poder conectarnos, el otro lo que har ser mostrar
las subclaves que tiene una clave determinada y seleccionando una de esas subclaves,
mostrar los valores contenidos en ella, de esta forma podremos saber, por ejemplo, que
programas se ejecutan al iniciarse Windows, etc.
Para un futuro no muy lejano, ampliar la clase para que pueda guardar el contenido de una
clave con sus subclaves en un fichero REG, tambin habr una funcin o mtodo para lo
contrario, es decir: indicarle un fichero REG y asignar esos valores al registro.
Vamos a ver las nuevas funciones y los ejemplos.
Como creo que con los comentarios que tienen habr suficiente, te muestro slo el cdigo,
despus viendo en el cdigo de ejemplo cmo se usan, tendrs la informacin necesaria, al
menos eso espero, pero si quieres hacer algn comentario o necesitas aclaracin, escribeme
y lo pondr en esta pgina para que el resto del personal se entere...
'
Public Function EnumKeys(colKeys() As String, ByVal sKey As String)
As Boolean
'--------------------------------------------------------------------'Enumera todas las subclaves de la clave indicada en sKey
(12/Oct/98)
'
'Parmetros:
'
colKeys()
halladas

Array unidimensional que contendr las claves

'
sKey
informacin

Clave completa de la que se quiere la

'
'Devolver True si todo va bien
'
'Revisado para Array y buen funcionamiento (espero)
(14/Oct/98)

'--------------------------------------------------------------------Dim dwIndex

As Long

Dim ret

As Long

Dim hKey2

As Long

Dim hKey

As Long

Dim lpName

As String

Dim lpftLastWriteTime

As FILETIME

Dim colItems

As Long

Dim lSize

As Long

Dim SubKeysNum

As Long

Dim MaxSubKeyLen

As Long

Dim numValues

As Long

Dim MaxValueNameLen

As Long

Dim MaxDataLen

As Long

colItems = 0
ReDim colKeys(0)
'Si se pasa una cadena en sKey, esta funcin la convierte
'en un valor vlido para la clave principal
hKey = ParseKey(sKey, hKey)
'Abrir la clave indicada
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_ENUMERATE_SUB_KEYS,
hKey2)
EnumKeys = True
'Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
'Obtener informacin de la clave y datos, devolver:
'SubKeysNum

Nmero de subclaves

'MaxSubKeyLen

Tamao mximo de nombre de clave

'numValues

Nmero de valores en esta clave

'MaxValueNameLen

Tamao mximo del nombre del valor

'MaxDataLen

Tamao mximo de los datos

ret = RegQueryInfoKey(hKey2, 0&, 0&, 0&, SubKeysNum,


MaxSubKeyLen, _
0&, numValues, MaxValueNameLen, _

MaxDataLen, 0&, lpftLastWriteTime)


'Se empieza desde cero
For dwIndex = 0 To SubKeysNum
lSize = MaxSubKeyLen
lpName = String$(lSize + 1, 0)
'Slo nos interesa los nombres de las subclaves
ret = RegEnumKeyEx(hKey2, dwIndex, lpName, lSize, _
0&, 0&, 0&, lpftLastWriteTime)
If ret = ERROR_MORE_DATA Or ret = ERROR_SUCCESS Then
'Redimensionar el array
colItems = colItems + 1
ReDim Preserve colKeys(colItems)
'lSize tiene el nmero de caracteres devuelto,
'sin incluir el CHR$(0) del final
colKeys(colItems) = Left$(lpName, lSize)
End If
Next
Else
EnumKeys = False
End If
'Cerrar la clave abierta
ret = CloseKey(hKey2)
End Function

'
Public Function EnumValues(ByRef colKeys() As String, ByVal sKey As
String) As Boolean
'--------------------------------------------------------------------'Enumera todos los valores de la clave indicada en sKey
(12/Oct/98)
'
'Parmetros:
'
colKeys()
halladas
'
a

Array unidimensional que contendr las claves


En este array se almacena el nombre del valor y

'
tener

continuacin el valor en si, por tanto hay que

'
informacin.

esto en cuenta a la hora de recuperar la

'

Ver el ejemplo de cmo usarla un poco ms abajo.

'
sKey
informacin

Clave completa de la que se quiere la

'
'Devolver True si todo va bien
'
'Revisado para Array y buen funcionamiento (espero)
(14/Oct/98)
'--------------------------------------------------------------------'Para recuperar la informacin de colKeys(), hacer esto:
'
'If .EnumValues(colKeys(), sKey) Then
'

For i = 1 To UBound(colKeys) Step 2

'

'colKeys(i)

ser el nombre

'

'colKeys(i + 1)

ser el valor o dato almacenado

'

Next

'End If
'--------------------------------------------------------------------'
Dim dwIndex

As Long

Dim ret

As Long

Dim hKey2

As Long

Dim hKey

As Long

Dim lpName

As String

Dim lpftLastWriteTime

As FILETIME

Dim retDT

As eHKEYDataType

Dim lSize

As Long

Dim sData

As String

Dim aData()

As Byte

Dim lDWord

As Long

Dim i

As Long

Dim colItems

As Long

Dim SubKeysNum

As Long

Dim MaxSubKeyLen

As Long

Dim numValues

As Long

Dim MaxValueNameLen

As Long

Dim MaxDataLen

As Long

'Si se pasa una cadena en sKey, esta funcin la convierte


'en un valor vlido para la clave principal
hKey = ParseKey(sKey, hKey)
'Abrir la clave indicada
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_QUERY_VALUE, hKey2)
'Valores por defecto
EnumValues = True
ReDim aData(0)
lDWord = 0
sData = ""
'Inicializar el array
colItems = 0
ReDim colKeys(colItems)
'Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
'Obtener la informacin de esta clave, devolver:
'SubKeysNum

Nmero de subclaves

'MaxSubKeyLen

Tamao mximo de nombre de clave

'numValues

Nmero de valores en esta clave

'MaxValueNameLen

Tamao mximo del nombre del valor

'MaxDataLen

Tamao mximo de los datos

ret = RegQueryInfoKey(hKey2, 0&, 0&, 0&, SubKeysNum,


MaxSubKeyLen, _
0&, numValues, MaxValueNameLen, _
MaxDataLen, 0&, lpftLastWriteTime)
lpName = String$(MaxValueNameLen + 1, 0)
'Hacer un bucle para el nmero de valores posibles
For dwIndex = 0 To numValues
lpName = String$(MaxValueNameLen + 1, 0)
'Llamarlo primero para saber el tipo de datos,

'el cual estar en retDT


0&, 0&)

ret = RegEnumValue(hKey2, dwIndex, 0&, 0&, 0&, retDT,

'la primera vez, cuando dwIndex = cero, devuelve


ERROR_SUCCESS,
datos.

'pero despus devuelve ERROR_MORE_DATA mientras haya


If ret = ERROR_MORE_DATA Or ret = ERROR_SUCCESS Then
lSize = MaxDataLen
Select Case retDT
Case REG_SZ 'Datos de cadena
sData = String$(lSize, 0)

ret = RegEnumValue(hKey2, dwIndex, lpName,


Len(lpName), 0&, retDT, ByVal sData, lSize)
sData = RTrimZero(sData)
lpName = RTrimZero(lpName)
ReDim Preserve colKeys(colItems + 2)
colKeys(colItems + 1) = lpName
colKeys(colItems + 2) = sData
colItems = colItems + 2
Case REG_DWORD
'Datos numricos (long)
ret = RegEnumValue(hKey2, dwIndex, lpName,
Len(lpName), 0&, retDT, lDWord, lSize)
sData = CStr(lDWord)
lpName = RTrimZero(lpName)
ReDim Preserve colKeys(colItems + 2)
colKeys(colItems + 1) = lpName
colKeys(colItems + 2) = sData
colItems = colItems + 2
'Case REG_BINARY
'

'Datos binarios

Case Else
'Tratarlo como Binary
If lSize Then
ReDim aData(lSize)
'Leer los datos binarios
ret = RegEnumValue(hKey2, dwIndex, lpName,
Len(lpName), 0&, retDT, aData(0), lSize)
lpName = RTrimZero(lpName)
'Al estilo de como se muestra con RegEdit

sData = ""
For i = 0 To UBound(aData) - 1
sData = sData & Format$(Hex$(aData(i)),
"00") & " "
Next
ReDim Preserve colKeys(colItems + 2)
colKeys(colItems + 1) = lpName
colKeys(colItems + 2) = sData
colItems = colItems + 2
End If
End Select
End If
Next
Else
EnumValues = False
End If
'Cerrar la clave abierta
ret = CloseKey(hKey2)
End Function

16.- Conectarse usando Acceso Telefnico a Redes (ejemplo usando la


clase cQueryReg)
El formulario para conectarse leyendo la informacin del registro.
Esta utilidad lo que hace es buscar en el registro los diferentes accesos telefnicos a redes
(Dial-Up Networking), mostrarlos en un combo, seleccionamos el que nos interese, en caso de
que haya ms de uno y despus al pulsar en conectar se llama a RunDll32 para hacer la
conexin, el cdigo para poder hacer esto, me lo mandaron, pero no me decian el nombre y
tampoco he encontrado el mail de quin lo hizo, as que doy las gracias a quin lo envi, que
no es plan de anotarse puntos que no son de uno...
Veamos el aspecto del formulario en tiempo de diseo:

Ahora veamos el cdigo que se usa al iniciarse el formulario, el cual llena el combo con las
diferentes conexiones, obtenidas de la siguiente clave del registro:
HKEY_USERS\.Default\RemoteAccess\Addresses
'
'------------------------------------------------------------------------'Ejemplo para conectarse usando Acceso Telefnico a Redes
(09/Oct/98)
'
(13/Oct/98)
'
'Guillermo 'guille' Som, 1998
'------------------------------------------------------------------------Option Explicit
Private m_QR As cQueryReg

'Clase de manipulacin del registro

Private colKeys() As String


nombres/contenidos

'Array para guardar los

Private Sub cmdConectar_Click()


'Conectar usando el nombre de conexin indicado
Dim sConex As String
'Obtener el nombre de la conexin a usar
With cboConex
sConex = .List(.ListIndex)
End With
'Llamar a RunDll para conectar con la conexin indicada

Call Shell("RunDll32.exe rnaui.dll,RnaDial " & sConex,


vbNormalFocus)
DoEvents
'Enviar una pulsacin para conectar
'si esto te da problemas y lo quitas, tendrs que aceptar la
conexin indicada
SendKeys "{Enter}", True
DoEvents
End Sub

Private Sub Form_Load()


'Leer del registro las conexiones disponibles
'y aadirlas al combo
'
Dim sKey As String
Dim i As Long
Set m_QR = New cQueryReg
'Borrar el contenido de colKeys
ReDim colKeys(0)
'Las conexiones disponibles (en Default)
sKey = "HKEY_USERS\.Default\RemoteAccess\Addresses"
'Leer los nombres de las conexiones disponibles
If m_QR.EnumValues(colKeys(), sKey) Then
'Es necesario el Step 2 ya que se lee el Nombre y el
Contenido,
'aunque en este caso el contenido no nos interesa.
For i = 1 To UBound(colKeys) Step 2
cboConex.AddItem colKeys(i)
Next
If cboConex.ListCount Then
cboConex.ListIndex = 0
End If
End If
End Sub

Private Sub Form_Unload(Cancel As Integer)


'Un poco de limpieza...
Set m_QR = Nothing
Set Form1 = Nothing
End Sub

17.- Enumerar las claves o valores de una clave del registro de


Windows (ejemplo usando la clase cQueryReg)
Ejemplo para leer las subclaves de una clave y/o los valores de una clave:
Lo que se hace en esta rutina es cargar inicialmente los valores de la clave:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion, que es donde est la
informacin, entre otras, de que programas se cargan al iniciarse Windows, esta clave se usa
automticamente al seleccionar el checkbox y se asigna al textbox de la parte superior que es
donde puedes escribir la clave de la que quieres listar las subclaves que tenga.
En el combo se incluirn esas subclaves y al seleccionar una de ellas se mostrar el nombre y
el valor contenido en el ListView. Cuando se seleccione un valor del ListView, se mostrar en
las cajas de texto correspondientes, listas para ser modificada en el caso de los datos o
borrada en el caso de la subclave, aunque el cdigo para hacer esto no he querido ponerlo
para que no metas la pata si te pones a probar...
Veamos el formulario en tiempo de diseo y el cdigo del formulario.

'
'------------------------------------------------------------------------'tEnumReg
(14/Oct/98)
'Prueba para usar la enumeracin de claves y valores del registro
'
'Guillermo 'guille' Som, 1998
'------------------------------------------------------------------------Option Explicit
Private m_QR As cQueryReg

'Clase de manipulacin del registro

Private colKeys() As String


nombres/contenidos

'Array para guardar los

Private Sub Form_Load()


'
Dim sKey As String
Dim i As Long
Set m_QR = New cQueryReg
ReDim colKeys(0) As String
txtKey =
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\explor
er"
txtNombre = ""
txtDatos = ""
'Valores del TreeView
With lvwRun
.View = lvwReport
.ColumnHeaders.Add , "Name", "Nombre", 2600
.ColumnHeaders.Add , "Data", "Datos", 5500
'No permitir que se modifique la etiqueta
.LabelEdit = lvwManual
'Mostrar la informacin ordenada
.Sorted = True
.SortOrder = lvwAscending

.SortKey = 0
End With
'Esto enumera las sub-claves que hay en esta clave
sKey =
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion"
If m_QR.EnumKeys(colKeys(), sKey) Then
For i = 1 To UBound(colKeys)
cboKeys.AddItem colKeys(i)
Next
cboKeys.ListIndex = 0
End If
End Sub

Private Sub cboKeys_Click()


Dim sKey As String
Dim lItem As Long
Dim i As Long
Dim itmX As ListItem
'Esto enumera los valores de esta clave
'sKey =
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\"
sKey = Trim$(txtKey)
If Right(sKey, 1) <> "\" Then
sKey = sKey & "\"
End If
With cboKeys
sKey = sKey & .List(.ListIndex)
End With
'Borrar el contenido de colKeys
ReDim colKeys(0)
'Borrar el contenido del TreeView
lvwRun.ListItems.Clear

If m_QR.EnumValues(colKeys(), sKey) Then


lItem = 1
For i = 1 To UBound(colKeys) Step 2
If Len(colKeys(i)) Then
Set itmX = lvwRun.ListItems.Add(lItem, "k" &
colKeys(i), colKeys(i))
itmX.SubItems(1) = colKeys(i + 1)
lItem = lItem + 1
End If
Next
If lvwRun.ListItems.Count Then
With lvwRun
.SelectedItem = .ListItems(1)
If Len(.SelectedItem.Text) Then
Set itmX = .ListItems("k" & .SelectedItem.Text)
End If
txtNombre = itmX.Text
txtDatos = itmX.SubItems(1)
End With
End If
End If
End Sub

Private Sub chkCurrentVersion_Click()


Dim sKey As String
Dim i As Long
If chkCurrentVersion.Value Then
ReDim colKeys(0)
'Esto enumera las sub-claves que hay en esta clave
sKey =
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion"
txtKey = sKey
If m_QR.EnumKeys(colKeys(), sKey) Then
For i = 1 To UBound(colKeys)
cboKeys.AddItem colKeys(i)
Next

If cboKeys.ListCount Then
cboKeys.ListIndex = 0
End If
End If
End If
End Sub

Private Sub cmdAsignar_Click()


'No implementado en el ejemplo...
'si quieres modificar algo, hazlo bajo tu responsabilidad
End Sub
Private Sub cmdBorrarRun_Click()
'No implementado en el ejemplo...
'si quieres borrar algo, hazlo bajo tu responsabilidad
End Sub

Private Sub Form_Unload(Cancel As Integer)


'Un poco de limpieza...
Set m_QR = Nothing
Set tEnumReg = Nothing
End Sub

Private Sub lvwRun_Click()


'Mostrar el elemento "clickeado" en las cajas de texto
(12/Oct/98)
Dim itmX As ListItem
With lvwRun
Set itmX = .ListItems(.SelectedItem.Text)
txtNombre = itmX.Text
txtDatos = itmX.SubItems(1)
End With
End Sub

Private Sub txtKey_KeyPress(KeyAscii As Integer)


Dim sKey As String
Dim i As Long
If KeyAscii = vbKeyReturn Then
KeyAscii = 0
ReDim colKeys(0)
cboKeys.Clear
chkCurrentVersion.Value = 0
'Esto enumera las sub-claves que hay en esta clave
sKey = Trim$(txtKey)
If m_QR.EnumKeys(colKeys(), sKey) Then
For i = 1 To UBound(colKeys)
cboKeys.AddItem colKeys(i)
Next
If cboKeys.ListCount Then
cboKeys.ListIndex = 0
End If
End If
End If
End Sub

18.- Enumerar los usuarios de nuestro equipo (profiles) (15/Oct)


Este ejemplo es para saber los diferentes usuarios que tienen acceso a
nuestro equipo (profiles).
Para saberlo, he usado la clase cQueryReg (revisin 2 del 14/Oct/98), que
permite enumerar claves y valores del registro del sistema, en este caso la
entrada del registro que nos interesa es:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVers
ion\ProfileList
Con el siguiente cdigo, (te recuerdo que necesitas la nueva versin de
cQueryReg), se mostrarn en un combo los usuarios, para ello se leen las
subclaves que hay en la clave indicada anteriormente, dentro de estas
claves habr ms informacin sobre el usuario, pero aqu slo vamos a
mostrar los nombres.
Nota: debers tener un formulario con un comboBox llamado cboProfiles.

'
Option Explicit
Private m_QR As cQueryReg
registro
Private colKeys() As String
nombres/contenidos

'Clase de manipulacin del


'Array para guardar los

Private Sub Form_Load()


'Leer del registro los usuarios disponibles
'y aadirlos al combo
'
Dim sKey As String
Dim i As Long
Set m_QR = New cQueryReg
'Borrar el contenido de colKeys
ReDim colKeys(0)
cboProfiles.Clear
cmdConectar.Enabled = False
'Los usuarios disponibles estn en:
sKey =
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersi
on\ProfileList"
'como mnimo tener el valor por defecto
cboProfiles.AddItem ".Default"
'Leer los nombres de las claves disponibles
If m_QR.EnumKeys(colKeys(), sKey) Then
For i = 1 To UBound(colKeys)
cboProfiles.AddItem colKeys(i)
Next
End If
cboProfiles.ListIndex = 0
End Sub

19.- Registrar Hot-Keys con nuestra aplicacin... para activarla


por ejemplo (07/Dic)
Con este ejemplo lo que se har es asignar las teclas Ctrl+F para que al
pulsa esa combinacin de teclas, se active nuestra aplicacin.
No he hecho pruebas con una aplicacin real, pero todo ser cuestin de
hacerlo...
Para hacer la prueba, crea un nuevo proyecto y aade el siguiente cdigo:
'-----------------------------------------------------------------'Prueba para registrar una combinacin de teclas

(06/Dic/98)

'y activar la aplicacin al recibirlas...


'
'El cdigo para esperar a que se reciban los mensajes, est
'inspirado en el cdigo de Francisco Charte para usar Drag&Drop
'con el VB4
'
'Guillermo 'guille' Som, 1998
'-----------------------------------------------------------------Option Explicit
Private Declare Function RegisterHotKey Lib "user32" _
(ByVal hWnd As Long, ByVal id As Long, ByVal fsModifiers As
Long, ByVal vk As Long) As Long
Private Declare Function UnregisterHotKey Lib "user32" (ByVal
hWnd As Long, ByVal id As Long) As Long
Private Const MOD_ALT = &H1
Private Const MOD_CONTROL = &H2
Private Const MOD_SHIFT = &H4
Private Const WM_HOTKEY = &H312
'Tipos de datos para las funciones del API
Private Type POINTAPI
x As Long
y As Long
End Type
Private Type Msg

hWnd As Long
message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type 'MSG
Private Const PM_REMOVE = &H1
'funciones para recibir los mensajes de windows
Private Declare Function PeekMessage Lib "user32" Alias
"PeekMessageA" _
(lpMsg As Msg, ByVal hWnd As Long, ByVal wMsgFilterMin As
Long, _
ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As
Long
Private Declare Function WaitMessage Lib "user32" () As Long
Private Termina As Boolean

Private Sub ProcesaMensajes()


'Para leer mensajes de la cola
Dim Mensaje As Msg
'Mientras Termina no sea True
Do While Not Termina
'esperamos a que llegue un mensaje
WaitMessage
'Si ese mensaje es WM_HOTKEY
If PeekMessage(Mensaje, Me.hWnd, WM_HOTKEY,
WM_HOTKEY, PM_REMOVE) Then
'Restauramos el formulario al estado normal
'por si est minimizado
WindowState = vbNormal
'Mostramos el form
Show
End If

'permitimos el trabajo de otros procesos


DoEvents
Loop
End Sub
Private Sub Form_Load()
'registrar las teclas para activacin de esta aplicacin
Dim ret As Long
Termina = False
'La tecla Crtl+F ser la que activar este formulario
ret = RegisterHotKey(Me.hWnd, &HBFFF&, MOD_CONTROL,
vbKeyF)
'If ret Then
'

Label2 = "Se ha registrado de forma correcta el Hot-Key"

'Else
'

Label2 = "No se ha registrado el Hot-Key"

'End If
'Hay que mostrar el form
'sino entrar en el bucle de espera de mensajes sin mostrarse
Show
ProcesaMensajes
End Sub
Private Sub Form_Unload(Cancel As Integer)
Termina = True
'Quitar la Hot-Key registrada
Call UnregisterHotKey(Me.hWnd, &HBFFF&)
End Sub

20.- Manejar ficheros INIs: leer, guardar, borrar, leer secciones


enteras, leer todas las secciones (06/Mar)
Introduccin:

Pues eso... que intentando mejorar el acceso a los ficheros INIs me "entretuve" en
leer la ayuda del API para 32 bits y vi un par de funciones que... al menos en
teora, slo servan para el Windows NT... y mira t por dnde... tambin sirven
para el Windows 98... no las he probado para el Windows 95, as que si lo haces y
funciona, me lo cuentas... vale? Gracias.
El cdigo:
El cdigo que te voy a mostrar ahora tiene dos funciones que ya habrs visto en
algn otro sitio de mis pginas, son las funciones para leer y escribir en ficheros
INIs; pero ahora te voy a dar otras tres que de seguro te interesarn:
Borrar claves o secciones de un fichero INI.
Leer todos las claves y valores de una seccin.
Leer todas las secciones.
Decirte que de esta ltima, no viene la declaracin del API en el fichero que se
incluye con el VB, as que toma nota, porque es interesante.
Las declaraciones de las funciones del API
'
'--- Declaraciones para leer ficheros INI --'
' Leer todas las secciones de un fichero INI, esto seguramente no
funciona en Win95
' *** Esta funcin no estaba en las declaraciones del API que se
incluye con el VB ***
Private Declare Function GetPrivateProfileSectionNames Lib
"kernel32" Alias "GetPrivateProfileSectionNamesA" _
(ByVal lpszReturnBuffer As String, ByVal nSize As Long, _
ByVal lpFileName As String) As Long
' Leer una seccin completa
Private Declare Function GetPrivateProfileSection Lib "kernel32"
Alias "GetPrivateProfileSectionA" _
(ByVal lpAppName As String, ByVal lpReturnedString As String, _
ByVal nSize As Long, ByVal lpFileName As String) As Long
' Leer una clave de un fichero INI
Private Declare Function GetPrivateProfileString Lib "kernel32"
Alias "GetPrivateProfileStringA" _
(ByVal lpApplicationName As String, ByVal lpKeyName As Any, _
ByVal lpDefault As String, ByVal lpReturnedString As String, _
ByVal nSize As Long, ByVal lpFileName As String) As Long
' Escribir una clave de un fichero INI (tambin para borrar claves y
secciones)

Private Declare Function WritePrivateProfileString Lib "kernel32"


Alias "WritePrivateProfileStringA" _
(ByVal lpApplicationName As String, ByVal lpKeyName As Any, _
ByVal lpString As Any, ByVal lpFileName As String) As Long
Ahora las funciones y despus vendr un ejemplo de cmo usarlas.
'
Private Function IniGet(ByVal lpFileName As String, ByVal lpAppName
As String, _
ByVal lpKeyName As String, _
Optional ByVal lpDefault As String = "") As

String
'

'Los parmetros son:


'lpFileName:

La Aplicacin (fichero INI)

'lpAppName:

La seccin que suele estar entrre corchetes

'lpKeyName:

Clave

'lpDefault:
la clave.

Valor opcional que devolver si no se encuentra

'
Dim LTmp As Long
Dim sRetVal As String
sRetVal = String$(255, 0)
LTmp = GetPrivateProfileString(lpAppName, lpKeyName, lpDefault,
sRetVal, Len(sRetVal), lpFileName)
If LTmp = 0 Then
IniGet = lpDefault
Else
IniGet = Left(sRetVal, LTmp)
End If
End Function

Private Sub IniWrite(ByVal lpFileName As String, ByVal lpAppName As


String, _
ByVal lpKeyName As String, ByVal lpString As
String)
'
'Guarda los datos de configuracin

'Los parmetros son los mismos que en IniGet


'Siendo lpString el valor a guardar
'
Call WritePrivateProfileString(lpAppName, lpKeyName, lpString,
lpFileName)
End Sub

Private Sub IniDelete(ByVal sIniFile As String, ByVal sSection As


String, _
Optional ByVal sKey As String = "")
'
' Borrar una clave o entrada de un fichero INI
(16/Feb/99)
' Si no se indica sKey, se borrar la seccin indicada en
sSection
' En otro caso, se supone que es la entrada (clave) lo que se
quiere borrar
'
If Len(sKey) = 0 Then
' Borrar una seccin
Call WritePrivateProfileString(sSection, 0&, 0&, sIniFile)
Else
' Borrar una entrada
Call WritePrivateProfileString(sSection, sKey, 0&, sIniFile)
End If
End Sub

Private Function IniGetSection(ByVal lpFileName As String, _


ByVal lpAppName As String) As Variant
'
' Lee una seccin entera de un fichero INI
(27/Feb/99)
'
' Usando Collection en lugar de cParrafos y cContenido
(06/Mar/99)
'
' Esta funcin devolver una coleccin con cada una de las
claves y valores
' que haya en esa seccin.

' Parmetros de entrada:


'

lpFileName

Nombre del fichero INI

'

lpAppName

Nombre de la seccin a leer

' Devuelve:
'

Una coleccin con el Valor y el contenido

'

Para leer los datos:

'

For i = 1 To tContenidos Step 2

'

sClave = tContenidos(i)

'

sValor = tContenidos(i+1)

'

Next

'
Dim tContenidos As Collection
Dim nSize As Long
Dim i As Long
Dim j As Long
Dim sTmp As String
Dim sClave As String
Dim sValor As String

' El tamao mximo para Windows 95


sBuffer = String$(32767, Chr$(0))
nSize = GetPrivateProfileSection(lpAppName, sBuffer,
Len(sBuffer), lpFileName)
If nSize Then
Set tContenidos = New Collection
' Cortar la cadena al nmero de caracteres devueltos
sBuffer = Left$(sBuffer, nSize)
' Quitar los vbNullChar extras del final
i = InStr(sBuffer, vbNullChar & vbNullChar)
If i Then
sBuffer = Left$(sBuffer, i - 1)
End If
' Cada una de las entradas estar separada por un Chr$(0)
Do
i = InStr(sBuffer, Chr$(0))

If i Then
sTmp = LTrim$(Left$(sBuffer, i - 1))
If Len(sTmp) Then
' Comprobar si tiene el signo igual
j = InStr(sTmp, "=")
If j Then
sClave = Left$(sTmp, j - 1)
sValor = LTrim$(Mid$(sTmp, j + 1))
' Asignar la clave y el valor
tContenidos.Add sClave
tContenidos.Add sValor
End If
End If
sBuffer = Mid$(sBuffer, i + 1)
End If
Loop While i
' Por si an queda algo...
If Len(sBuffer) Then
j = InStr(sBuffer, "=")
If j Then
sClave = Left$(sBuffer, j - 1)
sValor = LTrim$(Mid$(sBuffer, j + 1))
tContenidos.Add sClave
tContenidos.Add sValor
End If
End If
End If
Set IniGetSection = tContenidos
End Function

Private Function IniGetSections(ByVal lpFileName As String) As


Variant
'
' Devuelve todas las secciones de un fichero INI
(27/Feb/99)
'
' Usando Collection en lugar de cParrafos y cContenido
'

' Esta funcin devolver una coleccin con todas las secciones
del fichero
' Parmetros de entrada:
'

lpFileName

Nombre del fichero INI

' Devuelve:
'

Una coleccin con los nombres de las secciones

'
Dim tContenidos As Collection
Dim nSize As Long
Dim i As Long
Dim sTmp As String
' El tamao mximo para Windows 95
sBuffer = String$(32767, Chr$(0))
' Esta funcin del API no est definida en el fichero TXT
nSize = GetPrivateProfileSectionNames(sBuffer, Len(sBuffer),
lpFileName)
If nSize Then
' Crear una coleccin del tipo cParrafos que es una
coleccin
' con elementos del tipo cContenido
Set tContenidos = New Collection
' Cortar la cadena al nmero de caracteres devueltos
sBuffer = Left$(sBuffer, nSize)
' Quitar los vbNullChar extras del final
i = InStr(sBuffer, vbNullChar & vbNullChar)
If i Then
sBuffer = Left$(sBuffer, i - 1)
End If
' Cada una de las entradas estar separada por un Chr$(0)
Do
i = InStr(sBuffer, Chr$(0))
If i Then
sTmp = LTrim$(Left$(sBuffer, i - 1))
If Len(sTmp) Then
tContenidos.Add sTmp

End If
sBuffer = Mid$(sBuffer, i + 1)
End If
Loop While i
If Len(sBuffer) Then
tContenidos.Add sBuffer
End If
End If
Set IniGetSections = tContenidos
End Function
Bueno... eso es todo lo que se necesita para usar esas funciones, en este caso, si
te fijas, las funciones son Private, ya que las he "modificado" para usar en un Form
directamente, pero si las quieres "encapsular" en una clase o en un mdulo BAS,
deberas cambiar las declaraciones a Public, pero no las de las funciones del API...
esas pueden (y deberan) seguir siendo Privadas.
Del cdigo para usar estas funciones slo te voy a ensear parte... el resto te lo
imaginas... que con un poco de imaginacin seguro que eres capaz de crear el
ejemplo para que funcione... je, je... no es mala "leche", es que as te esfuerzas
un poco... que os estoy acostumbrando a darlo todo hecho y eso no ayuda
demasiado a aprender...

Aqu estn los trozos del Form y una imagen de cmo queda en modo de diseo.

El form de prueba

'
' Leer todas las secciones del fichero indicado y guardarlas en el
cboSecciones

Private Sub LeerSecciones()


Dim tContenidos As Collection
Dim i As Long
' Llenar las secciones de este fichero
Set tContenidos = IniGetSections(txtFicIni)
If Not tContenidos Is Nothing Then
cboSecciones.Clear
For i = 1 To tContenidos.Count
cboSecciones.AddItem tContenidos(i)
Next
cboSecciones.ListIndex = 0
txtValor = ""
End If
End Sub

' Leer las claves de la seccin seleccionada


Private Sub cboSecciones_Click()
' Mostrar las claves de esta seccin
Dim tContenidos As Collection
Dim i As Long
Set tContenidos = IniGetSection(txtFicIni, cboSecciones.Text)
If Not tContenidos Is Nothing Then
cboClaves.Clear
For i = 1 To tContenidos.Count Step 2
cboClaves.AddItem tContenidos(i)
Next
cboClaves.ListIndex = 0
txtValor = ""
End If
End Sub

Private Sub cmdBorrar_Click(Index As Integer)


' Borrar seccin o clave
Dim sFicINI As String
Dim sSeccion As String

Dim sClave As String


sFicINI = Trim$(txtFicIni)
sSeccion = Trim$(cboSecciones.Text)
sClave = Trim$(cboClaves.Text)
If Index = 0 Then
' Borrar seccin
IniDelete sFicINI, sSeccion
' Releer las secciones disponibles
LeerSecciones
Else
' Borrar clave
IniDelete sFicINI, sSeccion, sClave
' Leer las claves de esta seccin
cboSecciones_Click
End If
End Sub

Private Sub cmdLeer_Click()


' Leer del fichero INI
Dim sFicINI As String
Dim sSeccion As String
Dim sClave As String
Dim sValor As String
sFicINI = Trim$(txtFicIni)
sSeccion = Trim$(cboSecciones)
sClave = Trim$(cboClaves.Text)
sValor = Trim$(txtValor)
txtValor = IniGet(sFicINI, sSeccion, sClave, sValor)
End Sub

Private Sub cmdAdd_Click()


' Aadir la seccin, clave y/o valor

Dim sFicINI As String


Dim sSeccion As String
Dim sClave As String
Dim sValor As String
sFicINI = Trim$(txtFicIni)
sSeccion = Trim$(cboSecciones)
sClave = Trim$(cboClaves.Text)
sValor = Trim$(txtValor)
IniWrite sFicINI, sSeccion, sClave, sValor
End Sub
Pues esto es todo... que no es poco... a disfrutar y... a completar el programilla de
ejemplo.
Aunque todo sea dicho... te he dejado poco que hacer... pero...
Nos vemos.
Guillermo
Nota del 14/Sep/2003:
El cdigo aqu mostrado sirve igualmente para VB5 como para VB6, pero en VB6 se
podra cambiar el tipo de datos devuelto por las funciones IniGetSection e
IniGetSections, para que en lugar de devolver un valor de tipo Variant, devuelva
un array de tipo String. En el cdigo que usa el Array de tipo String no existe la
funcin IniDelete, sino que hay dos funciones, una para borrar una clave:
IniDeleteKey y otra para borrar una seccin: IniDeleteSection.
21.- Copiar, Mover y Eliminar ficheros usando el API de Windows
(SHFileOperation) (11/May)
Aunque ya hay un pequeo ejemplo en la segunda entrega del API, he
creado un nuevo ejemplo con estas tres operaciones bsicas, usando la
misma funcin que usa el Windows.
Lo he probado en Windows 98, pero me imagino que en Windows 95
tambin funcionar, aunque en NT no lo he comprobado... si lo haces, me
lo comunicas. Gracias. (* ver nota del 26/May/2004)
Segn las opciones que se especifiquen, ver el listado, Windows nos pedir
confirmacin o no, nos avisar si tiene que crear el directorio de destino e
incluso har una copia si el fichero de destino ya existe.
En este ejemplo slo se manipula un fichero, para especificar varios
ficheros, hay que separar cada nombre con vbvNullChar, ver el ejemplo de
enviar ficheros a la papelera de reciclaje, para una funcin que acepta
varios nombres de ficheros en el parmetro.

Aqu tienes una captura del form, en tiempo de diseo y el listado, creo que
no necesita ms comentarios... espero que te sea de utilidad.
Nota del 26/Mayo/2004:
La declaracin de fFlags del tipo SHFILEOPSTRUCT la he cambiado a Long
ya que fallaba en Windows XP. Usndola como Long tambin funciona en
Windows 98.
Tambin he aadido un zip con el cdigo y el ejecutable para VB6 SP5
(SHCopiar.zip 8.74 KB)

'----------------------------------------------------------------------------' Ejemplo de copiar y mover ficheros usando el API de


Windows
(11/May/99)
'
' Revisado y corregido para Windows XP Profesional
(26/May/04)
' En el XP (y seguramente en Windows 2000) la variable
fFlags es un Long
' Nota:
'
Esta revisin se la "debo" a un bug reportado por
Julin Collado Angulo
'
' Guillermo 'guille' Som, 1999, 2004

'----------------------------------------------------------------------------Option Explicit
' Variables para el programa de prueba
Private sFicOri As String
Private sFicDes As String
Private iFlags As Long
' Constantes para el orden de los chkOpciones
Private Enum eOpciones
cFOF_ALLOWUNDO
cFOF_FILESONLY
cFOF_MULTIDESTFILES
cFOF_NOCONFIRMATION
cFOF_NOCONFIRMMKDIR
cFOF_RENAMEONCOLLISION
cFOF_SILENT
cFOF_SIMPLEPROGRESS
End Enum
' Variables, constantes y declaraciones para el API
Private Type SHFILEOPSTRUCT
hWnd As Long
formulario

' hWnd del

wFunc As Long
usar: FO_COPY, etc.

' Funcin a

pFrom As String
origen

' Fichero(s) de

pTo As String
destino

' Fichero(s) de

' fFlags

para Windows 2000/XP declararlo como

Long
'

para Windows 9x declararlo como Integer,

'
aunque tambin funciona si se declara
como Long (al menos en W98)
'fFlags As Integer

' Opciones

fFlags As Long
fAnyOperationsAborted As Boolean
cancelado
hNameMappings As Long
lpszProgressTitle As String
FOF_SIMPLEPROGRESS

' Si se ha
'
' Slo si se usa

End Type
' Constantes para FileOperation
Private Enum eFO
FO_COPY = &H2&

' Copiar

FO_DELETE = &H3&

' Borrar

FO_MOVE = &H1&

' Mover

FO_RENAME = &H4&

' Renombrar

'
FOF_MULTIDESTFILES = &H1&
archivos de destino

' Multiples

FOF_CONFIRMMOUSE = &H2&
implementada

' No est

FOF_SILENT = &H4&
progreso

' No mostrar el

FOF_RENAMEONCOLLISION = &H8&
' Cambiar el
nombre si el archivo de destino ya existe
FOF_NOCONFIRMATION = &H10&
confirmacin

' No pedir

FOF_WANTMAPPINGHANDLE = &H20&
SHFILEOPSTRUCT.hNameMappings

'// Fill in
'// Must be

freed using SHFreeNameMappings


FOF_ALLOWUNDO = &H40&
deshacer

' Permitir

FOF_FILESONLY = &H80&
' Si se
especifica *.*, hacerlo slo con archivos
FOF_SIMPLEPROGRESS = &H100&
nombres de los archivos

' No mostrar los

FOF_NOCONFIRMMKDIR = &H200&
la creacin de directorios

' No confirmar

FOF_NOERRORUI = &H400&
error UI

'// don't put up

FOF_NOCOPYSECURITYATTRIBS = &H800&
NT file Security Attributes

'// don't copy

End Enum
Private Declare Function SHFileOperation Lib
"shell32.dll" Alias "SHFileOperationA" _
(lpFileOp As SHFILEOPSTRUCT) As Long
Private Sub cmdCopiar_Click()
' Copiar
Dim SHFileOp As SHFILEOPSTRUCT

' Asignar el valor de las opciones


AsignarFlags
sFicOri = txtOri & vbNullChar & vbNullChar
sFicDes = txtDes & vbNullChar & vbNullChar
With SHFileOp
.wFunc = FO_COPY
.fFlags = iFlags
.hWnd = Me.hWnd
.pFrom = sFicOri
.pTo = sFicDes
.lpszProgressTitle = "Copiando los ficheros
especificados"
End With
Call SHFileOperation(SHFileOp)
End Sub
Private Sub cmdEliminar_Click()
' Eliminar
Dim SHFileOp As SHFILEOPSTRUCT
' Asignar el valor de las opciones
AsignarFlags
sFicDes = txtDes & vbNullChar & vbNullChar
With SHFileOp
.wFunc = FO_DELETE
.fFlags = iFlags
.hWnd = Me.hWnd
.pFrom = sFicDes
.lpszProgressTitle = "Eliminando el fichero
especificado"
End With
Call SHFileOperation(SHFileOp)
End Sub

Private Sub cmdMover_Click()


' Mover
Dim SHFileOp As SHFILEOPSTRUCT
' Asignar el valor de las opciones
AsignarFlags
sFicOri = txtOri & vbNullChar & vbNullChar
sFicDes = txtDes & vbNullChar & vbNullChar
With SHFileOp
.wFunc = FO_MOVE
.fFlags = iFlags
.hWnd = Me.hWnd
.pFrom = sFicOri
.pTo = sFicDes
.lpszProgressTitle = "Moviendo los ficheros
especificados"
End With
Call SHFileOperation(SHFileOp)
End Sub
Private Sub Form_Load()
Dim i As Long
sFicOri = App.Path & "\Prueba.txt"
sFicDes = App.Path & "\Temporal\Prueba.txt"
txtOri = sFicOri
txtDes = sFicDes
' Crear el fichero de prueba.txt
i = FreeFile
Open sFicOri For Output As i
Print #i, "Fichero de prueba"
Close
'

End Sub
Private Sub AsignarFlags()
' Ajusta el valor del flag, segn las opciones
seleccionadas
iFlags = 0
If chkOpciones(cFOF_ALLOWUNDO) Then _
iFlags = iFlags + FOF_ALLOWUNDO
If chkOpciones(cFOF_FILESONLY) Then _
iFlags = iFlags + FOF_FILESONLY
If chkOpciones(cFOF_MULTIDESTFILES) Then _
iFlags = iFlags + FOF_MULTIDESTFILES
If chkOpciones(cFOF_NOCONFIRMATION) Then _
iFlags = iFlags + FOF_NOCONFIRMATION
If chkOpciones(cFOF_NOCONFIRMMKDIR) Then _
iFlags = iFlags + FOF_NOCONFIRMMKDIR
If chkOpciones(cFOF_RENAMEONCOLLISION) Then _
iFlags = iFlags + FOF_RENAMEONCOLLISION
If chkOpciones(cFOF_SILENT) Then _
iFlags = iFlags + FOF_SILENT
If chkOpciones(cFOF_SIMPLEPROGRESS) Then _
iFlags = iFlags + FOF_SIMPLEPROGRESS
End Sub
22.- Seleccionar carpetas e incluso ficheros, usando SHBrowseForFolder
(13/May)
Otra funcin del API de Windows que puede darle un "look" ms apropiado
a nuestras aplicaciones:
Mostrar el cuadro de dilogo de seleccionar carpetas (o directorios) que usa
el propio Windows.
La funcin del API que se encarga de hacerlo es: SHBrowseForFolder,
aunque para poder sacarle el jugo nos tendremos que apoyar en otras
funciones, una de ellas no es "redistribuible" y debe estar ya instalada en el

sistema, de hecho todas las DLLs del API de Windows ya deben estar
instaladas en el sistema... por tanto slo podremos usar estas funciones si
previamente estn instaladas... el que avisa.
Dejemos las partes "legalistas" a un lado y vamos a centrarnos en lo que
interesa: saber cmo usarla.
Esta funcin no es tan "intuitiva" como el resto y se basa, como otras
muchas de la librera Shell, en unos parmetros que se pasan en forma de
datos asignados a un tipo definido, en este caso es: Browseinfo.
En este tipo definido (UDT), asignaremos lo que queremos que ese cuadro
de dilogo nos muestre, por ejemplo se le puede decir que tambin nos
permita seleccionar ficheros, adems de carpetas, el ttulo que queramos
que tenga, etc.
Lo que es un poco ms complicado de indicarle es el directorio por el que
debe empezar a mostrar, (por defecto empieza en el Escritorio); para
indicarle el directorio por defecto hay que recurrir a la subclasificacin, por
suerte, desde la versin 5.0 del Visual Basic es algo ms fcil, ya que
disponemos de AddressOf, el problema es que la subclasificacin se hace
asignando a uno de los parmetros del tipo definido la direccin de la
funcin que se encargar de procesar esos mensajes... por desgracia
AddressOf no devuelve ningn valor, y lo que necesitamos es poder asignar
a una variable la direccin de memoria de una funcin creada en Visual
Basic... Cmo lo solucionaremos? Creando una funcin que devuelva ese
valor... ya vers el cdigo y lo comprenders mejor.
Aqu tienes el cdigo y una "foto" del formulario de prueba en pleno
funcionamiento.
Como puedes comprobar, puedes especificar si quieres empezar a
"browsear" por una carpeta determinada y tambin si quieres seleccionar
ficheros, adems de poder seleccionar carpetas.

Nos vemos.
Guillermo
P.S.
Por suerte, con el Visual Basic 6.0 se incluyen los CDs de la MSDN Library
de Microsoft, que es de dnde he sacado parte del cdigo que he usado... el
problema es que los artculos estn en ingls... pero algo es algo...
Espero que te sea de utilidad...

Cambios del 14/May/99:


Haciendo caso del consejo del colega Eduardo Morcillo, aqu te digo los
cambios que habra que hacer para poder especificar el ttulo de la ventana,
para que no salga el que pone por defecto, en ingls: "Browse for folder",
sino el que nosotros queramos.
Lo primero que hay que hacer es aadir un nuevo parmetro a la funcin
BrowseForFolder para que acepte el Caption que queremos mostrar.
Tambin he cambiado la funcin Callback para que "siempre" sea llamada,

de esta forma, si se especifica el path de inicio y/o el ttulo a mostrar, se


llamarn a las funciones apropiadas del API.
Para poder cambiar el ttulo de una ventana, sabiendo el "handle" (hWnd),
simplemente llamaremos a SetWindowText.
He dejado el cdigo tal y como estaba originalmente, si quieres ver los
cambios, estn al final.

'
'///////////////////////////////////////////////////////
///////////////////////
'/////
BAS

ESTE CDIGO INSERTALO EN UN MDULO


/////

'///////////////////////////////////////////////////////
///////////////////////
'
'----------------------------------------------------------------------------' Mdulo con las declaraciones y funciones para
BrowseForFolder
(12/May/99)
'

' Guillermo 'guille' Som, 1999


'----------------------------------------------------------------------------Option Explicit
'///////////////////////////////////////////////////////
///////////////////////
' Variables, constantes y funciones para usar con
BrowseForFolder
(12/May/99)
'///////////////////////////////////////////////////////
///////////////////////
'
Private sFolderIni As String
'
Private Const WM_USER = &H400&
Public Const MAX_PATH = 260&
'
' Tipo para usar con SHBrowseForFolder
Private Type BrowseInfo
hWndOwner
del formulario

As Long

' hWnd

pIDLRoot
As Long
Especifica el pID de la carpeta inicial

'

pszDisplayName
del item seleccionado

As String

' Nombre

lpszTitle
a mostrar encima del rbol

As String

' Ttulo

ulFlags

As Long

'

lpfnCallback
Funcin CallBack

As Long

'

lParam
As Long
'
Informacin extra a pasar a la funcin Callback
iImage

As Long

'

End Type
'
'// Browsing for directory.
Public Const BIF_RETURNONLYFSDIRS = &H1&
finding a folder to start document searching

'// For

Public Const BIF_DONTGOBELOWDOMAIN = &H2&


starting the Find Computer

'// For

Public Const BIF_STATUSTEXT = &H4&


Public Const BIF_RETURNFSANCESTORS = &H8&
Public Const BIF_EDITBOX = &H10&

Public Const BIF_VALIDATE = &H20&


insist on valid result (or CANCEL)

'//

'
Public Const BIF_BROWSEFORCOMPUTER = &H1000&
Browsing for Computers.

'//

Public Const BIF_BROWSEFORPRINTER = &H2000&


Browsing for Printers

'//

Public Const BIF_BROWSEINCLUDEFILES = &H4000&


Browsing for Everything

'//

'
'// message from browser
Public Const BFFM_INITIALIZED = 1
Public Const BFFM_SELCHANGED = 2
Public Const BFFM_VALIDATEFAILED = 3
lParam:szPath ret:1(cont),0(EndDialog)

'//

'Public Const BFFM_VALIDATEFAILEDW = 4&


lParam:wzPath ret:1(cont),0(EndDialog)

'//

'
'// messages to browser
Public Const BFFM_SETSTATUSTEXT = (WM_USER + 100)
Public Const BFFM_ENABLEOK = (WM_USER + 101)
Public Const BFFM_SETSELECTION = (WM_USER + 102)
'Public Const BFFM_SETSELECTIONW = (WM_USER + 103&)
'Public Const BFFM_SETSTATUSTEXTW = (WM_USER + 104&)
'
Private Declare Function SHBrowseForFolder Lib
"shell32.dll" _
(lpbi As BrowseInfo) As Long
'
Private Declare Sub CoTaskMemFree Lib "OLE32.DLL" _
(ByVal hMem As Long)
'
Private Declare Function SHGetPathFromIDList Lib
"shell32.dll" _
(ByVal pidList As Long, ByVal lpBuffer As
String) As Long
'
Private Declare Function SendMessage Lib "user32.dll"
Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Any) As Long

Public Function BrowseFolderCallbackProc(ByVal hWndOwner


As Long, _
ByVal uMSG As
Long, _
ByVal lParam As
Long, _
ByVal pData As
Long) As Long
' Llamada CallBack para usar con la funcin
BrowseForFolder
(12/May/99)
Dim szDir As String
On Local Error Resume Next
Select Case uMSG
'------------------------------------------------------------------------' Este mensaje se enviar cuando se inicia el
dilogo,
' entonces es cuando hay que indicar el directorio
de inicio.
Case BFFM_INITIALIZED
' El path de inicio ser el directorio indicado,
' si no se ha asignado, usar el directorio
actual
If Len(sFolderIni) Then
szDir = sFolderIni & Chr$(0)
Else
szDir = CurDir$ & Chr$(0)
End If
' WParam

ser TRUE

si se especifica un path.

'

ser FALSE si se especifica un pIDL.

Call SendMessage(hWndOwner, BFFM_SETSELECTION,


1&, ByVal szDir)
'------------------------------------------------------------------------' Este mensaje se produce cuando se cambia el
directorio
' Si nuestro form est subclasificado para recibir
mensajes,
' puede interceptar el mensaje BFFM_SETSTATUSTEXT
' para mostrar el directorio que se est
seleccionando.

Case BFFM_SELCHANGED
szDir = String$(MAX_PATH, 0)
' Notifica a la ventana del directorio
actualmente seleccionado,
' (al menos en teora, ya que no lo hace...)
If SHGetPathFromIDList(lParam, szDir) Then
'Debug.Print szDir
Call SendMessage(hWndOwner,
BFFM_SETSTATUSTEXT, 0&, ByVal szDir)
End If
Call CoTaskMemFree(lParam)
End Select
Err = 0
BrowseFolderCallbackProc = 0
'----------------------------------------------------------------------------' Este es el cdigo de C en el que est basada esta
funcin Callback
' Cdigo obtenido de la MSDN Library de Microsoft:
' HOWTO: Browse for Folders from the Current Directory
' Article ID: Q179378
'
'

TCHAR szDir[MAX_PATH];

'
'
'

switch(uMsg) {
case BFFM_INITIALIZED: {

'
if
GetCurrentDirectory(sizeof(szDir)/sizeof(TCHAR),
'

szDir)) {

'
passing a path.

// WParam is TRUE since you are

'
passing a pidl.

// It would be FALSE if you were

'
SendMessage(hwnd,BFFM_SETSELECTION,TRUE,(LPARAM)szDir);
'

'

break;

'

'

case BFFM_SELCHANGED: {

'
// Set the status window to the
currently selected path.
'
lp ,szDir)) {

if (SHGetPathFromIDList((LPITEMIDLIST)

'
SendMessage(hwnd,BFFM_SETSTATUSTEXT,0,(LPARAM)szDir);
'

'

break;

'

'

default:

'

break;

'

'

return 0;

'----------------------------------------------------------------------------End Function

Public Function rtnAddressOf(lngProc As Long) As Long


' Devuelve la direccin pasada como parmetro
' Esto se usar para asignar a una variable la
direccin de una funcin
' o procedimiento.
' Por ejemplo, si en un tipo definido se asigna a
una variable la direccin
' de una funcin o procedimiento
rtnAddressOf = lngProc
End Function

Public Function BrowseForFolder(ByVal hWndOwner As Long,


ByVal sPrompt As String, _
Optional sInitDir As String = "",
_
Optional ByVal lFlags As Long =
BIF_RETURNONLYFSDIRS) As String
' Muestra el dilogo de seleccin de directorios de
Windows
' Si todo va bien, devuelve el directorio
seleccionado
' Si se cancela, se devuelve una cadena vaca y se
produce el error 32755
'

' Los parmetros de entrada:


'

El hWnd de la ventana

'

El ttulo a mostrar

'

Opcionalmente el directorio de inicio

'
En lFlags se puede especificar lo que se podr
seleccionar:
'

BIF_BROWSEINCLUDEFILES, etc.

'

por defecto es: BIF_RETURNONLYFSDIRS

'
Dim iNull As Integer
Dim lpIDList As Long
Dim lResult As Long
Dim sPath As String
Dim udtBI As BrowseInfo
On Local Error Resume Next
With udtBI
.hWndOwner = hWndOwner
' Ttulo a mostrar encima del rbol de seleccin
.lpszTitle = sPrompt & vbNullChar
' Que es lo que debe devolver esta funcin
.ulFlags = lFlags
'.ulFlags = lFlags Or BIF_RETURNONLYFSDIRS
'
' Si se especifica el directorio por el que se
empezar...
If Len(sInitDir) Then
' Asignar la variable que contendr el
directorio de inicio
sFolderIni = sInitDir
' Indicar la funcin Callback a usar.
' Como hay que asignar esa direccin a una
variable,
' se usa una funcin "intermedia" que
devuelve el valor
' del parmetro pasado... es decir: la
direccin de la funcin!
.lpfnCallback = rtnAddressOf(AddressOf
BrowseFolderCallbackProc)
End If
End With

Err = 0
On Local Error GoTo 0
' Mostramos el cuadro de dilogo
lpIDList = SHBrowseForFolder(udtBI)
'
If lpIDList Then
' Si se ha seleccionado un directorio...
'
' Obtener el path
sPath = String$(MAX_PATH, 0)
lResult = SHGetPathFromIDList(lpIDList, sPath)
Call CoTaskMemFree(lpIDList)
' Quitar los caracteres nulos del final
iNull = InStr(sPath, vbNullChar)
If iNull Then
sPath = Left$(sPath, iNull - 1)
End If
Else
' Si se ha pulsado en cancelar...
'
' Devolver una cadena vaca y asignar un error
sPath = ""
With Err
.Source = "MBrowseFolder::BrowseForFolder"
.Number = 32755
.Description = "Cancelada la operacin de
BrowseForFolder"
End With
End If
BrowseForFolder = sPath
End Function

'///////////////////////////////////////////////////////
///////////////////////
' Este cdigo insertalo en un formulario que tenga un
botn llamado cmdSelDir,

' un TextBox llamado Text1, un CheckBox llamado Check1 y


otro llamado chkIncludeFiles
'///////////////////////////////////////////////////////
///////////////////////
'
'----------------------------------------------------------------------------' Ejemplo de BrowseForFolder y asignacin del directorio
de inicio (12/May/99)
'
' Guillermo 'guille' Som, 1999
'----------------------------------------------------------------------------Option Explicit

Private Sub cmdSelDir_Click()


' Muestra el dilogo de seleccionar directorio
' Si se marca el Check1, se empezar por el
directorio indicado
Dim sDir As String
Dim lFlags As Long
' Para saber si se ha producido el "error" al
cancelar...
' no es necesrio interceptar errores:
'On Local Error Resume Next
lFlags = BIF_RETURNONLYFSDIRS
' Si se quiere seleccionar ficheros
If chkIncludeFiles Then
lFlags = lFlags Or BIF_BROWSEINCLUDEFILES
End If
Err = 0
If Check1 Then
sDir = BrowseForFolder(Me.hWnd, "Seleccionar
Directorio empezando en " & Text1, Text1, lFlags)
Else
sDir = BrowseForFolder(Me.hWnd, "Seleccionar
Directorio", , lFlags)

End If
If Err = 0 Then
Text1 = sDir
Else
MsgBox "Se ha cancelado la operacin, el error
devuelto es:" & vbCrLf & _
"Source: " & Err.Source & vbCrLf &
"Description: " & Err.Description
End If
' Pero si es conveniente poner de nuevo el valor a
cero
Err = 0
End Sub

Private Sub Form_Load()


' Asignamos al Text1 el directorio actual
Text1 = CurDir$
End Sub

Los cambios a realizar para poder mostrar un ttulo en la ventana de


seleccin de carpetas:
' En la parte general de declaraciones del mdulo BAS:
'
' Variable para guardar el Caption a mostrar
Private sBFFCaption As String

' Declaracin de la funcin del API para cambiar el


ttulo de una ventana
Private Declare Function SetWindowText Lib "user32.dll"
Alias "SetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String) As
Long

' Estas son las dos funciones para "browsear"

' La primera es la funcin callback, que se encargar de


inicializar la ventana de seleccin
Public Function BrowseFolderCallbackProc(ByVal hWndOwner
As Long, _
ByVal uMSG As

Long, _

ByVal lParam As

Long, _

ByVal pData As

Long) As Long

' Llamada CallBack para usar con la funcin


BrowseForFolder
(12/May/99)
Dim szDir As String
On Local Error Resume Next
Select Case uMSG
'------------------------------------------------------------------------' Este mensaje se enviar cuando se inicia el
dilogo,
' entonces es cuando hay que indicar el directorio
de inicio.
Case BFFM_INITIALIZED
' Si se ha asignado el path de inicio, empezar
por ese path
If Len(sFolderIni) Then
szDir = sFolderIni & Chr$(0)
' WParam

ser TRUE

si se especifica un

'

ser FALSE si se especifica un

path.
pIDL.
Call SendMessage(hWndOwner,
BFFM_SETSELECTION, 1&, ByVal szDir)
End If
' Si se ha especificado el ttulo de la ventana
If Len(sBFFCaption) Then
' Cambiar el ttulo de la ventana.
' Aunque parezca que se cambia el ttulo de
la ventana "propietaria",
seleccin.

' realmente se cambia el de la ventana de

Call SetWindowText(hWndOwner, sBFFCaption)


End If
'------------------------------------------------------------------------' Este mensaje se produce cuando se cambia el
directorio
' Si nuestro form est subclasificado para recibir
mensajes,
' puede interceptar el mensaje BFFM_SETSTATUSTEXT
' para mostrar el directorio que se est
seleccionando.
Case BFFM_SELCHANGED
szDir = String$(MAX_PATH, 0)
' Notifica a la ventana del directorio
actualmente seleccionado,
' (al menos en teora, ya que no lo hace...)
If SHGetPathFromIDList(lParam, szDir) Then
'Debug.Print szDir
Call SendMessage(hWndOwner,
BFFM_SETSTATUSTEXT, 0&, ByVal szDir)
End If
Call CoTaskMemFree(lParam)
End Select
Err = 0
BrowseFolderCallbackProc = 0
End Function

Public Function BrowseForFolder(ByVal hWndOwner As Long,


_
ByVal sPrompt As String,
_
Optional sInitDir As
String = "", _
Optional ByVal lFlags As
Long = BIF_RETURNONLYFSDIRS, _
Optional sCaption As
String = "") As String
' Muestra el dilogo de seleccin de directorios de
Windows

' Si todo va bien, devuelve el directorio


seleccionado
' Si se cancela, se devuelve una cadena vaca y se
produce el error 32755
'
' Los parmetros de entrada:
'

El hWnd de la ventana

'

El ttulo a mostrar encima del rbol

'

Opcionalmente el directorio de inicio

'
En lFlags se puede especificar lo que se podr
seleccionar:
'

BIF_BROWSEINCLUDEFILES, etc.

'

por defecto es: BIF_RETURNONLYFSDIRS

'

El Caption de la ventana

'
Dim iNull As Integer
Dim lpIDList As Long
Dim lResult As Long
Dim sPath As String
Dim udtBI As BrowseInfo
On Local Error Resume Next
With udtBI
.hWndOwner = hWndOwner
' Ttulo a mostrar encima del rbol de seleccin
.lpszTitle = sPrompt & vbNullChar
' Que es lo que debe devolver esta funcin
.ulFlags = lFlags
'
' Asignar el caption de la ventana
sBFFCaption = sCaption
'
' Asignar la variable que contendr el
directorio de inicio
sFolderIni = sInitDir
'
' Indicar la funcin Callback a usar.
'
Nota:
cambiar el caption

Esto slo es necesario si se quiere

'

y especificar el directorio de

inicio.
'
' Como hay que asignar esa direccin a una
variable,
' se usa una funcin "intermedia" que devuelve

el valor

' del parmetro pasado... es decir: la


direccin de la funcin!
.lpfnCallback = rtnAddressOf(AddressOf
BrowseFolderCallbackProc)
End With
Err = 0
On Local Error GoTo 0
' Mostramos el cuadro de dilogo
lpIDList = SHBrowseForFolder(udtBI)
'
If lpIDList Then
' Si se ha seleccionado un directorio...
'
' Obtener el path
sPath = String$(MAX_PATH, 0)
lResult = SHGetPathFromIDList(lpIDList, sPath)
Call CoTaskMemFree(lpIDList)
' Quitar los caracteres nulos del final
iNull = InStr(sPath, vbNullChar)
If iNull Then
sPath = Left$(sPath, iNull - 1)
End If
Else
' Si se ha pulsado en cancelar...
'
' Devolver una cadena vaca y asignar un error
sPath = ""
With Err
.Source = "MBrowseFolder::BrowseForFolder"
.Number = 32755
.Description = "Cancelada la operacin de
BrowseForFolder"
End With

End If
BrowseForFolder = sPath
End Function

' Este es el cdigo que hay que cambiar en el


procedimiento cmdSelDir_Click:
' Si te fijas, slo tendrs que aadirle al final un
parmetro con el ttulo de la ventana.

Err = 0
If Check1 Then
sDir = BrowseForFolder(Me.hWnd, "Seleccionar
Directorio empezando en " & Text1, Text1, _
lFlags, "Ttulo de la ventana")
Else
sDir = BrowseForFolder(Me.hWnd, "Seleccionar
Directorio", , _
lFlags, "Ttulo de la ventana")
End If
23.- cQueryReg: Revisin de la clase para manejar el registro del sistema
(12/Jun/99)
Ya he probado esta clase con el Windows NT (realmente con el Windows
2000 Professional Beta 3) y aqu estn los cambios de los "apaillos" que le
he tenido que hacer... espero que funcione bien en los dems NTs... ya me
contars si no es as... aunque preferira esos comentarios con las
soluciones... je, je...
No te voy a explicar mucho... slo pondr el cdigo y el link para los
ejemplos, que sern casi los mismos que en las revisiones anteriores... Los
fallillos que he encontrado era de que los parmetros no eran los correctos,
as que he intentado solucionarlo, con algn que otro cuelgue de por
medio... pero al final he conseguido que funcione...
Otra de las cosillas que ahora contempla es que acepta y "casi" entiende,
dos nuevos tipos de datos del registro:
REG_EXPAND_SZ y REG_MULTI_SZ.
El primero, son cadenas con referencias de variables del entorno, por ahora
slo se leen, pero no se interpretan esas variables del entorno... eso puede
que en otra revisin.
El segundo es un tipo especial de cadenas, realmente mltiples cadenas...

Tengo que recomendarte que no te fies al 100% de los resultados... lo


mejor que hars ser comprobar en tu caso particular, ya que, no he
probado a fondo esos dos tipos de datos...
Lo que he probado con el Windows 2000 es:
-Leer las subclaves de una clave,
-Leer los valores de una subclave,
-Registrar una extensin,
-Quitar una extensin del registro,
-Crear una nueva clave (con y sin subclaves),
-Asignar valores de Cadena, Numrico y Binario,
-Leer de esos tres tipos (dems de los otros dos mencionados),
-Borrar valores de los tres tipos bsicos.
Osea que he probado las operaciones ms habituales.
Aqu tienes un form de prueba con varias de esas operaciones, no recuerdo
si este ejemplo ya lo puse, pero... aqu lo pongo de nuevo, (el cdigo est
en el fichero ZIP)

El cdigo de la clase tambin est en el fichero ZIP y es "casi" el mismo que


en las revisiones anteriores, an as, aqu lo tienes de nuevo... busca
(12/Jun/99) para ver los cambios.

'
'----------------------------------------------------------------------------' cQueryReg
(13/Ago/98)
' Clase para obtener valores del Registro del Sistema
'
' Revisin 0.01 (18/Ago/98) Funciones de crear/borrar
claves/valores
' Revisin 0.02 (12/Oct/98) Nueva funcin para
enumeracin de claves
' Revisin 0.03 (15/Oct/98) Importar/Exportar claves del
registro
'

(slo exportar...)

' Revisin 0.04 (16/Dic/98) Modificadas las funciones de


obtener los
'

directorios del sistema,

'
de usarlas.

no se ha cambiado la forma

'
' Revisin 0.10 (12/Jun/99) Probado en Windows 2000
Professional Beta 3
'
bien...

y parece que funciona

'
' La informacin para crear las funciones estn tomadas
de ejemplos
' y valores obtenidos en el cdigo del Setup1.vbp
' y de artculos incluidos en los CDs del MSDN Library.
'
' De algn sitio tena que sacar la
informacin... !!!
'
' Guillermo 'guille' Som, 1998-99
<mensaje@elguille.info>
'
'----------------------------------------------------------------------------'

Mtodo

Descripcin

'

------

-----------

'
AsociarExt
programa

Asociar una extensin con un

'
Tambin sirve para aadir comandos a
extensiones existentes

'
CloseKey
Cierra la clave abierta usando el
handle pasado como parmetro
'
DeleteKey
especificado

Borra la clave o el valor

'
DeleteKeyNT
Borra la clave especificada y sus
subclaves y valores, para usar con Windows NT y Windows
98
'
DeleteKeyWin95 En Windows 95, borra la clave
especificada y sus subclaves y valores. En Windows NT y
Windows 98 no funcionar si la clave indicada tiene
subclaves.
'
DesasociarExt
del registro)

Desasociar la extensin (la borra

'
EnumKeys
Enumera todas las subclaves de la
clave indicada y las devuelve en un array de tipo String
que se pasa como parmetro.
'
EnumValues
Enumera todos los valores de la
clave indicada y las devuelve en un array de tipo String
que se pasa como parmetro.
'
GetFolder
Devuelve el path de la carpeta
"especial" del sistema.
'
El parmetro espera un nombre del
tipo de carpeta a obtener,
'
sistema

ver Nombres de directorios del

'
GetReg
Obtener un valor, de cualquier tipo,
de una entrada del registro
'
GetRegBinary
Obtener un valor binario de una
entrada del registro
'
GetRegDWord
Obtener un valor DWORD de una
entrada del registro
'
GetRegString
Obtener un valor cadena de una
entrada del registro
'

QueryRegBase

Busca una entrada en el registro

'
RegSaveKey
Guarda en un fichero el contenido de
una clave, las subclaves y datos.
'
El formato no es ASCII, es un
formato "propio" que se puede usar con la funcin
RegLoadKey (an no implementada)
'
RTrimZero
Chr$(0)

Devuelve una cadena hasta el primer

'
SetReg
la clave indicada.

Asigna un valor de cualquier tipo a

'
ShellFolders
Devuelve una coleccin con los
Nombres de directorios del sistema
'
referencia.

o los paths a los que hacen

'

Depender del parmetro pasado,

'
paths

por defecto False para devolver los

'
' Estas funciones simplemente llaman a la funcin del
API de windows, (el nombre de la funcin del API empieza
con Reg, salvo que se indique lo contrario)
'

EnumKeyEx

'
EnumValue
RegEnumValue, usarla para tipos
diferentes de String
'
EnumValueString RegEnumValue, usarla para tipos
String
'

OpenKeyEx

'
OpenKeyQuery
RegOpenKeyEx, abre una clave para
consultar informacin
'

QueryInfoKey

'
RegSetValue2
con cadenas.

RegSetValueEx, pero para usar slo

'
Esta funcin asigna el valor por
defecto de la clave indicada.
'
"Default".

Es decir, el "Predeterminado" o

'----------------------------------------------------------------------------' La funcin / mtodo GetReg devolver el valor


adecuado,
' si se ha encontrado la clave especificada en el
registro.
'
' Las funciones GetRegXXX se usarn para asegurarnos que
el valor
' devuelto es del tipo especificado.
' Por ejemplo:
'
si se usa GetRegString y el valor de la clave
indicada
'

no es del tipo cadena, se devolver una cadena vaca

'=======================================================
=================
'=== NOTA: Las he dejado para poder ver cmo se usaran
segn el tipo ===
'=======================================================
=================
'----------------------------------------------------------------------------Option Explicit

Private colShellFolders As Collection


Private colShellFoldersKey As Collection
Private Declare Function GetWindowsDirectory Lib
"kernel32" Alias "GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As

Long

Private Declare Function GetSystemDirectory Lib


"kernel32" Alias "GetSystemDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As

Long

' Registry manipulation API's (32-bit)


' Claves del Registro
Public Enum eHKEY
HKEY_CLASSES_ROOT = &H80000000
HKEY_CURRENT_USER = &H80000001
HKEY_LOCAL_MACHINE = &H80000002
HKEY_USERS = &H80000003
'
HKEY_PERFORMANCE_DATA = &H80000004

' Slo para NT

HKEY_CURRENT_CONFIG = &H80000005
HKEY_DYN_DATA = &H80000006
'
HKEY_FIRST = HKEY_CLASSES_ROOT
HKEY_LAST = HKEY_DYN_DATA
End Enum
'
'
HKEY_CLASSES_ROOT es un duplicado de
HKEY_LOCAL_MACHINE\Software\Classes
'
HKEY_CURRENT_USER es un duplicado de HKEY_USERS\
[Usuario]
'
'
Public Enum eHKEYError
ERROR_SUCCESS = 0
error
ERROR_NONE = 0

'Todo correcto, sin


'

"

"
'The configuration
registry...

'ERROR_BADDB = 1
corrupt

'database is

'1009&
'ERROR_BADKEY = 2

'key is invalid

'1010&
'Tambin declarada como:
ERROR_FILE_NOT_FOUND = 2&
cuando se abre

'este error ocurre


'una clave y no

existe
'ERROR_CANTOPEN = 3
opened

'key could not be

'1011&
'ERROR_CANTREAD = 4

'key could not be

read
'1012&
'ERROR_CANTWRITE = 5
written

'key could not be

'1013&
'Tambin declarada como:
ERROR_ACCESS_DENIED = 5&
ERROR_OUTOFMEMORY = 6&

'

ERROR_INVALID_PARAMETER = 7&

'

'ERROR_ACCESS_DENIED = 8&
ERROR_INVALID_PARAMETERS = 87&

'
'

'
ERROR_MORE_DATA = 234&
available

'More data is

ERROR_NO_MORE_ITEMS = 259&
available

'No more data is

ERROR_BADKEY = 1010&
se intenta acceder

'Se produce cuando

est abierta
'KEY_ALL_ACCESS = &H3F

'a una clave que no


'

'REG_OPTION_NON_VOLATILE = 0
End Enum
'
' Los tipos de datos posibles, algunos slo para Windows
NT

Public Enum eHKEYDataType


REG_NONE = 0&

'No value type

REG_SZ = 1&
string

'Unicode null terminated

REG_EXPAND_SZ = 2
string

'Unicode null terminated


'(with environment

variable references)
REG_BINARY = 3

'Free form binary

REG_DWORD = 4

'32-bit number

REG_DWORD_LITTLE_ENDIAN = 4 '32-bit number (same as


REG_DWORD)
REG_DWORD_BIG_ENDIAN = 5

'32-bit number

REG_LINK = 6

'Symbolic Link (unicode)

REG_MULTI_SZ = 7
strings

'Multiple Unicode

REG_RESOURCE_LIST = 8
resource map

'Resource list in the

REG_FULL_RESOURCE_DESCRIPTOR = 9
in the hardware description

'Resource list

REG_RESOURCE_REQUIREMENTS_LIST = 10
End Enum
' Standard rights, used later below
Const SYNCHRONIZE = &H100000
Const READ_CONTROL = &H20000
Const STANDARD_RIGHTS_ALL = &H1F0000
Const STANDARD_RIGHTS_REQUIRED = &HF0000
Const STANDARD_RIGHTS_EXECUTE = (READ_CONTROL)
Const STANDARD_RIGHTS_READ = (READ_CONTROL)
Const STANDARD_RIGHTS_WRITE = (READ_CONTROL)
' Security Access Mask
Public Enum eREGSAM
'Permission to:
KEY_QUERY_VALUE = &H1

'

query subkey

KEY_SET_VALUE = &H2

'

set subkey data

KEY_CREATE_SUB_KEY = &H4

'

create subkeys

KEY_ENUMERATE_SUB_KEYS = &H8
subkeys

'

enumerate

KEY_NOTIFY = &H10
notification

'

for change

data

KEY_CREATE_LINK = &H20
symbolic link

'

create a

'KEY_READ Combination of:


'

KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS,

'

KEY_NOTIFY access.

and
KEY_READ = ((STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE
Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY) And (Not
SYNCHRONIZE))
'KEY_WRITE Combination of:
'
access.

KEY_SET_VALUE and KEY_CREATE_SUB_KEY

KEY_WRITE = ((STANDARD_RIGHTS_WRITE Or KEY_SET_VALUE


Or KEY_CREATE_SUB_KEY) And (Not SYNCHRONIZE))
'Permission for read
access
KEY_EXECUTE = ((KEY_READ) And (Not SYNCHRONIZE))
'KEY_ALL_ACCESS Combination of:
'
KEY_QUERY_VALUE, KEY_SET_VALUE,
KEY_CREATE_SUB_KEY,
'
KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY and
KEY_CREATE_LINK access.
KEY_ALL_ACCESS = ((STANDARD_RIGHTS_ALL Or
KEY_QUERY_VALUE Or KEY_SET_VALUE Or KEY_CREATE_SUB_KEY
Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY Or
KEY_CREATE_LINK) And (Not SYNCHRONIZE))
'#define DELETE
(0x00010000L)
'KEY_DELETE = &H10000
End Enum
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Declare Function RegQueryInfoKey Lib
"advapi32.dll" Alias "RegQueryInfoKeyA" _
(ByVal hKey As Long, ByVal lpClass As String,
lpcbClass As Long, _

ByVal lpReserved As Long, lpcSubKeys As Long,


lpcbMaxSubKeyLen As Long, _
lpcbMaxClassLen As Long, lpcValues As Long,
lpcbMaxValueNameLen As Long, _
lpcbMaxValueLen As Long, lpcbSecurityDescriptor As
Long, _
lpftLastWriteTime As FILETIME) As Long
'Private Declare Function RegOpenKey Lib "advapi32.dll"
Alias "RegOpenKeyA" _
(ByVal hKey As Long, ByVal lpszSubKey As String, _
phkResult As Long) As Long
Private Declare Function RegOpenKeyEx Lib "advapi32.dll"
Alias "RegOpenKeyExA" _
(ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal ulOptions As Long, ByVal samDesired As Long, _
phkResult As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll"
_
(ByVal hKey As Long) As Long
Private Declare Function RegEnumValue Lib "advapi32.dll"
Alias "RegEnumValueA" _
(ByVal hKey As Long, ByVal dwIndex As Long, _
_

ByVal lpValueName As String, lpcbValueName As Long,

ByVal lpReserved As Long, lpType As Long, lpData As


Any, _
lpcbData As Long) As Long
Private Declare Function RegCreateKey Lib "advapi32.dll"
Alias "RegCreateKeyA" _
(ByVal hKey As Long, ByVal lpszSubKey As String, _
phkResult As Long) As Long
'
'Windows 95:
'
The RegDeleteKey function deletes a subkey and all
its descendants.
'Windows NT:
'
The RegDeleteKey function deletes the specified
subkey.
'

The subkey to be deleted must not have subkeys.

'
Private Declare Function RegDeleteKey Lib "advapi32.dll"
Alias "RegDeleteKeyA" _

(ByVal hKey As Long, ByVal lpszSubKey As String) As


Long
Private Declare Function RegDeleteValue Lib
"advapi32.dll" Alias "RegDeleteValueA" _
(ByVal hKey As Long, ByVal szValueName As String) As

Long

Private Declare Function RegEnumKey Lib "advapi32.dll"


Alias "RegEnumKeyA" _
(ByVal hKey As Long, ByVal iSubKey As Long, _
ByVal lpszName As String, ByVal cchName As Long) As

Long

Private Declare Function RegEnumKeyEx Lib "advapi32.dll"


Alias "RegEnumKeyExA" _
(ByVal hKey As Long, ByVal dwIndex As Long, _
ByVal lpName As String, lpcbName As Long, _
ByVal lpReserved As Long, ByVal lpClass As String, _
lpcbClass As Long, lpftLastWriteTime As FILETIME) As
Long
Private Declare Function RegQueryValue Lib
"advapi32.dll" Alias "RegQueryValueA" _
(ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal lpValue As String, lpcbValue As Long) As Long
Private Declare Function RegQueryValueEx Lib
"advapi32.dll" Alias "RegQueryValueExA" _
(ByVal hKey As Long, ByVal lpszValueName As String,
_
ByVal dwReserved As Long, lpdwType As Long, _
lpbData As Any, cbData As Long) As Long
' The RegSetValue function sets the data for the default
or unnamed
' value of a specified registry key. The data must be a
text string.
Private Declare Function RegSetValue Lib "advapi32.dll"
Alias "RegSetValueA" _
(ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal dwType As Long, ByVal lpData As String, _
ByVal cbData As Long) As Long
'
' The RegSetValueEx function sets the data and type of a
' specified value under a registry key.

'
'lpValueName:
' Pointer to a string containing the name of the value
to set.
' If a value with this name is not already present in
the key,
' the function adds it to the key.
' If lpValueName is NULL or an empty string, "", the
function sets
' the type and data for the key's unnamed or default
value.
'
'On Windows 95, the type of a key's default value is
always REG_SZ,
' so the dwType parameter must specify REG_SZ for an
unnamed value.
'On Windows 98, an unnamed value can be of any type.
'
Private Declare Function RegSetValueEx Lib
"advapi32.dll" Alias "RegSetValueExA" _
(ByVal hKey As Long, ByVal lpszValueName As String,
_
ByVal dwReserved As Long, ByVal fdwType As Long, _
lpbData As Any, ByVal cbData As Long) As Long
'
' Funciones del API para guardar y recuperar informacin
del registro.
'
'Private Type SECURITY_ATTRIBUTES
'

nLength As Long

'

lpSecurityDescriptor As Long

'

bInheritHandle As Long

'End Type
'
' RegSaveKey:
' El nombre guardado en Windows 95 slo permite nombres
cortos,
' si no se especifica el path se guardar en el
directorio del Windows.
' Adems se guardar con los atributos Hidden, Read-Only
y System
'

'Private Declare Function RegSaveKeyA Lib "advapi32.dll"


_
(ByVal hKey As Long, ByVal lpFile As String, _
lpSecurityAttributes As SECURITY_ATTRIBUTES) As Long
Private Declare Function RegSaveKeyA Lib "advapi32.dll"
_
(ByVal hKey As Long, ByVal lpFile As String, _
lpSecurityAttributes As Long) As Long
'RegLoadKey:
' En Windows 95 el nombre del fichero no permite nombres
largos
'
Private Declare Function RegLoadKeyA Lib "advapi32.dll"
_
(ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal lpFile As String) As Long
'----------------------------------------------------------------------------' Este cdigo est 'copiado' de un ejemplo de David
Janson
' Slo es necesario para Windows NT, ya que win95
permite borrar todas
' las subclaves
'
' Tambin hay que usarla en windows 98
'
'
' this gets a bit tricky since you can't delete a key
that has subkeys.
' We have to do this recursively.
errors (such as security

This code ignores

' problems) when they occur.


'----------------------------------------------------------------------------Private Function DeleteKeyNT(hParentKey As Long, szKey
As String) As Long
Dim hKey As Long
Dim lRet As eHKEYError
Dim cSubKeys As Long
Dim cbMaxSubKeyLen As Long
Dim cbSubKeyLen As Long

Dim dwIndex As Long


Dim ft As FILETIME
Dim szTempSubKey As String
Dim szSubKey As String
' open the key to look for subkeys
lRet = RegOpenKeyEx(hParentKey, szKey, 0,
KEY_ALL_ACCESS, hKey)
If Not lRet = ERROR_SUCCESS Then
' ERROR_ACCESS_DENIED (5)
DeleteKeyNT = lRet
Exit Function
End If
'lRet = RegQueryInfoKey(hKey, ByVal 0&, ByVal 0&, 0,
_
cSubKeys, cbMaxSubKeyLen, _
ByVal 0&, ByVal 0&, ft)

ByVal 0&, ByVal 0&, ByVal 0&,

lRet = RegQueryInfoKey(hKey, vbNullString, 0&, 0, _


cSubKeys, cbMaxSubKeyLen, _
0&, 0&, 0&, 0&, 0&, ft)
If Not lRet = ERROR_SUCCESS Then
' ERROR_INVALID_PARAMETERS (87)
DeleteKeyNT = lRet
Call RegCloseKey(hKey)
Exit Function
End If
' if there are subkeys, then recursively delete them
If cSubKeys > 0 Then
dwIndex = cSubKeys - 1
start at the end

'

cbMaxSubKeyLen = cbMaxSubKeyLen + 1
+1 for the null terminator

'

szTempSubKey = String(cbMaxSubKeyLen, "*")


buffer to get name back in

'

Do
cbSubKeyLen = cbMaxSubKeyLen

'lRet = RegEnumKeyEx(hKey, dwIndex,


szTempSubKey, cbSubKeyLen, 0, ByVal 0&, 0, ft)
lRet = RegEnumKeyEx(hKey, dwIndex,
szTempSubKey, cbSubKeyLen, 0&, vbNullString, 0&, ft)
If lRet = ERROR_SUCCESS Then
szSubKey = Left(szTempSubKey,

cbSubKeyLen)

Call DeleteKeyNT(hKey, szSubKey)


End If
dwIndex = dwIndex - 1
enumerate backwards

'

Loop While dwIndex >= 0


End If
' done enumerating subkeys.
delete it

Close this key and

Call RegCloseKey(hKey)
lRet = RegDeleteKey(hParentKey, szKey)
'If Not lRet = ERROR_SUCCESS Then
'

Exit Sub

'End If
DeleteKeyNT = lRet
End Function
Public Function GetRegDWord(ByVal sKey As String,
Optional ByVal sValue As String = "", Optional ByVal
hKey As eHKEY = HKEY_CURRENT_USER, Optional ByVal
bAsString As Boolean = False) As Variant
' Obtener un valor DWORD de una entrada del registro
'
' Parmetros de entrada:
'

sKey

'
sValue
obtener
'

hKey

'
bAsString
RegEdit

SubClave del registro


Nombre de la entrada que queremos
Clave principal del registro
Mostrar en formato al estilo del

' Devuelve:
'

el contenido de esa clave o una valor cero

'
Dim ret As Long
Dim hKey2 As Long

Dim rDT As eHKEYDataType


Dim lSize As Long
Dim lDWord As Long
hKey = ParseKey(sKey, hKey)
' Abrir la clave indicada
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_READ, hKey2)
' Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
' Leer esa entrada y obtener el tipo de dato,
longitud, etc.
ret = RegQueryValueEx(hKey2, sValue, 0&, rDT,
0&, lSize)
' Si es un valor DWORD
If rDT = REG_DWORD Then
' Leer los datos DWORD
ret = RegQueryValueEx(hKey2, sValue, 0&,
rDT, lDWord, lSize)
End If
' Cerrar la clave abierta
RegCloseKey hKey2
End If
' Devolver el valor ledo
If bAsString Then
' Al estilo de como se muestra con RegEdit
GetRegDWord = "0x" & Format$(Hex$(lDWord),
"00000000") & " (" & lDWord & ")"
Else
GetRegDWord = lDWord
End If
End Function
Public Function GetReg(ByVal sKey As String, Optional
ByVal sValue As String = "", Optional ByVal hKey As
eHKEY = HKEY_CURRENT_USER, Optional ByVal bAsString As
Boolean = False) As Variant
'------------------------------------------------------------------------' Obtener un valor de una entrada del registro

'
' Parmetros de entrada:
'

sKey

SubClave del registro

'
clave raiz

Se puede especificar el nombre de la

'

que se convertir al valor adecuado

'
sValue
obtener
'

hKey

Nombre de la entrada que queremos


Clave principal del registro.

'
Si en sKey se incluye, no es
necesario especificarla
'
Nota: este valor se obvia si se
indica la raiz en sKey.
'
bAsString
de RegEdit

Mostrarlo como una cadena, al estilo

' Devuelve:
'

el contenido de esa clave o un valor vaco

'
' Revisado para usarlo con Windows NT (Win2000 Pro
Beta 3)
(12/Jun/99)
'------------------------------------------------------------------------Dim lRet As Long
Dim hKey2 As Long
Dim rDT As eHKEYDataType
Dim retDT As eHKEYDataType
Dim lSize As Long
Dim sData As String
Dim aData() As Byte
Dim lDWord As Long
Dim i As Long
Dim sTmp As String
hKey = ParseKey(sKey, hKey)
' Valores por defecto
ReDim aData(0)
lDWord = 0
sData = ""
' Abrir la clave indicada

'lRet = RegOpenKeyEx(hKey, sKey, 0&,


KEY_QUERY_VALUE, hKey2)
lRet = RegOpenKeyEx(hKey, sKey, 0&, KEY_READ, hKey2)
' Si todo va bien (se ha podido abrir la clave)
If lRet = ERROR_SUCCESS Then
' Leer esa entrada y obtener el tipo de dato,
longitud, etc.
lRet = RegQueryValueEx(hKey2, sValue, 0&, retDT,
0&, lSize)
Select Case retDT
Case REG_DWORD
lRet = RegQueryValueEx(hKey2, sValue, 0&,
rDT, lDWord, lSize)
Case REG_EXPAND_SZ, REG_SZ, REG_MULTI_SZ
If lSize Then
sData = String$(lSize - 1, Chr$(0))
' Leer la cadena
'(el ByVal es porque est declarada como
Any)---v
lRet = RegQueryValueEx(hKey2, sValue,
0&, rDT, ByVal sData, lSize)
End If
Case Else ' Tratarlos como REG_BINARY
If lSize Then
ReDim aData(lSize)
'Leer los datos binarios
lRet = RegQueryValueEx(hKey2, sValue,
0&, rDT, aData(0), lSize)
End If
End Select
' Cerrar la clave abierta
RegCloseKey hKey2
End If
' Devolver el valor ledo
Select Case retDT
Case REG_DWORD
If bAsString Then
' Al estilo de como se muestra con RegEdit
GetReg = "0x" & Format$(Hex$(lDWord),
"00000000") & " (" & lDWord & ")"
Else

GetReg = lDWord
End If
Case REG_EXPAND_SZ, REG_SZ
GetReg = sData
Case REG_MULTI_SZ
' Mltiples cadenas, separadas por Chr$(0)
(12/Jun/99)
' La cadena termina en el ltimo Chr$(0)
'

For i = Len(sData) To 1 Step -1

'

If Mid$(sData, i, 1) = Chr$(0) Then

'

sData = Left$(sData, i - 1)

'

Exit For

'

End If

'

Next

'

' Sustituir los Chr$(0) por espacios

'

For i = 1 To Len(sData)

'

If Mid$(sData, i, 1) = Chr$(0) Then

'

Mid$(sData, i, 1) = " "

'
'

End If
Next
GetReg = RTrimZero(sData, True)
Case Else ' REG_BINARY
If bAsString Then
' Al estilo de como se muestra con RegEdit
For i = 0 To UBound(aData) - 1
'sTmp = sTmp & Hex$(aData(i)) & " "
' Los nmeros formateados a dos cifras

(12/Oct/98)

sTmp = sTmp & Format$(Hex$(aData(i)),

"00") & " "


Next

GetReg = sTmp
Else
GetReg = aData
End If
End Select
End Function
Public Function GetRegBinary(ByVal sKey As String,
Optional ByVal sValue As String = "", Optional ByVal

hKey As eHKEY = HKEY_CURRENT_USER, Optional ByVal


bAsString As Boolean = False) As Variant
' Obtener un valor binario de una entrada del
registro
'
' Parmetros de entrada:
'

sKey

SubClave del registro

'
sValue
obtener
'

Nombre de la entrada que queremos

hKey

Clave principal del registro

'
bAsString
de RegEdit

Mostrarlo como una cadena, al estilo

' Devuelve:
'

el contenido de esa clave o una valor cero

'
Dim ret As Long
Dim hKey2 As Long
Dim rDT As eHKEYDataType
Dim lSize As Long
Dim aData() As Byte
Dim i As Long
Dim sTmp As String
hKey = ParseKey(sKey, hKey)
ReDim aData(0)
' Abrir la clave indicada
'ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_QUERY_VALUE,
hKey2)
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_READ, hKey2)
' Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
' Leer esa entrada y obtener el tipo de dato,
longitud, etc.
ret = RegQueryValueEx(hKey2, sValue, 0&, rDT,
0&, lSize)
' Si es un valor binario
If rDT = REG_BINARY Then
If lSize Then
ReDim aData(lSize)

' Leer los datos binarios


ret = RegQueryValueEx(hKey2, sValue, 0&,
rDT, aData(0), lSize)
End If
End If
' Cerrar la clave abierta
RegCloseKey hKey2
End If
' Devolver el valor ledo
If bAsString Then
' Al estilo de como se muestra con RegEdit
For i = 0 To UBound(aData) - 1
sTmp = sTmp & Hex$(aData(i)) & " "
Next
GetRegBinary = sTmp
Else
GetRegBinary = aData
End If
End Function
Public Function GetRegString(ByVal sKey As String,
Optional ByVal sValue As String = "", Optional ByVal
hKey As eHKEY = HKEY_CURRENT_USER) As String
' Obtener un valor cadena de una entrada del
registro
'
' Parmetros de entrada:
'

sKey

'
sValue
obtener
'

hKey

Clave del registro


Nombre de la entrada que queremos
Clave principal del registro

' Devuelve:
'

el contenido de esa clave o una cadena vaca

'
Dim ret As Long
Dim hKey2 As Long
Dim rDT As eHKEYDataType
Dim sData As String
Dim lSize As Long
hKey = ParseKey(sKey, hKey)

' Abrir la clave indicada


'ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_QUERY_VALUE,
hKey2)
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_READ, hKey2)
' Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
' Leer esa entrada y obtener el tipo de dato,
longitud, etc.
ret = RegQueryValueEx(hKey2, sValue, 0&, rDT,
0&, lSize)
' Si es una cadena
If rDT = REG_SZ Then
If lSize Then
sData = String$(lSize - 1, Chr$(0))
' Leer la cadena
como Any)---v

' (el ByVal es porque est declarada

ret = RegQueryValueEx(hKey2, sValue, 0&,


rDT, ByVal sData, lSize)
End If
End If
' Cerrar la clave abierta
RegCloseKey hKey2
End If
' Devolver el valor ledo
GetRegString = sData
End Function
' Busca una entrada en el registro
Public Function QueryRegBase(ByVal sValue As String, _
Optional ByVal hKey As eHKEY
= HKEY_CLASSES_ROOT _
) As String
' Devuelve el valor de la entrada del registro
' Esta funcin se usar para los valores por defecto
'
Dim sBuf As String
Dim buflen As Long

' Nos aseguramos que hKey tenga el valor correcto


Select Case hKey
'Case HKEY_CLASSES_ROOT, HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE, HKEY_USERS
Case HKEY_FIRST To HKEY_LAST
' nada que hacer, todo correcto
Case Else
' Asignamos el valor por defecto
hKey = HKEY_CLASSES_ROOT
End Select
'On Local Error Resume Next
sBuf = String$(300, Chr$(0))
buflen = Len(sBuf)
' Buscar la entrada especificada y devolver el valor
asignado
If RegQueryValue(hKey, sValue, sBuf, buflen) =
ERROR_SUCCESS Then
If buflen > 1 Then
' El formato devuelto es ASCIIZ, as que
quitar el ltimo caracter
QueryRegBase = Left$(sBuf, buflen - 1)
Else
QueryRegBase = ""
End If
Else
QueryRegBase = ""
End If
'On Local Error GoTo 0
End Function
Private Function ParseKey(sKey As String, _
Optional ByVal hKey As eHKEY =
HKEY_CURRENT_USER _
) As eHKEY
'------------------------------------------------------------------------' Esta funcin se usa internamente (privada) para
convertir una cadena
' en la correspondiente clave raiz.

' El segundo parmetro es para poder usarlo en caso


que se pase como
' parmetro, pero normalmente ser totalmente
opcional.
'
' En sKey se devolver el valor de la clave una vez
quitada la clave
' principal.
'
'------------------------------------------------------------------------' NOTA del 14/Oct/98
'
En sKey se debe especificar el nombre de la
clave raiz.
'
La utilidad de esta funcin es que devuelve
el valor de esa
'
clave raiz y se usar en caso de que no
sepamos que clave es.
'
Si ya sabes el valor de la clave raiz, no es
necesario que
'

uses esta funcin.

'--------------------------------------------------------------------Dim i As Long
Dim sRootKey As String
' Comprobar si se indica la clave principal en sKey
i = InStr(sKey, "HKEY_")
If i Then
i = InStr(sKey, "\")
If i Then
sRootKey = Left$(sKey, i - 1)
sKey = Mid$(sKey, i + 1)
Else
sRootKey = sKey
sKey = ""
End If
' Por si se usan abreviaturas de las claves
ElseIf Left$(sKey, 5) = "HKCR\" Then
sRootKey = "HKEY_CLASSES_ROOT"
sKey = Mid$(sKey, 6)

ElseIf Left$(sKey, 5) = "HKCU\" Then


sRootKey = "HKEY_CURRENT_USER"
sKey = Mid$(sKey, 6)
ElseIf Left$(sKey, 5) = "HKLM\" Then
sRootKey = "HKEY_LOCAL_MACHINE"
sKey = Mid$(sKey, 6)
ElseIf Left$(sKey, 4) = "HKU\" Then
sRootKey = "HKEY_USERS"
sKey = Mid$(sKey, 5)
ElseIf Left$(sKey, 5) = "HKCC\" Then
sRootKey = "HKEY_CURRENT_CONFIG"
sKey = Mid$(sKey, 6)
ElseIf Left$(sKey, 5) = "HKDD\" Then
sRootKey = "HKEY_DYN_DATA"
sKey = Mid$(sKey, 6)
ElseIf Left$(sKey, 5) = "HKPD\" Then
sRootKey = "HKEY_PERFORMANCE_DATA"
sKey = Mid$(sKey, 6)
Else
' Nos aseguramos que kKey tenga el valor

correcto

Select Case hKey


'Case HKEY_CLASSES_ROOT, HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG,
HKEY_DYN_DATA
Case HKEY_FIRST To HKEY_LAST
'nada que hacer, todo correcto
Case Else
' Asignamos el valor por defecto
hKey = HKEY_CLASSES_ROOT
End Select
End If
' Si se ha indicado el nombre de la clave raiz
If Len(sRootKey) Then
Select Case sRootKey
Case "HKEY_CLASSES_ROOT"
hKey = HKEY_CLASSES_ROOT
Case "HKEY_CURRENT_USER"
hKey = HKEY_CURRENT_USER
Case "HKEY_LOCAL_MACHINE"

hKey = HKEY_LOCAL_MACHINE
Case "HKEY_USERS"
hKey = HKEY_USERS
Case "HKEY_CURRENT_CONFIG"
hKey = HKEY_CURRENT_CONFIG
Case "HKEY_DYN_DATA"
hKey = HKEY_DYN_DATA
Case "HKEY_PERFORMANCE_DATA"
hKey = HKEY_PERFORMANCE_DATA
Case Else
hKey = HKEY_CLASSES_ROOT
End Select
End If
ParseKey = hKey
End Function
Public Function OpenKeyEx(ByVal hKey As Long, ByVal
lpSubKey As String, _
ByVal ulOptions As Long, _
ByVal samDesired As eREGSAM,
phkResult As Long) As Long
' Abre una clave del registro, en phkResult devuelve
el handle de
' la clave abierta y se usar para los siguientes
accesos.
'
' ulOptions es un valor reservado que debe ser 0&
'
API

' Esta funcin simplemente llama a la original del


'

OpenKeyEx = RegOpenKeyEx(hKey, lpSubKey, 0&,


samDesired, phkResult)
End Function
Public Function OpenKeyQuery(ByVal hKey As Long, ByVal
lpSubKey As String, ByVal ulOptions As Long, ByVal
samDesired As eREGSAM, phkResult As Long) As Long
' Los parmetros: ulOptions (un valor reservado que
debe ser 0&)
'

y samDesired, no se tienen en cuenta

' pero se dejan por compatibilidad de parmetros de


RegOpenKeyEx
'
' Para usar otros valores de accesos, usar la
funcin OpenKeyEx
'
' Esta funcin simplemente llama a la original del
API
' Con las "peculiaridades" indicadas
'
OpenKeyQuery = RegOpenKeyEx(hKey, lpSubKey, 0&,
KEY_QUERY_VALUE, phkResult)
End Function
Public Function EnumValueString(ByVal hKey As Long,
ByVal dwIndex As Long, _
lpValueName As String, lpcbValueName As Long, _
lpReserved As Long, lpType As Long, lpData As
String, _
lpcbData As Long) As Long
'
API

' Esta funcin simplemente llama a la original del


' Slo para tipos String
'
EnumValueString = RegEnumValue(hKey, dwIndex, _
lpValueName, lpcbValueName,

_
lpReserved, lpType, ByVal
lpData, _
lpcbData)
End Function
Public Function EnumValue(ByVal hKey As Long, ByVal
dwIndex As Long, _
lpValueName As String, lpcbValueName As Long, _
_

lpReserved As Long, lpType As Long, lpData As Byte,


lpcbData As Long) As Long
'

API

' Esta funcin simplemente llama a la original del

' Usarla para tipos diferentes de String


'
EnumValue = RegEnumValue(hKey, dwIndex, _
lpValueName, lpcbValueName,

lpReserved, lpType, lpData,

lpcbData)
End Function
Public Function CloseKey(ByVal hKey As Long) As Long
' Cierra la clave abierta usando el handle hKey
'
' Esta funcin simplemente llama a la original del
API
'
CloseKey = RegCloseKey(hKey)
End Function
Public Function QueryInfoKey(ByVal hKey As Long,
lpcbMaxValueNameLen As Long) As Long
'
API

' Esta funcin simplemente llama a la original del


'
Dim lpftLastWriteTime As FILETIME

QueryInfoKey = RegQueryInfoKey(hKey, 0&, 0&, 0&, 0&,


0&, 0&, 0&, _
lpcbMaxValueNameLen, 0&, 0&,
lpftLastWriteTime)
End Function
Public Function EnumKeyEx(ByVal hKey As Long, ByVal
dwIndex As Long, lpName As String, lpcbName As Long) As
Long
'
' Esta funcin simplemente llama a la original del
API
'
Dim lpftLastWriteTime As FILETIME

EnumKeyEx = RegEnumKeyEx(hKey, dwIndex, lpName,


lpcbName, _
0&, 0&, 0&,
lpftLastWriteTime)
End Function
Public Function ShellFolders(Optional bSoloClaves As
Boolean = False) As Variant
' Devolver las claves de la clave Shell Folders
Dim sKey As String
Dim buf As String
Dim i As Long
Dim sValue As String
Dim iCount As Long
Dim colKeys() As String
Dim colShellFoldersKey As Collection
' Borrar el contenido de la coleccin
Set colShellFolders = Nothing
' Esta coleccin tendr los paths, el ndice ser la
clave
Set colShellFolders = New Collection
' En esta coleccin se guardarn las claves
' (slo se usa por si se indica bSoloClaves=True)
Set colShellFoldersKey = New Collection

'=======================================================
=======
'
'=== NOTA CACHONDA === por lo incomprensible...
' Es curioso, pero si utilizo estas intrucciones

aqu

' el bucle For iCount=0 to 1 no acaba nunca


'
'=======================================================
=======
'
'Para el directorio de windows

'buf = "WindowsDir"
'colShellFoldersKey.Add buf, buf
'colShellFolders.Add "Windows", buf
'
'Para el directorio de System
'buf = "SystemDir"
'colShellFoldersKey.Add buf, buf
'colShellFolders.Add "System", buf
'
'=======================================================
=======
For iCount = 0 To 1
' Enumerar el contenido de Shell Folders
If iCount = 0 Then
sKey =
"HKEY_USERS\.Default\Software\Microsoft\Windows\CurrentV
ersion\Explorer\Shell Folders"
Else
sKey =
"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVe
rsion"
End If
' Usar la funcin EnumValues
If EnumValues(colKeys(), sKey) Then
For i = 1 To UBound(colKeys) Step 2
' colKeys(i)

ser el nombre de la

' colKeys(i + 1)

ser el valor o dato

clave
almacenado
If iCount = 0 Then
colShellFoldersKey.Add colKeys(i),

colKeys(i)

colShellFolders.Add colKeys(i + 1),

colKeys(i)
Else

If InStr(colKeys(i + 1), ":\") Then


colKeys(i), colKeys(i)
1), colKeys(i)

colShellFoldersKey.Add
colShellFolders.Add colKeys(i +

End If
End If
Next
End If
Next
' Obtener el directorio de windows
buf = String$(300, Chr$(0))
i = GetWindowsDirectory(buf, Len(buf))
sValue = Left$(buf, i)
buf = "WindowsDir"
colShellFoldersKey.Add buf, buf
colShellFolders.Add sValue, buf
' Obtener el directorio de System
buf = String$(300, Chr$(0))
i = GetSystemDirectory(buf, Len(buf))
sValue = Left$(buf, i)
buf = "SystemDir"
colShellFoldersKey.Add buf, buf
colShellFolders.Add sValue, buf
If bSoloClaves Then
Set ShellFolders = colShellFoldersKey
Else
Set ShellFolders = colShellFolders
End If
Set colShellFoldersKey = Nothing
End Function
Private Sub Class_Initialize()
Set colShellFolders = New Collection
'Set colShellFoldersKey = New Collection
End Sub
Private Sub Class_Terminate()
Set colShellFolders = Nothing
'Set colShellFoldersKey = Nothing
End Sub

Public Function GetFolder(ByVal vIndex As Variant) As


String
' Devuelve el directorio de la clave indicada
' Si no est preparada la coleccin, prepararla
If colShellFolders.Count = 0 Then
Call ShellFolders
End If
On Local Error Resume Next
' Devolver el directorio de la clave indicada
GetFolder = colShellFolders(vIndex)
' Si da error es que no existe la clave que se
indica...
If Err Then
GetFolder = ""
End If
Err = 0
On Local Error GoTo 0
End Function
Public Function RegSetValue2(ByVal hKey As Long, ByVal
lpSubKey As String, _
ByVal dwType As
eHKEYDataType, lpData As String, _
ByVal cbData As Long) As
Long
'------------------------------------------------------------------------' Lo que dice la ayuda de Windows:
'
The RegSetValue function sets the data for the
default or unnamed
'
value of a specified registry key. The data must
be a text string.
'
' Funcin para compatibilidad con versiones
anteriores

'------------------------------------------------------------------------cbData = Len(lpData)
' Hay que usar ByVal porque est definida "As Any"
-------v
RegSetValue2 = RegSetValueEx(hKey, lpSubKey, 0&,
REG_SZ, ByVal lpData, cbData)
End Function
Public Sub AsociarExt(ByVal sExt As String, _
Optional ByVal sExe As String =

"", _
= "open", _
= True, _

Optional ByVal sCommand As String


Optional ByVal bDefault As Boolean
Optional ByVal sProgId As String =

"", _
String = "")

Optional ByVal sDescription As

'------------------------------------------------------------------------' Asociar una extensin con un programa


' Tambin sirve para aadir comandos a extensiones
existentes
'
' Parmetros:
'

sExt

Extensin a asociar

'

sExe

Path completo del programa

'

sProgId

Nombre de la clave asociada

'

sDescription

Descripcin de la extensin

'
sCommand
Abrir (open)

Clave a crear, por defecto es

'
bDefault
se usar por defecto

Si la clave indicada es la que

'
'------------------------------------------------------------------------'Para probar:
'tQR.AsociarExt ".cIt", "C:\Vb5_L\Cut-It\CutIt.exe", "open", False, "gsCutIt", "Cut-It (trocear y
unir archivos)"

'tQR.AsociarExt ".cIt", "C:\Windows\Notepad.exe",


"&Editar", True, "gsCutIt", "Cut-It (trocear y unir
archivos)"
'
'Slo se quitar el valor por defecto si se asigna a
otra clave.
'tQR.AsociarExt ".cIt", "", "open", True, "gsCutIt",
"Cut-It (trocear y unir archivos)"
'tQR.AsociarExt ".cIt", "", "&Editar", True,
"gsCutIt", "Cut-It (trocear y unir archivos)"
'------------------------------------------------------------------------Dim sDef As String
Dim hKey As Long
Dim phkResult As Long
Dim lRet As eHKEYError
Dim sValue As String
Dim sKey As String
Dim sAccess As String
' Quitar los espacios
sExt = Trim$(sExt)
sExe = Trim$(sExe)
sCommand = Trim$(sCommand)
sProgId = Trim$(sProgId)
sDescription = Trim$(sDescription)
' Si no se especifica el punto
If InStr(sExt, ".") = 0 Then
sExt = "." & sExt
End If
' Comprobar el tipo de ejecutable, si no se
especifica la extensin
' se aade .exe
If Len(sExe) Then
If InStr(sExe, ".") = 0 Then
sExe = sExe & ".exe"
End If
sExe = sExe & " "
End If

' Si no se especifica el ProgId


If Len(sProgId) = 0 Then
sProgId = "progID" & sExt
End If
' Si no se especifica la descripcin
If Len(sDescription) = 0 Then
sDescription = "Descripcin de " & sProgId
End If
sAccess = sCommand
' Comprobar si tiene el smbolo & y quitarlo del
commando
lRet = InStr(sAccess, "&")
If lRet Then
sCommand = Left$(sAccess, lRet - 1) & Mid$
(sAccess, lRet + 1)
End If
'

On Local Error GoTo AsociarExtErr


sValue = sProgId
sProgId = QueryRegBase(sExt)
If Len(sProgId) = 0 Then
' Registrar la extensin
sKey = sExt
sProgId = sValue

lRet = RegSetValue(HKEY_CLASSES_ROOT, sKey,


REG_SZ, sValue, Len(sValue))
'
sKey = sProgId
sValue = sDescription
lRet = RegSetValue(HKEY_CLASSES_ROOT, sKey,
REG_SZ, sValue, Len(sValue))
End If
sProgId = QueryRegBase(sExt)
If Len(sProgId) Then
' Nombre de la clave para esta extensin
sDef = "Software\Classes\" & sProgId & "\shell"

' usar HKEY_LOCAL_MACHINE, ya que


HKEY_CLASSES_ROOT es una copia de:
' HKEY_LOCAL_MACHINE\Software\Classes
hKey = HKEY_LOCAL_MACHINE
' Crear la clave del registro, si ya existe,
simplemente la abre.
' Nota: Esta funcin permite crear varios

niveles

lRet = RegCreateKey(hKey, sDef, phkResult)


If lRet = ERROR_SUCCESS Then
abierta

' Si no hay error, la clave est creada y/o


'
' Si no es "open"
If sCommand <> "open" Then
sKey = sCommand
sValue = sAccess

lRet = RegSetValue(phkResult, sKey,


REG_SZ, sValue, Len(sValue))
'
If Len(sExe) Then
sKey = sCommand & "\command"
sValue = sExe & Chr$(34) & "%1" &

Chr$(34)

lRet = RegSetValue(phkResult, sKey,


REG_SZ, sValue, Len(sValue))
End If
Else
' Abrir (open)
If Len(sExe) Then
sKey = "\open\command"
sValue = sExe & Chr$(34) & "%1" &
Chr$(34)
' Si no se especifica sKey, se
asigna a la clave abierta
lRet = RegSetValue(phkResult, sKey,
REG_SZ, sValue, Len(sValue))
End If
End If
If bDefault Then
' Poner este prograna por defecto
(asignarlo a Shell)

' Si no se especifica sKey, se asigna a


la clave abierta
sKey = ""
sValue = sCommand 'sProgId
lRet = RegSetValue(phkResult, sKey,
REG_SZ, sValue, Len(sValue))
End If
'
' Cerrar la clave abierta
lRet = RegCloseKey(phkResult)
End If
End If
'

Exit Sub

'AsociarExtErr:
'
Debug.Print "AsociarExt, error # " & Err.Number & "
" & Err.Description
'

Err = 0

End Sub
Public Function DeleteKeyWin95(ByVal hKey As Long, ByVal
szKey As String) As Long
' Esta no funciona en Windows NT y parece que
tampoco en Win98
' Slo en Windows 95
DeleteKeyWin95 = RegDeleteKey(hKey, szKey)
'Dim lRet As eHKEYError
'Dim phkResult As Long
'lRet = RegOpenKeyEx(hKey, szKey, 0&,
KEY_ALL_ACCESS, phkResult)
'If lRet = ERROR_SUCCESS Then
'

lRet = RegDeleteKey(phkResult, szKey)

'

Call RegCloseKey(phkResult)

'End If
'DeleteKeyWin95 = lRet
End Function
Public Sub DesasociarExt(ByVal sExt As String)

' Para desasociar la extensin indicada


'
Dim sProgId As String
Dim lRet As eHKEYError
' Si no se especifica el punto
If InStr(sExt, ".") = 0 Then
sExt = "." & sExt
End If
sProgId = QueryRegBase(sExt)
' Si la extensin est registrada...
If Len(sProgId) Then
' Esto slo funciona en Windows 95
'lRet = DeleteKeyWin95(HKEY_CLASSES_ROOT, sExt)
'If lRet = ERROR_SUCCESS Then
'

Call DeleteKeyWin95(HKEY_CLASSES_ROOT,

sProgId)
'End If
' Esto funciona en Windows 98 y Windows NT,
' tambin en Win95, aunque algo ms lento...
Call DeleteKeyNT(HKEY_CLASSES_ROOT, sExt)
Call DeleteKeyNT(HKEY_CLASSES_ROOT, sProgId)
End If
End Sub
Public Function SetReg(ByVal sKey As String, ByVal sName
As String, _
Optional ByVal vValue As Variant,

Optional ByVal hKey As eHKEY =

HKEY_CURRENT_USER, _

Optional ByVal RegDataType As


eHKEYDataType = REG_SZ, _
Optional ByVal bCreateKey As
Boolean = True) As eHKEYError
' Asignar un valor en el registro
'
' Parmetros:
'

sKey

Clave a la que se asignar el valor

'
valor

sName

Nombre de la entrada a asignar el

'
vValue
Valor a asignar, el tipo se debe
corresponder con el
'
RegDataType
'

tipo indicado en el parmetro

hKey

Clave principal del registro.

'
Si en sKey se incluye, no es
necesario especificarla
'

RegDataType Tipo de dato a asignar

'

bCreateKey

Si no existe la clave, crearla

'
' Devolver un valor del tipo: eHKEYError
'
Dim lRet As Long
Dim hKey2 As Long
Dim cbData As Long
Dim aData() As Byte
Dim sData As String
Dim lData As Long
' Convertimos la clave indicada en un valor
correcto,
' para el caso que se indique la clave raiz en sKey
hKey = ParseKey(sKey, hKey)
' Abrir la clave indicada
lRet = RegOpenKeyEx(hKey, sKey, 0&, KEY_WRITE,
hKey2)
' Si da error, comprobar si se crea la clave
If lRet <> ERROR_SUCCESS Then
If bCreateKey Then
lRet = RegCreateKey(hKey, sKey, hKey2)
End If
End If
' Si se produce error, salir
If lRet <> ERROR_SUCCESS Then
SetReg = lRet
Exit Function

End If
' Asignar el valor
'
Select Case RegDataType
Case REG_BINARY
aData = vValue
cbData = UBound(aData)
lRet = RegSetValueEx(hKey2, sName, 0&,
RegDataType, aData(0), cbData)
Case REG_DWORD
cbData = 4
lData = CLng(vValue)
lRet = RegSetValueEx(hKey2, sName, 0&,
RegDataType, lData, cbData)
Case REG_SZ
sData = CStr(vValue)
If Len(sData) = 0 Then
sData = ""
End If
cbData = Len(sData) + 1
Any---v

' Hay que usar ByVal porque est declarado como

lRet = RegSetValueEx(hKey2, sName, 0&,


RegDataType, ByVal sData, cbData)
Case Else
' No implementado...
End Select
lRet = RegCloseKey(hKey2)
SetReg = lRet
End Function
Public Function DeleteKey(ByVal sKey As String, _
Optional ByVal sValue As
String = "", _
Optional ByVal hKey As eHKEY =
HKEY_CURRENT_USER _
) As eHKEYError
'-------------------------------------------------------------------------

' Borrar la clave especificada del registro


' o el valor especificado
'
' Parmetros de entrada:
'

sKey

SubClave del registro

'
clave raiz

Se puede especificar el nombre de la

'

que se convertir al valor adecuado

'
sValue
borrar.

Nombre de la entrada que queremos

'
clave.

Si no se especifica, se borrar la

'

hKey

Clave principal del registro.

'
Si en sKey se incluye, no es
necesario especificarla
' Devuelve:
'

el cdigo devuelto por la operacin realizada

'------------------------------------------------------------------------Dim lRet As eHKEYError


Dim hKey2 As Long
' Nos aseguramos que hKey tenga el valor correcto
Select Case hKey
'Case HKEY_CLASSES_ROOT, HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE, HKEY_USERS
Case HKEY_FIRST To HKEY_LAST
' nada que hacer, todo correcto
Case Else
' Asignamos el valor por defecto
hKey = HKEY_CLASSES_ROOT
End Select
hKey = ParseKey(sKey)
' Si no se especifica sValue, se borra la clave
If Len(sValue) = 0 Then
DeleteKey = DeleteKeyNT(hKey, sKey)
Exit Function
End If
' Borrar el valor indicado

lRet = RegOpenKeyEx(hKey, sKey, 0&, KEY_WRITE,


hKey2)
If lRet = ERROR_SUCCESS Then
lRet = RegDeleteValue(hKey2, sValue)
Call RegCloseKey(hKey2)
End If
DeleteKey = lRet
End Function
Public Function EnumKeys(colKeys() As String, ByVal sKey
As String) As Boolean
'------------------------------------------------------------------------' Enumera todas las subclaves de la clave indicada
en sKey
(12/Oct/98)
'
' Parmetros:
'
colKeys()
Array unidimensional que contendr
las claves halladas
'
Los valores devueltos estarn
comprendidos entre:
'
'
sKey
la informacin

1 y UBound(colKeys)
Clave completa de la que se quiere

'
' Devolver True si todo va bien
'
' Revisado para Array y buen funcionamiento (espero)
(14/Oct/98)
' Revisado para funcionar en Windows NT (Win2000
Prof Beta 3)
(12/Jun/99)
'------------------------------------------------------------------------Dim dwIndex

As Long

Dim ret

As Long

Dim hKey2

As Long

Dim hKey

As Long

Dim lpName

As String

Dim lpftLastWriteTime

As FILETIME

Dim colItems

As Long

Dim lSize

As Long

Dim SubKeysNum

As Long

Dim MaxSubKeyLen

As Long

Dim numValues

As Long

Dim MaxValueNameLen

As Long

Dim MaxDataLen

As Long

colItems = 0
ReDim colKeys(0)
' Si se pasa una cadena en sKey, esta funcin la
convierte
' en un valor vlido para la clave principal
hKey = ParseKey(sKey, hKey)
' Abrir la clave indicada
'///////////////////////////////////////////////////////
///////////////////
'
Para que en Windows 2000 funcione,
(12/Jun/99)
'
he cambiado el tipo de acceso de
KEY_ENUMERATE_SUB_KEYS a KEY_READ
'///////////////////////////////////////////////////////
///////////////////
'ret = RegOpenKeyEx(hKey, sKey, 0&,
KEY_ENUMERATE_SUB_KEYS, hKey2)
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_READ, hKey2)
EnumKeys = True
' Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
' Obtener informacin de la clave y datos,
devolver:

clave
clave
valor

' SubKeysNum

Nmero de subclaves

' MaxSubKeyLen

Tamao mximo de nombre de

' numValues

Nmero de valores en esta

' MaxValueNameLen

Tamao mximo del nombre del

' MaxDataLen

Tamao mximo de los datos

'
' Probado para Windows 2000 Professional
(12/Jun/99)
'ret = RegQueryInfoKey(hKey2, vbNullString, 0&,

0&, _

SubKeysNum, MaxSubKeyLen, _
0&, numValues,
MaxValueNameLen, _
MaxDataLen, 0&,
lpftLastWriteTime)
' Slo nos interesa el nmero de subclaves y la
longitud mxima
ret = RegQueryInfoKey(hKey2, vbNullString, 0&,
0&, _
SubKeysNum, MaxSubKeyLen, _
0&, 0&, 0&, _
0&, 0&, lpftLastWriteTime)
' Se empieza desde cero
For dwIndex = 0 To SubKeysNum
lSize = MaxSubKeyLen
lpName = String$(lSize + 1, 0)
' Slo nos interesa los nombres de las
subclaves
ret = RegEnumKeyEx(hKey2, dwIndex, lpName,
lSize, _
0&, vbNullString, 0&,
lpftLastWriteTime)
If ret = ERROR_MORE_DATA Or ret =
ERROR_SUCCESS Then
' Redimensionar el array
colItems = colItems + 1
ReDim Preserve colKeys(colItems)
' lSize tiene el nmero de caracteres

devuelto,

' sin incluir el CHR$(0) del final


colKeys(colItems) = Left$(lpName, lSize)
End If
Next
Else
EnumKeys = False
End If
' Cerrar la clave abierta

Call CloseKey(hKey2)
End Function
Private Function RTrimZero(ByVal sString As String, _
Optional ByVal PorElFinal As
Boolean = False) As String
' Devuelve una cadena hasta el primer Chr$(0)
(12/Oct/98)
' Ampliada para poder devolver hasta el ltimo
(12/Jun/99)
Dim i As Long
' Si se quitan los ltimos Chr$(0)
If PorElFinal Then
' La cadena termina en el ltimo Chr$(0)
For i = Len(sString) To 1 Step -1
If Mid$(sString, i, 1) = Chr$(0) Then
sString = Left$(sString, i - 1)
Exit For
End If
Next
' Sustituir los Chr$(0) por espacios
For i = 1 To Len(sString)
If Mid$(sString, i, 1) = Chr$(0) Then
Mid$(sString, i, 1) = " "
End If
Next
'

i = Len(sString)

'

Do While Mid$(sString, i, 1) = Chr$(0)

'

i = i - 1

'

Loop

'
Chr$(0)

' i tendr el primer caracter que no es un

'

If i > 0 Then

'

sString = Left$(sString, i)

'

End If
Else
i = InStr(sString, Chr$(0))
If i Then
sString = Left$(sString, i - 1)

End If
End If
RTrimZero = sString
End Function
Public Function EnumValues(ByRef colKeys() As String,
ByVal sKey As String) As Boolean
'------------------------------------------------------------------------' Enumera todos los valores de la clave indicada en
(12/Oct/98)

sKey

'
' Parmetros:
'
colKeys()
Array unidimensional que contendr
las claves halladas
'
del valor y a

En este array se almacena el nombre

'
continuacin el valor en si, por
tanto hay que tener
'
esto en cuenta a la hora de
recuperar la informacin.
'
Los valores estarn comprendidos
entre 1 y UBound(colKeys)
'
valor

colKeys(i)= nombre, colKeys(i+1)=

'
poco ms abajo.

Ver el ejemplo de cmo usarla un

'
sKey
la informacin

Clave completa de la que se quiere

'
' Devolver True si todo va bien
'
' Revisado para Array y buen funcionamiento (espero)
(14/Oct/98)
'------------------------------------------------------------------------'Para recuperar la informacin de colKeys(), hacer
esto:
'
'If .EnumValues(colKeys(), sKey) Then
'
'

For i = 1 To UBound(colKeys) Step 2


'colKeys(i)

ser el nombre

'
almacenado
'

'colKeys(i + 1)

ser el valor o dato

Next

'End If
'--------------------------------------------------------------------'
Dim dwIndex

As Long

Dim ret

As Long

Dim hKey2

As Long

Dim hKey

As Long

Dim lpName

As String

Dim lpftLastWriteTime

As FILETIME

Dim retDT

As eHKEYDataType

Dim lSize

As Long

Dim sData

As String

Dim aData()

As Byte

Dim lDWord

As Long

Dim i

As Long

Dim colItems

As Long

Dim SubKeysNum

As Long

Dim MaxSubKeyLen

As Long

Dim numValues

As Long

Dim MaxValueNameLen

As Long

Dim MaxDataLen

As Long

' Si se pasa una cadena en sKey, esta funcin la


convierte
' en un valor vlido para la clave principal
hKey = ParseKey(sKey, hKey)
' Abrir la clave indicada
' En este caso da igual el tipo de acceso,
(12/Jun/99)
' pero... ms vale prevenir
'ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_QUERY_VALUE,
hKey2)
ret = RegOpenKeyEx(hKey, sKey, 0&, KEY_READ, hKey2)
' Valores por defecto

EnumValues = True
ReDim aData(0)
lDWord = 0
sData = ""
' Inicializar el array
colItems = 0
ReDim colKeys(colItems)
' Si todo va bien (se ha podido abrir la clave)
If ret = ERROR_SUCCESS Then
' Obtener la informacin de esta clave,
devolver:
' SubKeysNum

Nmero de subclaves

' MaxSubKeyLen

Tamao mximo de nombre de

' numValues

Nmero de valores en esta

' MaxValueNameLen

Tamao mximo del nombre del

' MaxDataLen

Tamao mximo de los datos

clave
clave
valor
'ret = RegQueryInfoKey(hKey2, 0&, 0&, 0&,
SubKeysNum, MaxSubKeyLen, _
MaxValueNameLen, _
lpftLastWriteTime)

0&, numValues,
MaxDataLen, 0&,

' A ver si as funciona...


(12/Jun/99)
ret = RegQueryInfoKey(hKey2, vbNullString, 0&,
0&, _
SubKeysNum, MaxSubKeyLen, _
MaxValueNameLen, _
lpftLastWriteTime)

0&, numValues,
MaxDataLen, 0&,

' Este es el error que me da el Windows 2000 Pro


(12/Jun/99)
If ret = ERROR_INVALID_PARAMETERS Then
Debug.Print "ERROR_INVALID_PARAMETERS"
EnumValues = False

GoTo SalirEnumValues
End If
lpName = String$(MaxValueNameLen + 1, 0)
' Hacer un bucle para el nmero de valores
posibles
For dwIndex = 0 To numValues
lpName = String$(MaxValueNameLen + 1, 0)
' Llamarlo primero para saber el tipo de
datos,
' el cual estar en retDT
'///////////////////////////////////////////////////////
///////////
' De esta forma en Win2000 produce un error
de proteccin
'ret = RegEnumValue(hKey2, dwIndex, ByVal
0&, ByVal 0&, 0&, retDT, ByVal 0&, ByVal 0&)
'ret = RegEnumValue(hKey2, dwIndex,
vbNullString, ByVal 0&, 0&, retDT, ByVal 0&, ByVal 0&)
'///////////////////////////////////////////////////////
///////////
ret = RegEnumValue(hKey2, dwIndex, 0&, 0&,
0&, retDT, 0&, 0&)
'ret = RegEnumValue(hKey2, dwIndex, lpName,
Len(lpName), 0&, retDT, ByVal sData, lSize)
' la primera vez, cuando dwIndex = cero,
devuelve ERROR_SUCCESS,
' pero despus devuelve ERROR_MORE_DATA
mientras haya datos.
If ret = ERROR_MORE_DATA Or ret =
ERROR_SUCCESS Then
lSize = MaxDataLen
Select Case retDT
Case REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ
' Datos de cadena
sData = String$(lSize, 0)
ret = RegEnumValue(hKey2, dwIndex,
lpName, Len(lpName), 0&, retDT, ByVal sData, lSize)
If retDT = REG_MULTI_SZ Then
sData = RTrimZero(sData, True)
Else
sData = RTrimZero(sData)

End If
lpName = RTrimZero(lpName)
ReDim Preserve colKeys(colItems + 2)
colKeys(colItems + 1) = lpName
colKeys(colItems + 2) = sData
colItems = colItems + 2
Case REG_DWORD
' Datos numricos (long)
ret = RegEnumValue(hKey2, dwIndex,
lpName, Len(lpName), 0&, retDT, lDWord, lSize)
sData = CStr(lDWord)
lpName = RTrimZero(lpName)
ReDim Preserve colKeys(colItems + 2)
colKeys(colItems + 1) = lpName
colKeys(colItems + 2) = sData
colItems = colItems + 2
'Case REG_BINARY
'

'Datos binarios

Case Else
' Tratarlo como Binary
If lSize Then
ReDim aData(lSize)
' Leer los datos binarios
ret = RegEnumValue(hKey2,
dwIndex, lpName, Len(lpName), 0&, retDT, aData(0),
lSize)
lpName = RTrimZero(lpName)
' Al estilo de como se muestra

con RegEdit

sData = ""
For i = 0 To UBound(aData) - 1
sData = sData & Format$(Hex$

(aData(i)), "00") & " "


Next

ReDim Preserve colKeys(colItems


+ 2)
colKeys(colItems + 1) = lpName
colKeys(colItems + 2) = sData
colItems = colItems + 2
End If
End Select

End If
Next
Else
EnumValues = False
End If
SalirEnumValues:
' Cerrar la clave abierta
ret = CloseKey(hKey2)
End Function
Public Function RegSaveKey(ByVal sKey As String, ByVal
lpFile As String) As Long
' Guarda en un fichero el contenido de una clave,
las subclaves y datos.
' Y funcionar, funciona, pero el fichero que da como
resultado no es
' un fichero de texto...
' No he probado a asignar de nuevo el valor
guardado, pero seguramente
' funcionar, lo que pasa es que no tiene un formato
reconocido por
' RegEdit.exe (extensin .REG)
'
'$Por hacer: comprobar si esta funcin est bien...
'
' Los atributos de seguridad se ignoran en Win95/98

(0&)

' si ese valor se usa en NT, se usarn los atributos


por defecto...
'
' Nombre a usar de forma temporal
Const stmpFic As String = "\tmp.reg"
Dim hKey As Long
Dim hKey2 As Long
Dim ret As eHKEYError
' Abrir la clave del registro
hKey = ParseKey(sKey)
ret = RegOpenKeyEx(hKey, sKey, 0&, 0&, hKey2)
' Guardarla en el fichero indicado

' como no se permiten nombres largos, se grabar


en \tmp.reg
' y despus se copiar en el nombre indicado.
' En Win95 se guarda con los atributos ReadOnly,
Hide y System
'
' La funcin falla si ya existe el fichero
On Local Error Resume Next
If Len(Dir$(stmpFic, vbHidden + vbReadOnly +
vbSystem)) Then
SetAttr stmpFic, vbNormal
Kill stmpFic
End If
ret = RegSaveKeyA(hKey2, stmpFic, 0&)
If ret = ERROR_SUCCESS Then
' Quitarle los atributos
SetAttr stmpFic, vbNormal
' renombrar el fichero
FileCopy stmpFic, lpFile
' borrar el temporal
Kill stmpFic
End If
Err = 0
RegCloseKey hKey2
End Function
24.- Conectarse a unidad de red (23/Jun/99)
Este es un ejemplo de cmo conectarse y desconectarse de unidades de red,
usando cdigo de Visual Basic... con ayuda del API de Windows.
El cdigo aqu mostrado est sacado de la Knowledge Base de Microsoft, artculo
Q173011. Lo que yo he hecho ha sido probarlo y adaptarlo para su uso en un
formulario... pero como creo que es un tema que a ms de uno le puede
interesar... aqu est.
He dejado los comentarios originales... en ingls, claro.
Puedes bajarte el cdigo de ejemplo, (net.zip 2.67 KB) aunque bsicamente es
muy simple, lo que al formulario se refiere, ya que el cdigo a usar es el que te
muestro a continuacin.
Este es el aspecto del formulario en ejecucin:

'----------------------------------------------------------------------------' Prueba de conexin / desconexin a unidades de red


(23/Jun/99)
'
' Guillermo 'guille' Som, 1999
'
' Ejemplo basado en un artculo de la Knowledge Base de Microsoft:
'

HOWTO: Add and Remove Network Connections

'

Article ID: Q173011

'----------------------------------------------------------------------------Option Explicit
Private Declare Function WNetAddConnection2 Lib "mpr.dll" Alias
"WNetAddConnection2A" _
(lpNetResource As NETRESOURCE, ByVal lpPassword As String, _
ByVal lpUserName As String, ByVal dwFlags As Long) As Long
Private Declare Function WNetCancelConnection2 Lib "mpr.dll" Alias
"WNetCancelConnection2A" _
(ByVal lpName As String, ByVal dwFlags As Long, ByVal fForce As
Long) As Long
Private Type NETRESOURCE
dwScope As Long
dwType As Long
dwDisplayType As Long

dwUsage As Long
lpLocalName As String
lpRemoteName As String
lpComment As String
lpProvider As String
End Type
Private Const NO_ERROR = 0
Private Const CONNECT_UPDATE_PROFILE = &H1
' The following includes all the constants defined for NETRESOURCE,
' not just the ones used in this example.
Private Const RESOURCETYPE_DISK = &H1
Private Const RESOURCETYPE_PRINT = &H2
Private Const RESOURCETYPE_ANY = &H0
Private Const RESOURCE_CONNECTED = &H1
Private Const RESOURCE_REMEMBERED = &H3
Private Const RESOURCE_GLOBALNET = &H2
Private Const RESOURCEDISPLAYTYPE_DOMAIN = &H1
Private Const RESOURCEDISPLAYTYPE_GENERIC = &H0
Private Const RESOURCEDISPLAYTYPE_SERVER = &H2
Private Const RESOURCEDISPLAYTYPE_SHARE = &H3
Private Const RESOURCEUSAGE_CONNECTABLE = &H1
Private Const RESOURCEUSAGE_CONTAINER = &H2
' Error Constants:
Private Const ERROR_ACCESS_DENIED = 5&
Private Const ERROR_ALREADY_ASSIGNED = 85&
Private Const ERROR_BAD_DEV_TYPE = 66&
Private Const ERROR_BAD_DEVICE = 1200&
Private Const ERROR_BAD_NET_NAME = 67&
Private Const ERROR_BAD_PROFILE = 1206&
Private Const ERROR_BAD_PROVIDER = 1204&
Private Const ERROR_BUSY = 170&
Private Const ERROR_CANCELLED = 1223&
Private Const ERROR_CANNOT_OPEN_PROFILE = 1205&
Private Const ERROR_DEVICE_ALREADY_REMEMBERED = 1202&
Private Const ERROR_EXTENDED_ERROR = 1208&
Private Const ERROR_INVALID_PASSWORD = 86&
Private Const ERROR_NO_NET_OR_BAD_PATH = 1203&

Private Sub cmdAdd_Click()


Dim NetR As NETRESOURCE
Dim ErrInfo As Long
' Datos del usuario y password
Dim MyPass As String, MyUser As String
Dim sPath As String
sPath = CurDir$
NetR.dwScope = RESOURCE_GLOBALNET
NetR.dwType = RESOURCETYPE_DISK
NetR.dwDisplayType = RESOURCEDISPLAYTYPE_SHARE
NetR.dwUsage = RESOURCEUSAGE_CONNECTABLE
NetR.lpLocalName = txtLocal ' If undefined, Connect with no
device
NetR.lpRemoteName = txtNet

' Your valid share

'NetR.lpComment = "Optional Comment"


'NetR.lpProvider =

' Leave this undefined

' If the UserName and Password arguments are NULL, the user
context
' for the process provides the default user name.
ErrInfo = WNetAddConnection2(NetR, MyPass, MyUser,
CONNECT_UPDATE_PROFILE)
If ErrInfo = NO_ERROR Then
lblStatus = " Conexin realizada con xito"
sPath = txtLocal
Else
lblStatus = " ERROR al intentar realizar la conexin"
MsgBox "ERROR: " & ErrInfo & " - Net Connection Failed!",
vbExclamation, "Share not Connected"
End If
' Rellenar el DirBox
Dir1.Path = sPath
End Sub

Private Sub cmdCancel_Click()


Dim ErrInfo As Long

Dim strLocalName As String


' You may specify either the lpRemoteName or lpLocalName
'strLocalName = "\\ServerName\ShareName"
strLocalName = txtLocal
ErrInfo = WNetCancelConnection2(strLocalName,
CONNECT_UPDATE_PROFILE, False)
If ErrInfo = NO_ERROR Then
lblStatus = " Desconectado de la unidad " & strLocalName & "
satisfactoriamente"
Else
lblStatus = " ERROR al desconectar la unidad " &
strLocalName
MsgBox "ERROR: " & ErrInfo & " - Net Disconnection Failed!",
vbExclamation, "Share not Disconnected"
End If
Dir1.Path = CurDir$
End Sub

25.- Clase para manipular el volumen de la tarjeta de sonido (09/Jul/99)


Con este cdigo podrs manejar el volumen del sistema... el de la tarjeta de
sonido, que con esto del volumen, puede parecer que es el volumen de una unidad
de disco...
Este cdigo est basado en un ejemplo de la Knowledge Base de Microsoft, en ese
ejemplo tambin se manipula el volumen de entrada, el del micrfono, pero en
esta clase slo se maneja el volumen de salida... adems, le he aadido una
funcin para hacer fade... es decir, desvanecer el sonido, adems de algunas otras
cosillas... pocas, esa es la verdad, pero creo que es interesante, adems de que de
vez en cuando se hacen consultas sobre este tema... por tanto, espero que te
pueda ser de utilidad.
Aqu tienes el cdigo de la clase y un ejemplo de cmo usarla.
Los listados puedes bajarlo pulsando en este link (volumen.zip 6.55 KB)
'
'----------------------------------------------------------------------------' cVolumen
(09/Jul/99)
' Clase para manejar el volumen del sistema
'
' Las propiedades, mtodos y eventos son:

'

Fade

Para hacer fade (desvanecer el volumen)

'

MaxVol

Valor mximo para el volumen (slo lectura)

'

MinVol

Valor mnimo para el volumen (slo lectura)

'

Volumen

Para asignar u obtener el valor del volumen

'

CambioVolumen

Evento producido cada vez que se cambia el volumen

'
' Guillermo 'guille' Som, 1999
'----------------------------------------------------------------------------' El cdigo est basado en un ejemplo de la Knowledge Base de Microsoft:
'

FILE: VOLUME.EXE: Set Volume Control Levels Using Visual Basic

'

Article ID: Q178456

'----------------------------------------------------------------------------Option Explicit
' Evento para notificar el cambio del volumen
Public Event CambioVolumen(ByVal VolumenActual As Long)

'----------------------------------------------------------------------------' Variables, constantes, tipos y declaraciones para el control del volumen


'
Private VolActual As Long

' Volumen actual

Private hMixer As Long

' mixer handle

Private volCtrl As MIXERCONTROL ' waveout volume control


Private rc As Long

' return code

Private ok As Boolean

' boolean return code

Private Const MIXER_SETCONTROLDETAILSF_VALUE = &H0&


Private Const MMSYSERR_NOERROR = 0
Private Const MAXPNAMELEN = 32
Private Const MIXER_LONG_NAME_CHARS = 64
Private Const MIXER_SHORT_NAME_CHARS = 16
Private Const MIXER_GETLINEINFOF_COMPONENTTYPE = &H3&
Private Const MIXER_GETCONTROLDETAILSF_VALUE = &H0&
Private Const MIXER_GETLINECONTROLSF_ONEBYTYPE = &H2&
Private Const MIXERLINE_COMPONENTTYPE_DST_FIRST = &H0&
Private Const MIXERLINE_COMPONENTTYPE_SRC_FIRST = &H1000&
Private Const MIXERLINE_COMPONENTTYPE_DST_SPEAKERS = _

(MIXERLINE_COMPONENTTYPE_DST_FIRST + 4)
Private Const MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE = _
(MIXERLINE_COMPONENTTYPE_SRC_FIRST + 3)
Private Const MIXERLINE_COMPONENTTYPE_SRC_LINE = _
(MIXERLINE_COMPONENTTYPE_SRC_FIRST + 2)
Private Const MIXERCONTROL_CT_CLASS_FADER = &H50000000
Private Const MIXERCONTROL_CT_UNITS_UNSIGNED = &H30000
Private Const MIXERCONTROL_CONTROLTYPE_FADER = _
(MIXERCONTROL_CT_CLASS_FADER Or _
MIXERCONTROL_CT_UNITS_UNSIGNED)
Private Const MIXERCONTROL_CONTROLTYPE_VOLUME = _
(MIXERCONTROL_CONTROLTYPE_FADER + 1)
Private Declare Function mixerClose Lib "winmm.dll" _
(ByVal hmx As Long) As Long
Private Declare Function mixerGetControlDetails Lib "winmm.dll" _
Alias "mixerGetControlDetailsA" _
(ByVal hmxobj As Long, _
pmxcd As MIXERCONTROLDETAILS, _
ByVal fdwDetails As Long) As Long
Private Declare Function mixerGetDevCaps Lib "winmm.dll" _
Alias "mixerGetDevCapsA" _
(ByVal uMxId As Long, _
ByVal pmxcaps As MIXERCAPS, _
ByVal cbmxcaps As Long) As Long
Private Declare Function mixerGetID Lib "winmm.dll" _
(ByVal hmxobj As Long, _
pumxID As Long, _
ByVal fdwId As Long) As Long
Private Declare Function mixerGetLineControls Lib "winmm.dll" _
Alias "mixerGetLineControlsA" _
(ByVal hmxobj As Long, _
pmxlc As MIXERLINECONTROLS, _

ByVal fdwControls As Long) As Long


Private Declare Function mixerGetLineInfo Lib "winmm.dll" _
Alias "mixerGetLineInfoA" _
(ByVal hmxobj As Long, _
pmxl As MIXERLINE, _
ByVal fdwInfo As Long) As Long
Private Declare Function mixerGetNumDevs Lib "winmm.dll" () As Long
Private Declare Function mixerMessage Lib "winmm.dll" _
(ByVal hmx As Long, _
ByVal uMsg As Long, _
ByVal dwParam1 As Long, _
ByVal dwParam2 As Long) As Long
Private Declare Function mixerOpen Lib "winmm.dll" _
(phmx As Long, _
ByVal uMxId As Long, _
ByVal dwCallback As Long, _
ByVal dwInstance As Long, _
ByVal fdwOpen As Long) As Long
Private Declare Function mixerSetControlDetails Lib "winmm.dll" _
(ByVal hmxobj As Long, _
pmxcd As MIXERCONTROLDETAILS, _
ByVal fdwDetails As Long) As Long
Private Declare Sub CopyStructFromPtr Lib "kernel32" _
Alias "RtlMoveMemory" _
(struct As Any, _
ByVal ptr As Long, ByVal cb As Long)
Private Declare Sub CopyPtrFromStruct Lib "kernel32" _
Alias "RtlMoveMemory" _
(ByVal ptr As Long, _
struct As Any, _
ByVal cb As Long)
Private Declare Function GlobalAlloc Lib "kernel32" _
(ByVal wFlags As Long, _
ByVal dwBytes As Long) As Long

Private Declare Function GlobalLock Lib "kernel32" _


(ByVal hMem As Long) As Long
Private Declare Function GlobalFree Lib "kernel32" _
(ByVal hMem As Long) As Long
Private Type MIXERCAPS
wMid As Integer

'

manufacturer id

wPid As Integer

'

product id

vDriverVersion As Long

'

version of the driver

szPname As String * MAXPNAMELEN

'

product name

fdwSupport As Long

'

misc. support bits

cDestinations As Long

'

count of destinations

End Type
Private Type MIXERCONTROL
cbStruct As Long

'

size in Byte of MIXERCONTROL

dwControlID As Long

'

unique control id for mixer device

dwControlType As Long

'

MIXERCONTROL_CONTROLTYPE_xxx

fdwControl As Long

'

MIXERCONTROL_CONTROLF_xxx

cMultipleItems As Long

'

if MIXERCONTROL_CONTROLF_MULTIPLE set

szShortName As String * MIXER_SHORT_NAME_CHARS

' short name of control

szName As String * MIXER_LONG_NAME_CHARS

' long name of control

lMinimum As Long

'

Minimum value

lMaximum As Long

'

Maximum value

reserved(10) As Long

'

reserved structure space

End Type
Private Type MIXERCONTROLDETAILS
cbStruct As Long

'

size in Byte of MIXERCONTROLDETAILS

dwControlID As Long

'

control id to get/set details on

cChannels As Long

'

number of channels in paDetails array

item As Long

'

hwndOwner or cMultipleItems

cbDetails As Long

'

size of _one_ details_XX struct

paDetails As Long

'

pointer to array of details_XX structs

End Type
Private Type MIXERCONTROLDETAILS_UNSIGNED
dwValue As Long
End Type

'

value of the control

Private Type MIXERLINE


cbStruct As Long

'

size of MIXERLINE structure

dwDestination As Long

'

zero based destination index

dwSource As Long

'

zero based source index (if source)

dwLineID As Long

'

unique line id for mixer device

fdwLine As Long

'

state/information about line

dwUser As Long

'

driver specific information

dwComponentType As Long

'

component type line connects to

cChannels As Long

'

number of channels line supports

cConnections As Long

'

number of connections (possible)

cControls As Long

'

number of controls at this line

szShortName As String * MIXER_SHORT_NAME_CHARS


szName As String * MIXER_LONG_NAME_CHARS
dwType As Long
dwDeviceID As Long
wMid

As Integer

wPid As Integer
vDriverVersion As Long
szPname As String * MAXPNAMELEN
End Type
Private Type MIXERLINECONTROLS
cbStruct As Long

'

size in Byte of MIXERLINECONTROLS

dwLineID As Long

'

line id (from MIXERLINE.dwLineID)

'

MIXER_GETLINECONTROLSF_ONEBYID or

dwControl As Long

'

MIXER_GETLINECONTROLSF_ONEBYTYPE

cControls As Long

'

count of controls pmxctrl points to

cbmxctrl As Long

'

size in Byte of _one_ MIXERCONTROL

pamxctrl As Long

'

pointer to first MIXERCONTROL array

End Type
Private Sub Class_Initialize()
' Abrir el mezclador?
Call AbrirMixer
End Sub
Private Sub Class_Terminate()
On Local Error Resume Next
' Cerrar el mixer
Call mixerClose(hMixer)

Err = 0
End Sub
Public Sub Fade(Optional ByVal Segundos As Long = 3&, _
Optional ByVal Pasos As Long = 8&, _
Optional ByVal Restaurar As Boolean = True, _
Optional ByVal Inverso As Boolean = True, _
Optional ByVal VolMin As Long = 1000&)
'-------------------------------------------------------------------------' Hacer fade llevando el volumen hasta cero

( 1/Ago/98)

'
' Parmetros:
'

Segundos

Tiempo mximo de la duracin del fade, (de 0 a Segundos)

'

Pasos

el valor de Pasos es para la cuenta hacia atrs

'

Restaurar

Para dejar el volumen que haba antes de hacer Fade

'

Inverso

Para hacer fade de mayor a menor

VolMin

Ser el volumen mnimo al que se llegar haciendo el

'
Fade

'-------------------------------------------------------------------------Dim i As Long
Dim horaActual As Date
Static tmpVolActual As Long
Dim j As Long, k As Long
' Siempre debe ser un nmero negativo
Pasos = -Abs(Pasos)
horaActual = Now
'
tmpVolActual = ObtenerVolumen(hMixer, volCtrl)
'
If Inverso Then
' Obtener el valor actual del volumen, por si se cambia
' mientras se est tocando
VolActual = ObtenerVolumen(hMixer, volCtrl)
tmpVolActual = VolActual
j = tmpVolActual
If VolMin < 0 Then VolMin = 0
k = VolMin
Else
j = tmpVolActual

k = VolActual
Pasos = Abs(Pasos)
End If
For i = j To k Step Pasos
Call SetVolumeControl(hMixer, volCtrl, i)
DoEvents
If Second(Now - horaActual) >= Segundos Then
Exit For
End If
Next
tmpVolActual = ObtenerVolumen(hMixer, volCtrl)
If Restaurar Then
Call SetVolumeControl(hMixer, volCtrl, VolActual)
End If
End Sub
Private Function GetVolumeControl(ByRef hMixer As Long, _
ByVal componentType As Long, _
ByVal ctrlType As Long, _
ByRef mxc As MIXERCONTROL) As Boolean
' This function attempts to obtain a mixer control.
' Returns True if successful.
Dim mxlc As MIXERLINECONTROLS
Dim mxl As MIXERLINE
Dim hMem As Long
mxl.cbStruct = Len(mxl)
mxl.dwComponentType = componentType
' Obtain a line corresponding to the component type
rc = mixerGetLineInfo(hMixer, mxl, MIXER_GETLINEINFOF_COMPONENTTYPE)
If (MMSYSERR_NOERROR = rc) Then
mxlc.cbStruct = Len(mxlc)
mxlc.dwLineID = mxl.dwLineID
mxlc.dwControl = ctrlType
mxlc.cControls = 1
mxlc.cbmxctrl = Len(mxc)
' Allocate a buffer for the control

hMem = GlobalAlloc(&H40, Len(mxc))


mxlc.pamxctrl = GlobalLock(hMem)
mxc.cbStruct = Len(mxc)
' Get the control
rc = mixerGetLineControls(hMixer, _
mxlc, _
MIXER_GETLINECONTROLSF_ONEBYTYPE)
If (MMSYSERR_NOERROR = rc) Then
GetVolumeControl = True
' Copy the control into the destination structure
CopyStructFromPtr mxc, mxlc.pamxctrl, Len(mxc)
Else
GetVolumeControl = False
End If
GlobalFree (hMem)
Exit Function
End If
GetVolumeControl = False
End Function
Private Function ObtenerVolumen(ByRef hMixer As Long, _
ByRef mxc As MIXERCONTROL) As Long
' Obtiene el volumen actual
'
Dim mxcd As MIXERCONTROLDETAILS
Dim vol As MIXERCONTROLDETAILS_UNSIGNED
Dim hMem2 As Long
mxcd.item = 0
mxcd.dwControlID = mxc.dwControlID
mxcd.cbStruct = Len(mxcd)
mxcd.cbDetails = Len(vol)
' Allocate a buffer for the control value buffer
hMem2 = GlobalAlloc(&H40, Len(vol))
mxcd.paDetails = GlobalLock(hMem2)

( 1/Ago/98)

mxcd.cChannels = 1
' Get the control value
rc = mixerGetControlDetails(hMixer, _
mxcd, _
MIXER_GETCONTROLDETAILSF_VALUE)
'
' Copy the data into the control value buffer
CopyStructFromPtr vol, mxcd.paDetails, Len(vol)
'
GlobalFree (hMem2)
If (rc = MMSYSERR_NOERROR) Then
ObtenerVolumen = vol.dwValue
RaiseEvent CambioVolumen(vol.dwValue)
Else
ObtenerVolumen = -1&
RaiseEvent CambioVolumen(-1&)
End If
End Function
Private Function SetVolumeControl(ByVal hMixer As Long, _
mxc As MIXERCONTROL, _
ByVal volume As Long) As Boolean
' This function sets the value for a volume control.
' Returns True if successful
Dim mxcd As MIXERCONTROLDETAILS
Dim vol As MIXERCONTROLDETAILS_UNSIGNED
Dim hMem As Long
mxcd.item = 0
mxcd.dwControlID = mxc.dwControlID
mxcd.cbStruct = Len(mxcd)
mxcd.cbDetails = Len(vol)
' Allocate a buffer for the control value buffer
hMem = GlobalAlloc(&H40, Len(vol))
mxcd.paDetails = GlobalLock(hMem)
mxcd.cChannels = 1
vol.dwValue = volume

' Copy the data into the control value buffer


CopyPtrFromStruct mxcd.paDetails, vol, Len(vol)
' Set the control value
rc = mixerSetControlDetails(hMixer, _
mxcd, _
MIXER_SETCONTROLDETAILSF_VALUE)
GlobalFree (hMem)
If (MMSYSERR_NOERROR = rc) Then
SetVolumeControl = True
Else
SetVolumeControl = False
End If
RaiseEvent CambioVolumen(volume)
End Function
Private Function AbrirMixer() As Long
'
' Abre el Mixer y devuelve el valor del volumen actual
' Si no se puede abrir, devolver -1
'

( 1/Ago/98)

'
'
'

// Open the mixer. This opens the mixer with a deviceID of 0. If you

'

// have a single sound card/mixer, then this will open it. If you have

'

// multiple sound cards/mixers, the deviceIDs will be 0, 1, 2, and

'

// so on.

'

rc = mixerOpen(&hMixer, 0,0,0,0);

'

if (MMSYSERR_NOERROR == rc) {

'
'

// Couldn't open the mixer.


}

'
' Open the mixer with deviceID 0.
rc = mixerOpen(hMixer, 0, 0, 0, 0)
If ((MMSYSERR_NOERROR <> rc)) Then
AbrirMixer = -1
Exit Function
End If
' Get the waveout volume control
ok = GetVolumeControl(hMixer, _

MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, _
MIXERCONTROL_CONTROLTYPE_VOLUME, _
volCtrl)
If (ok = True) Then
AbrirMixer = ObtenerVolumen(hMixer, volCtrl)
Else
AbrirMixer = -1
End If
End Function
Private Sub CerrarMixer()
' Cerrar el mixer
Call mixerClose(hMixer)
End Sub
Public Property Get Volumen() As Long
' Obtener el volumen del sistema
Volumen = ObtenerVolumen(hMixer, volCtrl)
End Property
Public Property Let Volumen(ByVal NewValue As Long)
' Asignar un nuevo valor para el volumen
'
' Los valores mximo y mnimo estarn dentro del rango de:
' volCtrl.lMinimum y volCtrl.lMaximum
If Not (NewValue > volCtrl.lMaximum Or NewValue < volCtrl.lMinimum) Then
Call SetVolumeControl(hMixer, volCtrl, NewValue)
End If
End Property
Public Property Get MinVol() As Long
' Devuelve el valor mnimo del volumen (suele ser cero)
MinVol = volCtrl.lMinimum
End Property
Public Property Get MaxVol() As Long
' Devuelve el valor mximo del mixer, normalmente 65535
MaxVol = volCtrl.lMaximum
End Property

El cdigo del ejemplo, con una imagen del formulario:

'
'----------------------------------------------------------------------------' Prueba de la clase cVolumen
(09/Jul/99)
'
' Guillermo 'guille' Som, 1999
'----------------------------------------------------------------------------Option Explicit
Private WithEvents m_cVol As cVolumen
el volumen
Private m_VolIni As Long
el programa

' Clase para manipular


' Valor del volumen al iniciar

Private Sub cmdFade_Click(Index As Integer)


' Hacer fade
' Si Index = 0 se har descendiendo el volumen
' Si Index = 1 se har aumentando

el volumen

If Index = 0 Then
m_cVol.Fade Restaurar:=False, Inverso:=True, VolMin:=4000
'm_cVol.Fade 3, 8, False, True, 4000
Else
m_cVol.Fade Segundos:=2, Pasos:=16, Inverso:=False
'm_cVol.Fade 2, 16, True, False
End If
' Mostrar el volumen actual

' (usarlo si no se quiere declarar con WithEvents)


'txtVol = m_cVol.Volumen
End Sub
Private Sub cmdRestaurar_Click()
' Restaurar el volumen inicial
m_cVol.Volumen = m_VolIni
txtVol = m_VolIni
End Sub
Private Sub cmdVolumen_Click()
m_cVol.Volumen = txtVol
End Sub
Private Sub Form_Load()
Set m_cVol = New cVolumen
With m_cVol
' Leer el volumen inicial, por si se restaura
m_VolIni = .Volumen
' asignar el volumen actual, si no se ha declarado con
WintEvents
'txtVol = m_VolIni
' Mostrar los valores mnimo y mximo del nivel de volumen
Label1(1) = "Min: " & .MinVol & " - Max: " & .MaxVol
End With
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As
Integer)
' Restaurar el volumen inicial
cmdRestaurar_Click
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set m_cVol = Nothing
Set fTestVol = Nothing
End Sub

Private Sub m_cVol_CambioVolumen(ByVal VolumenActual As Long)


txtVol = VolumenActual
End Sub
Private Sub txtVol_KeyPress(KeyAscii As Integer)
If KeyAscii = 13 Then
KeyAscii = 0
cmdVolumen_Click
End If
End Sub
26.- Formularios transparentes en Windows 2000 (Layered Windows)
(24/Abr/00)
S, ya s que lo de hacer formularios transparente en Visual Basic es fcil..., (al
menos despus de que nuestro colega Luis Sanz nos proporcionara un control para
hacer los formularios transparentes, si no sabes de que estoy hablando, sigue este
link.); pero de lo que estamos hablando es de una transparencia "total", adems
manejada por el propio sistema operativo, en este caso slo en el Windows 2000,
(no se si la nueva versin del Windows 98 (Millenium) lo tendr... as que, hasta
que no salga, habr que esperar...)
La cuestin es que leyendo, hace unos das, un artculo aparecido en el MSDN
news del pasado Enero/Febrero 2000, me "calent" con el tema este... la
cuestin, segn la planteaban, era fcil, simplemente haba que cambiar un "bit"
del estilo de la ventana para que fuese WM_EX_LAYERED, llamar a una funcin
del API para hacer que fuese transparente y ya est.
El cdigo mostrado era este:
'
// Set WS_EX_LAYERED on this window
SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) |
WS_EX_LAYERED);
// Make this window 70% alpha
SetLayeredWindowAttributes(hWnd, 0, (255 * 70) / 100, LWA_ALPHA);

El primer problema con el que me top era que no encontr el valor de la


constante WS_EX_LAYERED; pero, para algo estn los grupos de noticias, as que
hice lo que muchos tendrais que hacer antes de enviarme consultas... (je, je), es
decir, pregunt por el valor de esa constante en un grupo de noticias y al da
siguiente ya tena la respuesta... (Mi agradecimiento a Bill McCarthy y a Tomas
Restrepo por facilitarme el valor de esa constante y a Toms por indicarme dnde
encontrar la definicin de la misma)
En el fichero de cabecera WinUser.h en el que estaba el valor de esa constante, as

como la de LWA_ALPHA, encontr tambin la definicin (en C) de la funcin


SetLayeredWindowAttributes, lo que quedaba era convertirla al Visual Basic y
probarla...
Aqu tienes las declaraciones en formato Visual Basic de las constantes y la
susodicha funcin:
'
Private Const WS_EX_LAYERED As Long = &H80000
Private Const LWA_ALPHA As Long = &H2
'
Private Declare Function SetLayeredWindowAttributes Lib "user32" _
(ByVal hWnd As Long, ByVal crKey As Long, _
ByVal bAlpha As Long, ByVal dwFlags As Long) As Long
Lo siguiente era probarla... por supuesto, como he dicho antes, en Windows 2000,
ya que en Windows 98 no existe esa funcin en el fichero user32.
Esto es lo que hay que hacer para que se convierta en un formulario transparente:
(despus veremos el cdigo completo)

'// Set WS_EX_LAYERED on this window


Call SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd,
GWL_EXSTYLE) Or WS_EX_LAYERED)
'// Make this window 70% alpha
Call SetLayeredWindowAttributes(hWnd, 0, (255 * 70) / 100,
LWA_ALPHA)
Fjate en el tercer parmetro, el que le indica a la funcin que porcentaje de
transparencia queremos, los valores que puede tomar van desde 0 a 255.
En el programilla de ejemplo, adems de poder hacer transparente un formulario,
pudiendo seleccionar la cantidad de "transparencia" del mismo, he aadido hacer
un efecto "fade", es decir que el formulario aparezca desde transparente hasta
normal.

Veamos una "foto" del formulario transparente en ejecucin y despus veremos el


cdigo, con todas las declaraciones de las funciones del API usadas en el mismo.

El formulario transparente en funcionamiento.


'
'----------------------------------------------------------------------------' Prueba de WS_EX_LAYERED
(24/Abr/00)
' Slo para Windows 2000
'
' Guillermo 'guille' Som, 2000
'
' Parte del cdigo est basado en un ejemplo de C++ publicado en:
' MSDN news January/February 2000 Volume 9, Number 1
' Autores: Vadim Gorokhovsky y Lou Amadio
'
' Agradecimientos a Bill McCarthy y Tomas Restrepo por facilitarme
el valor
' de WS_EX_LAYERED
'----------------------------------------------------------------------------Option Explicit
Private mAlpha As Long

' Declaraciones para Layered Windows (slo Windows 2000 y superior)


Private Const WS_EX_LAYERED As Long = &H80000
Private Const LWA_ALPHA As Long = &H2
'
Private Declare Function SetLayeredWindowAttributes Lib "user32" _
(ByVal hWnd As Long, ByVal crKey As Long, _
ByVal bAlpha As Long, ByVal dwFlags As Long) As Long
'----------------------------------------------------------------------------Private Const GWL_EXSTYLE = (-20)
Private Declare Function SetWindowLong Lib "user32" Alias
"SetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As
Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias
"GetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long) As Long

Private Const RDW_INVALIDATE = &H1


Private Const RDW_ERASE = &H4
Private Const RDW_ALLCHILDREN = &H80
Private Const RDW_FRAME = &H400
Private Declare Function RedrawWindow2 Lib "user32" Alias
"RedrawWindow" _
(ByVal hWnd As Long, ByVal lprcUpdate As Long, ByVal hrgnUpdate
As Long, _
ByVal fuRedraw As Long) As Long

Private Sub cmdFade_Click()


' Hacer efecto Fade
'
If cmdFade.Caption = "Hacer &Fade" Then
cmdFade.Caption = "Quitar &Fade"
' Guardar el valor actual del TextBox

With txtAlpha
.Tag = .Text
End With
' Para que no se ponga negra antes de empezar el fade,
' seguramente es una chapuza, pero funciona!
Hide
txtAlpha = "1"
cmdLayered_Click 0
Show
'// Set WS_EX_LAYERED on this window
Call SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd,
GWL_EXSTYLE) Or WS_EX_LAYERED)
' Empezar el efecto desde 20% de transparencia
Const cAlpha As Long = 20
mAlpha = cAlpha
Timer1.Interval = txtInterval
Timer1.Enabled = True
Else
cmdFade.Caption = "Hacer &Fade"
Timer1.Enabled = False
' Quitar el efecto Layered
cmdLayered_Click 1
' Volver a dejar el valor que haba
With txtAlpha
.Text = .Tag
End With
End If
End Sub

Private Sub cmdLayered_Click(Index As Integer)


If Index = 0 Then
Dim tAlpha As Long

' Aplicar el efecto

tAlpha = Val(txtAlpha)
If tAlpha < 1 Or tAlpha > 100 Then
tAlpha = 70
End If
'// Set WS_EX_LAYERED on this window
Call SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd,
GWL_EXSTYLE) Or WS_EX_LAYERED)
'// Make this window tAlpha% alpha
Call SetLayeredWindowAttributes(hWnd, 0, (255 * tAlpha) /
100, LWA_ALPHA)
Else

' Quitar el efecto


'// Remove WS_EX_LAYERED from this window styles

Call SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd,


GWL_EXSTYLE) And Not WS_EX_LAYERED)
'// Ask the window and its children to repaint
Call RedrawWindow2(hWnd, 0&, 0&, RDW_ERASE Or RDW_INVALIDATE
Or RDW_FRAME Or RDW_ALLCHILDREN)
End If
End Sub

Private Sub cmdSalir_Click()


Unload Me
End Sub

Private Sub Form_Load()


' Deshabilitar el temporizador
Timer1.Enabled = False
' Aplicar el efecto
cmdLayered_Click 0
End Sub

Private Sub Timer1_Timer()

' Mostrar el valor...


txtAlpha = mAlpha
'// Make this window tAlpha% alpha
Call SetLayeredWindowAttributes(hWnd, 0, (255 * mAlpha) / 100,
LWA_ALPHA)
mAlpha = mAlpha + 10
If mAlpha > 100 Then
Timer1.Enabled = False
cmdLayered_Click 1
cmdFade.Caption = "Hacer &Fade"
' Volver a dejar el valor que haba
With txtAlpha
.Text = .Tag
End With
End If
End Sub
27.- Posicionarse al principio o final de un MSFlexGrid (19/Ago/00)
Es una llamada a la api de Windows (send message) que lo que hace es mandarte
al principio o al final de un control msflexgrid.

Cdigo de ejemplo:
Crea un nuevo proyecto, aade un MSFlexGrid (Flex), dos botones (Command1 y
Command2) y aade el siguiente cdigo al formulario (Form1):
'
'----------------------------------------------------------------------------' From: "norberto horacio miras" <nmiras@topmail.com.ar>
' Sent: Friday, August 18, 2000 3:53 PM
' Subject: Te mando un truco de Api de Windows
'
' Descripcin:
' Es una llamada a la api de Windows (send message)
' que lo que hace es mandarte al principio o al final de un control
msflexgrid.
'
' Modificado: Guillermo 'guille' Som, 19/Ago/2000
'----------------------------------------------------------------------------Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA"
_

(ByVal hWnd As Long, ByVal wMsg As Long, _


ByVal wParam As Long, lParam As Any) As Long
Private Sub Command1_Click()
Dim var As Long
Const WM_VSCROLL = &H115
Const SB_TOP = 6
var = SendMessage(Flex.hWnd, WM_VSCROLL, SB_TOP, 0)
End Sub
Private Sub Command2_Click()
Dim var As Long
Const WM_VSCROLL = &H115
Const SB_BOTTOM = 7
var = SendMessage(Flex.hWnd, WM_VSCROLL, SB_BOTTOM, 0)
End Sub
28.- cLocaleInfo: clase para obtener la configuracin regional de Windows
(23/Mar/01, 29/Oct/02)
Esta clase la tena por ah medio olvidada, (la hice en Septiembre del 99 y
ni me acordaba que la tena) y hoy la he desempolvado para que puedas
usarla, si as lo crees conveniente, no voy a ser yo el que te obligue a
usarla... faltara ms!
Si no quieres usarla en forma de clase, al menos puedes tomar la parte que
necesites, por ejemplo el formato de la fecha, el signo decimal, etc. Aqu te
dejo el cdigo, tanto de la clase como de una pequea prueba para mostrar
la informacin de la configuracin regional (local) del Windows.
Espero que te sea de utilidad.
Nos vemos.
Guillermo
Nota del 29/Oct/02:
Gracias a Jos Mara Mata por la informacin del "pequeo" bug en la
funcin TrimNull, aunque no por eso dejaba de ser funcional, pero as es
ms correcto.
El cdigo incluido en el zip ya tiene corregido ese fallo.
El cdigo mostrado, est "coloreado" para que resulte ms fcil la lectura.
Este Link al fichero ZIP con el cdigo de ejemplo: LocaleInfo.zip 5.41 KB

'----------------------------------------------------------------------------' CLocaleInfo
(03/Sep/99)
' Clase para obtener la informacin del idioma actual
del sistema
'

' Probado en Windows 2000 Professional


(23/Mar/01)
' Cambio en la funcin TrimNull
(29/Oct/02)
'
' Guillermo 'guille' Som, 1999-2002
'----------------------------------------------------------------------------Option Explicit
' LCID del idioma actual
Private LCID As Long
' Longitud del buffer para obtener la informacin
Private Const MAX_LOCALE_BUF As Long = 256&
' formato de la fecha
Public Enum eDateOrder
MDY = 0 ' Month-Day-Year
DMY = 1 ' Day-Month-Year
YMD = 2 ' Year-Month-Day
End Enum
'----------------------------------------------------------------------------' Funciones del API para la informacin local
'----------------------------------------------------------------------------'Private Type FILETIME
'

dwLowDateTime As Long

'

dwHighDateTime As Long

'End Type
Private Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer

End Type
'Private Declare Function GetUserDefaultLangID Lib
"kernel32" () As Integer
Private Declare Function GetSystemDefaultLangID Lib
"kernel32" () As Integer
Private Declare Function SetLocaleInfo Lib "kernel32"
Alias "SetLocaleInfoA" _
(ByVal Locale As Long, ByVal LCType As Long, _
ByVal lpLCData As String) As Long
Private Declare Function GetLocaleInfo Lib "kernel32"
Alias "GetLocaleInfoA" _
(ByVal Locale As Long, ByVal LCType As Long, _
ByVal lpLCData As String, ByVal cchData As Long) As

Long

Private Declare Function GetTimeFormat Lib "kernel32"


Alias "GetTimeFormatA" _
(ByVal Locale As Long, ByVal dwFlags As Long, _
lpTime As SYSTEMTIME, ByVal lpFormat As String, _
ByVal lpTimeStr As String, ByVal cchTime As Long) As

Long

Private Declare Function GetDateFormat Lib "kernel32"


Alias "GetDateFormatA" _
(ByVal Locale As Long, ByVal dwFlags As Long, _
lpDate As SYSTEMTIME, ByVal lpFormat As String, _
ByVal lpDateStr As String, ByVal cchDate As Long) As

Long

'----------------------------------------------------------------------------' Constantes
'----------------------------------------------------------------------------' Locale Types.
' These types are used for the GetLocaleInfo NLS API
routine.
'
Public Enum eLocaleInfo
LOCALE_ILANGUAGE = &H1

'

language

LOCALE_SLANGUAGE = &H2
name of language

'

localized

LOCALE_SENGLANGUAGE = &H1001
name of language

'

English

id

LOCALE_SABBREVLANGNAME = &H3
abbreviated language name

'

LOCALE_SNATIVELANGNAME = &H4
name of language

'

native

'

country

LOCALE_SCOUNTRY = &H6
name of country

'

localized

LOCALE_SENGCOUNTRY = &H1002
name of country

'

English

LOCALE_SABBREVCTRYNAME = &H7
abbreviated country name

'

LOCALE_SNATIVECTRYNAME = &H8
name of country

'

native

LOCALE_IDEFAULTLANGUAGE = &H9
language id

'

default

LOCALE_IDEFAULTCOUNTRY = &HA
country code

'

default

LOCALE_IDEFAULTCODEPAGE = &HB
code page

'

default

LOCALE_SLIST = &HC
separator

'

list item

LOCALE_IMEASURE = &HD
metric, 1 = US

'

0 =

LOCALE_SDECIMAL = &HE
separator

'

decimal

LOCALE_STHOUSAND = &HF
separator

'

thousand

LOCALE_SGROUPING = &H10
grouping

'

digit

LOCALE_IDIGITS = &H11
fractional digits

'

number of

LOCALE_ILZERO = &H12
zeros for decimal

'

leading

LOCALE_SNATIVEDIGITS = &H13
ascii 0-9

'

native

LOCALE_SCURRENCY = &H14
monetary symbol

'

local

LOCALE_SINTLSYMBOL = &H15
monetary symbol

'

intl

LOCALE_SMONDECIMALSEP = &H16
decimal separator

'

monetary

LOCALE_SMONTHOUSANDSEP = &H17
thousand separator

'

monetary

LOCALE_ICOUNTRY = &H5
code

'

'

'

LOCALE_SMONGROUPING = &H18
grouping

'

monetary

LOCALE_ICURRDIGITS = &H19
monetary digits

'

# local

LOCALE_IINTLCURRDIGITS = &H1A
monetary digits

'

# intl

LOCALE_ICURRENCY = &H1B
currency mode

'

positive

LOCALE_INEGCURR = &H1C
currency mode

'

negative

LOCALE_SDATE = &H1D
separator

'

date

LOCALE_STIME = &H1E
separator

'

time

LOCALE_SSHORTDATE = &H1F
date format string

'

short

LOCALE_SLONGDATE = &H20
format string

'

long date

LOCALE_STIMEFORMAT = &H1003
format string

'

time

LOCALE_IDATE = &H21
date format ordering

'

short

LOCALE_ILDATE = &H22
format ordering

'

long date

LOCALE_ITIME = &H23
format specifier

'

time

LOCALE_ICENTURY = &H24
format specifier

'

century

LOCALE_ITLZERO = &H25
zeros in time field

'

leading

LOCALE_IDAYLZERO = &H26
zeros in day field

'

leading

LOCALE_IMONLZERO = &H27
zeros in month field

'

leading

LOCALE_S1159 = &H28
designator

'

AM

LOCALE_S2359 = &H29
designator

'

PM

LOCALE_SDAYNAME1 = &H2A
for Monday

'

long name

LOCALE_SDAYNAME2 = &H2B
for Tuesday

'

long name

LOCALE_SDAYNAME3 = &H2C
for Wednesday

'

long name

'

'

LOCALE_SDAYNAME4 = &H2D
for Thursday

'

long name

LOCALE_SDAYNAME5 = &H2E
for Friday

'

long name

LOCALE_SDAYNAME6 = &H2F
for Saturday

'

long name

LOCALE_SDAYNAME7 = &H30
for Sunday

'

long name

LOCALE_SABBREVDAYNAME1 = &H31
abbreviated name for Monday

'

LOCALE_SABBREVDAYNAME2 = &H32
abbreviated name for Tuesday

'

LOCALE_SABBREVDAYNAME3 = &H33
abbreviated name for Wednesday

'

LOCALE_SABBREVDAYNAME4 = &H34
abbreviated name for Thursday

'

LOCALE_SABBREVDAYNAME5 = &H35
abbreviated name for Friday

'

LOCALE_SABBREVDAYNAME6 = &H36
abbreviated name for Saturday

'

LOCALE_SABBREVDAYNAME7 = &H37
abbreviated name for Sunday

'

LOCALE_SMONTHNAME1 = &H38
for January

'

long name

LOCALE_SMONTHNAME2 = &H39
for February

'

long name

LOCALE_SMONTHNAME3 = &H3A
for March

'

long name

LOCALE_SMONTHNAME4 = &H3B
for April

'

long name

LOCALE_SMONTHNAME5 = &H3C
for May

'

long name

LOCALE_SMONTHNAME6 = &H3D
for June

'

long name

LOCALE_SMONTHNAME7 = &H3E
for July

'

long name

LOCALE_SMONTHNAME8 = &H3F
for August

'

long name

LOCALE_SMONTHNAME9 = &H40
for September

'

long name

LOCALE_SMONTHNAME10 = &H41
for October

'

long name

LOCALE_SMONTHNAME11 = &H42
for November

'

long name

LOCALE_SMONTHNAME12 = &H43
for December

'

long name

LOCALE_SABBREVMONTHNAME1 = &H44
abbreviated name for January

'

LOCALE_SABBREVMONTHNAME2 = &H45
abbreviated name for February

'

LOCALE_SABBREVMONTHNAME3 = &H46
abbreviated name for March

'

LOCALE_SABBREVMONTHNAME4 = &H47
abbreviated name for April

'

LOCALE_SABBREVMONTHNAME5 = &H48
abbreviated name for May

'

LOCALE_SABBREVMONTHNAME6 = &H49
abbreviated name for June

'

LOCALE_SABBREVMONTHNAME7 = &H4A
abbreviated name for July

'

LOCALE_SABBREVMONTHNAME8 = &H4B
abbreviated name for August

'

LOCALE_SABBREVMONTHNAME9 = &H4C
abbreviated name for September

'

LOCALE_SABBREVMONTHNAME10 = &H4D
abbreviated name for October

'

LOCALE_SABBREVMONTHNAME11 = &H4E
abbreviated name for November

'

LOCALE_SABBREVMONTHNAME12 = &H4F
abbreviated name for December

'

LOCALE_SABBREVMONTHNAME13 = &H100F
'
LOCALE_SPOSITIVESIGN = &H50

'

positive

'

negative

LOCALE_IPOSSIGNPOSN = &H52
sign position

'

positive

LOCALE_INEGSIGNPOSN = &H53
sign position

'

negative

LOCALE_IPOSSYMPRECEDES = &H54
precedes pos amt

'

mon sym

LOCALE_IPOSSEPBYSPACE = &H55
sep by space from pos amt

'

mon sym

LOCALE_INEGSYMPRECEDES = &H56
precedes neg amt

'

mon sym

LOCALE_INEGSEPBYSPACE = &H57
sep by space from neg amt

'

mon sym

sign
LOCALE_SNEGATIVESIGN = &H51
sign

End Enum
' Time Flags for GetTimeFormatW.
Private Const TIME_NOMINUTESORSECONDS = &H1 '
use minutes or seconds

do not

Private Const TIME_NOSECONDS = &H2


use seconds

do not

'

Private Const TIME_NOTIMEMARKER = &H4


use time marker

'

do not

Private Const TIME_FORCE24HOURFORMAT = &H8


use 24 hour format

'

always

Private Const DATE_SHORTDATE = &H1


date picture

'

use short

Private Const DATE_LONGDATE = &H2


date picture

'

use long

' Date Flags for GetDateFormatW.

Public Function NativeLanguage() As String


' Devuelve el idioma actual, en el propio idioma
NativeLanguage = LocaleInfo(LOCALE_SNATIVELANGNAME)
End Function
Public Function Language() As String
' Devuelve el idioma actual
Language = LocaleInfo(LOCALE_SLANGUAGE)
End Function
Public Function Thousands() As String
' Devuelve el signo de los miles
Thousands = LocaleInfo(LOCALE_STHOUSAND)
End Function
Public Function sDecimal() As String
' Devuelve el signo decimal
sDecimal = LocaleInfo(LOCALE_SDECIMAL)
End Function
Private Function TrimNull(ByVal sCadena As String) As
String
' Devuelve la cadena hasta el primer null
' Tambin quita los espacios extras
Dim i As Long
i = InStr(sCadena, Chr$(0))
If i Then
'TrimNull = Left$(sCadena, i - 1)
' esta sera la asignacin correcta
(29/Oct/02)

' gracias a Jos Mara Mata


sCadena = Left$(sCadena, i - 1)
End If
TrimNull = Trim$(sCadena)
End Function
Private Sub Class_Initialize()
' Al iniciar la clase, obtener el ID del idioma
usado
GetLCID
End Sub
Public Function GetLCID() As Long
' Obtener el ID del idioma usado
' Lo he puesto en un mtodo pblico por si se quiere
refrescar la informacin
LCID = GetSystemDefaultLangID
GetLCID = LCID
End Function
Public Function LocaleInfo(Optional ByVal nLocaleInfo As
eLocaleInfo = LOCALE_SENGLANGUAGE) As String
' Devuelve la informacin indicada en el parmetro
' Si no se especifica el parmetro,
' ser el nombre del idioma segn la norma ISO (en
ingls)
Dim sBuf As String
sBuf = Space$(MAX_LOCALE_BUF)
Call GetLocaleInfo(LCID, nLocaleInfo, sBuf,
Len(sBuf))
LocaleInfo = TrimNull(sBuf)
End Function
Public Function ShortDateFormat() As String
' Devuelve el formato de la fecha corta
ShortDateFormat = LocaleInfo(LOCALE_SSHORTDATE)
End Function

Public Function LongDateFormat() As String


' Devuelve el formato de la fecha larga
LongDateFormat = LocaleInfo(LOCALE_SLONGDATE)
End Function
Public Function NativeCountryName() As String
' Devuelve el nombre del pais (en el idioma propio)
NativeCountryName =
LocaleInfo(LOCALE_SNATIVECTRYNAME)
End Function
Public Function CountryName() As String
' Devuelve el nombre del pais (en ingls)
CountryName = LocaleInfo(LOCALE_SCOUNTRY)
End Function
Public Function LongDateFormatOrder() As String
' El orden de la fecha larga (MDY, DMY, YMD)
Dim i As eDateOrder
i = LocaleInfo(LOCALE_ILDATE)
If i = DMY Then
LongDateFormatOrder = "DMY"
ElseIf i = MDY Then
LongDateFormatOrder = "MDY"
Else
LongDateFormatOrder = "YMD"
End If
End Function
Public Function DateFormatOrder() As String
' El orden de la fecha corta (MDY, DMY, YMD)
Dim i As eDateOrder
i = LocaleInfo(LOCALE_IDATE)
If i = DMY Then
DateFormatOrder = "DMY"
ElseIf i = MDY Then
DateFormatOrder = "MDY"

Else
DateFormatOrder = "YMD"
End If
End Function

El formulario de prueba (foto incluida)

'----------------------------------------------------------------------------' Prueba de Locale Info


(03/Sep/99)
' Para mostrar algunos aspectos de la configuracin
regional
'
' Probado en Windows 2000 Professional
(23/Mar/01)
'
' Guillermo 'guille' Som, 1999-2002
'----------------------------------------------------------------------------Option Explicit
Private Sub cmdRefresh_Click()
ActualizarLCDInfo
End Sub
Private Sub Form_Load()
ActualizarLCDInfo
End Sub
Private Sub ActualizarLCDInfo()
Dim tLocaleInfo As CLocaleInfo
Set tLocaleInfo = New CLocaleInfo
With tLocaleInfo
Text1(0) = .LocaleInfo
Text1(1) = .NativeLanguage
Text1(2) = .CountryName
Text1(3) = .NativeCountryName

'Text1(3) = .LocaleInfo(LOCALE_SNATIVECTRYNAME)
Text1(4) = .sDecimal
Text1(5) = .Thousands
Text1(6) = .ShortDateFormat
Text1(7) = .LongDateFormat
Text1(8) = .DateFormatOrder '& " , " &
.LongDateFormatOrder
Text1(9) = .LocaleInfo(LOCALE_SINTLSYMBOL)
Text1(10) = .LocaleInfo(LOCALE_SCURRENCY)
End With
End Sub

El formulario en ejecucin

29.- GetLogicalDrives y GetLogicalDriveStrings, funciones para saber las


unidades lgicas de nuestro equipo (17/Abr/01)

Hay ocasiones en las que necesitamos saber que unidades tenemos instaladas
en nuestro equipo e incluso puede que lo que necesitemos saber sea las letras

que estn disponibles para usarlas en conexiones de red.


En esas ocasiones podemos usar la funcin del API GetLogicalDrives para obtener
esa informacin.
La funcin GetLogicalDrives devuelve un valor LONG con las unidades lgicas
disponibles en el sistema.
El "problemilla" es que dicho valor es una "mscara de bits", es decir, si la unidad A
existe en nuestro sistema, el bit 0 ser 1, si no existe, ser un cero. La unidad C
est representada por el segundo bit y as sucesivamente.
Por tanto, podemos hacer un bucle desde 0 hasta 25, (para las unidades A hasta
Z), comprobando si el bit est conectado (1) o desconectado (0), para saber si la
unidad existe o no existe respectivamente.
Veamos el cdigo de ejemplo para mostrar las letras de unidades instaladas y las
disponibles, (las que no estn instaladas).
La declaracin de esta funcin (para 32 bits) es:
Private Declare Function GetLogicalDrives Lib "kernel32" () As Long
En el siguiente ejemplo, usaremos dos controles ComboBox, uno para las unidades
ocupadas y el otro para las disponibles:
Dim i As Long
Dim ret As Long
'
ret = GetLogicalDrives()
If ret Then
For i = 0 To 25
' Si el bit es cero, es que no existe la unidad o no est

mapeada

If (ret And 2 ^ i) = 0 Then


' Mostrar el nombre de la unidad disponible
Combo2.AddItem Chr$(i + 65) & ":"
Else
' Mostrar el nombre de la unidad ocupada
Combo1.AddItem Chr$(i + 65) & ":"
End If
Next
' Mostrar la primera letra disponible
If Combo2.ListCount > 0 Then
Combo2.ListIndex = 0
End If
' Mostrar la primera letra ocupada
If Combo1.ListCount > 0 Then

Combo1.ListIndex = 0
End If
End If
En el cdigo mostrado, se hace una comprobacin de los 26 primeros bits
(empiezan a contar por cero), si el contenido de dicho bit es UNO, quiere decir que
la unidad comprobada est asignada, sin embargo, si el contenido de ese bit es
CERO, es que dicha unidad no est asignada, por tanto, est disponible.
Adems de esta funcin, existe otra: GetLogicalDriveStrings, con la que se
pueden obtener las unidades disponibles, pero el resultado se devuelve como una
cadena de caracteres; cada una de esas unidades estarn separadas por un valor
Chr$(0).
Aqu te dejo la declaracin de dicha funcin:

Private Declare Function GetLogicalDriveStrings Lib "kernel32" Alias


"GetLogicalDriveStringsA" _
(ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
Este cdigo asignar a Combo1 las unidades disponibles en nuestro equipo:
Dim i As Long
Dim ret As Long
Dim s As String
'
i = 260
s = String$(i, Chr$(0))
ret = GetLogicalDriveStrings(i, s)
' Si el valor devuelto es mayor que el tamao del buffer, es que el
buffer debe ser mayor
If ret > i Then
i = ret + 2
s = String$(i, Chr$(0))
ret = GetLogicalDriveStrings(i, s)
End If
'
If ret Then
' Quitar los caracteres extras
s = Left$(s, ret)
Do
i = InStr(s, Chr$(0))
If i Then

Combo1.AddItem Left$(s, i - 1)
s = Mid$(s, i + 1)
End If
Loop While i
' Mostrar la primera letra disponible
If Combo1.ListCount > 0 Then
Combo1.ListIndex = 0
End If
End If
Fjate que en esete caso se incluye la barra junto al nombre de la unidad.
Estas funciones podemos usarlas junto a GetDriveType, para saber de que tipo,
(Fija, extraible, CD-ROM, etc.), es cada una de las unidades que tenemos
instaladas.

30.- GetPrinterJobs: Saber el nmero de trabajos pendientes de imprimir


(09/Jun/01)
Y yo que me quejo de las consultas...
Pues eso, que esta maana recib una consulta sobre cmo saber el nmero
de trabajos pendientes de imprimir de una impresora.
Estuve buscando en la MSDN Library y estuve viendo las funciones del API y
los tipos definidos que se podran usar para obtener esa informacin,
pero... al intentar adaptarlo al Visual Basic... nada de nada, (aparte de
algn que otro desplante... por fallo de acceso a la memoria y esas cosas
que suelen ocurrir cuando se accede al API)... segu buscando y me
encontr con un ejemplo en C/C++ que, entre otras cosas, mostraba cmo
obtener el nmero de trabajos pendientes de imprimir, (que era lo que
buscaba), pero haca uso de "reserva de memoria" y otras cosas que el
Visual Basic tiene vetado... As que, me lanc a "desempolvar" algunas
pruebas que hice con el CBuilder (s, de Borland) y me puse en la tarea de
adaptar el "trozo" de cdigo C que encontr.
Y este es el resultado:
(Perdona toda esta chchara, pero es que si no... pues iba a ser el artculo
como muy corto...)
Para usar la funcin:
MsgBox "Nmero de trabajos pendientes de la impresora " &
Printer.DeviceName & _
" es: " & GetPrinterJobs(Printer.DeviceName)

Aqu tienes el cdigo de la funcin (en C/C++), as como el que habra que
usar desde Visual Basic.

/
*---------------------------------------------------------------------------gsPrinterJobs.dll
(08/Jun/01)
DLL para devolver el nmero de trabajos pendiente de
una impresora
Basado en un ejemplo de la MSDN Library
Guillermo 'guille' Som, 2001
//---------------------------------------------------------------------------*/
#include <windows.h>
/*
Devuelve el nmero de trabajos pendientes de la
impresora indicada
o -1 si se produce algn error.
El parmetro ser el nombre de la impresora
(Printer.DeviceName)
*/
extern "C" WINBASEAPI DWORD WINAPI FAR PASCAL _export
GetPrinterJobs(BSTR bstrDeviceName)
{
HANDLE

hPrinter;

DWORD

cByteNeeded;

PRINTER_INFO_2

*pPrinterInfo = NULL;

LPSTR

strSrcByVal;

DWORD

Fallo = -1;

// Copia de la cadena en formato C/C++


strSrcByVal = (LPSTR)bstrDeviceName;

// Abrir la impresora
if (!OpenPrinter(strSrcByVal, &hPrinter, NULL))
return Fallo;
// Obtener el tamao del buffer
if (!GetPrinter(hPrinter, 2, NULL, 0, &cByteNeeded))
{
// Si da otro error distinto del esperado...
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return Fallo;
}
// Reserva memoria para el buffer
pPrinterInfo = (PRINTER_INFO_2 *)malloc(cByteNeeded);
if (!(pPrinterInfo))
// fallo en la reserva de memoria
return Fallo;
// Obtener la informacin de la impresora
if (!GetPrinter(hPrinter,
2,
(LPSTR)pPrinterInfo,
cByteNeeded,
&cByteNeeded))
{
// fallo en el acceso al puntero
free(pPrinterInfo);
pPrinterInfo = NULL;
return Fallo;
}
// Cerrar la impresora
ClosePrinter(hPrinter);
// Devolver el valor de cJobs
return (DWORD)pPrinterInfo->cJobs;
}
Ejemplo de cmo usar esta funcin en Visual Basic:

'----------------------------------------------------------------------------' Prueba para saber los trabajos pendientes de una


impresora
(08/Jun/01)
' Usando una DLL escrita en CBuilder
'
' Guillermo 'guille' Som, 2001
'----------------------------------------------------------------------------Option Explicit
' Declaracin de la funcin
Private Declare Function GetPrinterJobs Lib
"gsPrinterJobs.dll" _
(ByVal sDeviceName As String) As Long
Private Sub cmdInfo_Click()
Dim s As String
Dim n As Long
Const Fallo As Long = -1&
'
s = cboPrinters.Text
'
n = GetPrinterJobs(s)
If n = Fallo Then
List1.AddItem "Fallo al llamar a la funcin"
Else
List1.AddItem s
List1.AddItem "Nmero de trabajos pendientes: "

& n

End If
End Sub
Private Sub Form_Load()
' Enumerar las impresoras disponibles
Dim tPrinter As Printer
'
' Aadir las impresoras disponibles
For Each tPrinter In Printers
cboPrinters.AddItem tPrinter.DeviceName

Next
' Asignar la variable de la impresora seleccionada
Set tPrinter = Printer
If cboPrinters.ListCount > 0 Then
cboPrinters.Text = tPrinter.DeviceName
End If
End Sub
Este es el aspecto del formulario:

Espero que te pueda ser de utilidad.


Nos vemos.
Guillermo
P.S.
Aqu tienes el cdigo C/C++ y del ejemplo en VB: PrinterJobs.zip 59.6KB
(actualizado el 13/Jun/2001)

13/Jun/2001: Nmero de pginas pendientes de


imprimir.
Le he aadido una nueva funcin a la librera, en este caso es para saber
tambin el nmero de pginas que quedan por imprimir (adems del
nmero de trabajos pendientes)
He de avisar de que no todas las impresoras tienen esta caracterstica, la he
probado en una HP 840C y funciona bien, (como podrs comprobar en la
imagen).

La nueva funcin se llama: GetNumPages y se declara de la siguiente


forma:
fjate que el segundo parmetro es ByRef, para que se devuelva en la
variable el nmero de trabajos pendientes.

Private Declare Function GetNumPages Lib


"gsPrinterJobs.dll" _
(ByVal sDeviceName As String, ByRef NumJobs As Long)
As Long
Para usarla:
Dim s As String
Dim n As Long
Const Fallo As Long = -1&
Dim p As Long
'
s = cboPrinters.Text
' Nmero de trabajos pendientes y nmero de pginas
(11/Jun/01)
'
n = GetNumPages(s, p)
If n = Fallo Then
List1.AddItem "Fallo al llamar a la funcin"
Else
List1.AddItem s
List1.AddItem "Nmero de trabajos pendientes: "
& p & ", pginas: " & n
End If

Aqu tienes el cdigo en C/C++ de la nueva funcin:

/*
Funcin para leer el nmero de pginas pendientes de
imprimir
(11/Jun/01)
Basado en un ejemplo de la MSDN Library:
How To Call Win32 Spooler Enumeration APIs Properly
*/
// Devuelve el nmero de pginas por imprimir y en el
segundo parmetro,
// (por referencia) el nmero de trabajos pendientes
extern "C" WINBASEAPI DWORD WINAPI FAR PASCAL _export
GetNumPages(BSTR bstrDeviceName, DWORD *NumJobs)
{
HANDLE

hPrinter;

DWORD

dwNeeded, dwReturned, i;

JOB_INFO_1

*pJobInfo;

LPTSTR

szPrinterName;

DWORD

Fallo = -1;

DWORD

TotalPaginas=0;

// Copia de la cadena en formato C/C++


szPrinterName = (LPSTR)bstrDeviceName;
// You need a printer handle, open the printer
if( ! OpenPrinter( szPrinterName, &hPrinter, NULL ) )
return Fallo;
// First you call EnumJobs() to find out how much memory
you need
if( ! EnumJobs( hPrinter, 0, 0xFFFFFFFF, 1, NULL, 0,
&dwNeeded,
&dwReturned ) )
{
// It should have failed, but if it failed for any
reason other
// than "not enough memory", you should bail out
if( GetLastError() != ERROR_INSUFFICIENT_BUFFER )
{
ClosePrinter( hPrinter );

return Fallo;
}
}
// Allocate enough memory for the JOB_INFO_1 structures
plus
// the extra data - dwNeeded from the previous call
tells you
// the total size needed
if( (pJobInfo = (JOB_INFO_1 *)malloc( dwNeeded )) ==
NULL )
{
ClosePrinter( hPrinter );
return Fallo;
}
// Call EnumJobs() again and let it fill out our
structures
if( ! EnumJobs( hPrinter, 0, 0xFFFFFFFF, 1,
(LPBYTE)pJobInfo,
dwNeeded, &dwNeeded, &dwReturned ) )
{
ClosePrinter( hPrinter );
free( pJobInfo );
return Fallo;
}
// You're done with the printer handle, close it
ClosePrinter( hPrinter );
// dwReturned tells how many jobs there are
// Here, you'll simply display the number of jobs found
*NumJobs = dwReturned;
TotalPaginas = 0;
// It's easy to loop through the jobs and access each
one
for(i=0;i<dwReturned;i++)
{
// pJobInfo[i] is a JOB_INFO_1 struct for that job
// so here you could do whatever you want for each job
TotalPaginas = TotalPaginas +
pJobInfo[i].TotalPages;
}

// Clean up
free( pJobInfo );
return (DWORD)TotalPaginas;
}

31.- Deshabilitar el botn cerrar de un formulario (20/Jun/01)


En ocasiones puede ser interesante poder deshabilitar el botn cerrar (el de la X
que hay a la derecha de los formularios), sobre todo si lo que mostramos son
cuadros de dilogo.
De esta forma, evitamos que el usuario pueda cerrar el formulario tanto desde el
botoncito de la equis, como desde el men "cerrar" del controlbox.
A continuacin te voy a mostrar dos formas para hacer lo que indica el ttulo.
Una es usando funciones catalogadas como "obsoletas" y que han sido heredadas
del API de Windows de 16 bits, que aunque funcionales, no se recomienda su uso.
La otra es usando las funciones que sustituyen a esas que ya estn obsoletas.
Adems de ser funciones "actuales", nos dan la posibilidad de ms control sobre lo
que hacemos.
El cdigo de la primera forma de hacer que el botn cerrar quede deshabilitado (y
se pueda volver a habilitar), est basado en un ejemplo de la Knowledge Base y
aunque era para la versin 3.0 del Visual Basic, no ha habido problemas para
adaptarlo. Aunque he tenido que aadirle algunas cosillas para que sea 100%
operativo (lo he probado con Windows 2000 y funciona perfectamente, espero que
con versiones anteriores no haya problemas).
El cdigo de la segunda forma, es de mi cosecha, aunque basndome en el cdigo
del referido artculo.
Lo que sigue es vlido para las dos formas de hacerlo. Seguramente ser ms fcil
de entender y de codificar usando la primera, aunque con la segunda nos permite
usar la aplicacin sin importar el idioma del sistema operativo.
Como vers dentro de poco, en cuanto acabe esta "verborrea", usando las
funciones "obsoletas", tenemos que indicarle el Caption del men cerrar, sin
embargo con la segunda forma, podemos averiguar el Caption que tena asignado
y usar el mismo... si est en castellano, el caption ser Cerrar, pero si el idioma del
Windows es ingls, ser Close.
Vamos con un "poquillo" ms de teora y despus veremos el cdigo.
Hay que tener en cuenta que a la hora de deshabilitar el control cerrar, (y poner la
"x" en un gris tpico de deshabilitado), se puede hacer de dos formas:
1- Mientras se carga el formulario, dentro del evento Form_Load
2- Una vez que est el formulario mostrado.
Ahora veremos cmo hacerlo para las dos ocasiones.

1- En el caso de que la "deshabilitacin" del botn se haga en el evento


Form_Load, simplemente se le indica al Windows que modifique el men indicado y
lo ponga deshabilitado.
2-Si se quiere deshabilitar cuando el formulario est mostrado, adems del cdigo
que deshabilita el men, hay que hacer que se redibujen los mens. Este mismo
caso se da cuando se vuelve a habilitar. Si no se dibujan de nuevo los mens, se
quedar habilitado/deshabilitado, pero el aspecto no ser el que debe ser.
Bueno, dejemos la "chchara" y vayamos a ver el cdigo que necesitamos.
El formulario de prueba, tiene dos botones: uno para habilitar el men y la "x" y
otro para deshabilitarlo (ponerlo gris) y hacerlo inoperativo.
Copia el siguiente cdigo y pegalo en el formulario.
Las declaraciones de las funciones del API estn declaradas como Private para que
las puedas poner en el formulario, en la parte de Declaraciones/General.

Empecemos viendo el cdigo de las funciones obsoletas, (pero,


recuerda, que operativas y vlidas)
Fjate en el men que, aunque tengas el Windows en un idioma diferente al ingls,
te muestra la palabra Close.

'----------------------------------------------------------------------------' Prueba para deshabilitar el botn cerrar de un formulario


(20/Jun/01)
'
' Basado en un artculo de la KB para VB 3.0 (Q82876)
' How to Disable Close Command in VB Control Menu (System Menu)
'
' Guillermo 'guille' Som, 2001
'----------------------------------------------------------------------------Option Explicit
' Para deshabilitar el men cerrar (controlbox) de un formulario
Private Declare Function GetSystemMenu Lib "user32" _
(ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function ModifyMenu Lib "user32" Alias "ModifyMenuA"
_
(ByVal hMenu As Long, ByVal nPosition As Long, _
ByVal wFlags As Long, ByVal wIDNewItem As Long, _
ByVal lpString As Any) As Long
Private Declare Function DrawMenuBar Lib "user32" _
(ByVal hWnd As Long) As Long

'
Private Const MF_BYCOMMAND = &H0&
Private Const MF_ENABLED = &H0&
Private Const MF_GRAYED = &H1&
'
Private Const SC_CLOSE = &HF060&
Private Sub Form_Load()
' Deshabilitar el botn de cerrar el formulario
Dim hMenu As Long
'
hMenu = GetSystemMenu(hWnd, 0)
' Deshabilitar el men cerrar del formulario
Call ModifyMenu(hMenu, SC_CLOSE, MF_BYCOMMAND Or MF_GRAYED, -10,
"Close")
'
End Sub
Private Sub cmdDeshabilitar_Click()
' Deshabilitar el botn de cerrar el formulario
Dim hMenu As Long
'
hMenu = GetSystemMenu(hWnd, 0)
' Deshabilitar el men cerrar del formulario
Call ModifyMenu(hMenu, SC_CLOSE, MF_BYCOMMAND Or MF_GRAYED, -10,
"Close")
'
' Si esta llamada se hace dentro del Form_Load,
' no es necesario redibujar los mens
' Redibujar los mens, para que se muestre deshabilitado
Call DrawMenuBar(hWnd)
'
End Sub
Private Sub cmdHabilitar_Click()
' Habilitar el botn de cerrar el formulario
Dim hMenu As Long
'
hMenu = GetSystemMenu(hWnd, 0)
' Esto lo habilita, pero sigue en gris...

Call ModifyMenu(hMenu, -10, MF_BYCOMMAND Or MF_ENABLED,


SC_CLOSE, "Close")
' Redibujar los mens, para que se muestre habilitado
Call DrawMenuBar(hWnd)
'
End Sub

Ahora veremos el cdigo con las funciones que se deberan


usar.
Con esta otra forma de hacerlo, el caption del men se muestra igual que estaba
originalmente, aunque tambin se pierde el grfico que por defecto muestra.

'----------------------------------------------------------------------------' Prueba para deshabilitar el botn cerrar de un formulario


(20/Jun/01)
'
' Basado en un artculo de la KB para VB 3.0 (Q82876)
' How to Disable Close Command in VB Control Menu (System Menu)
'
' Guillermo 'guille' Som, 2001
'----------------------------------------------------------------------------Option Explicit
Private sTextClose As String
Private Const hSC_Close As Long = -10&
'
' Para deshabilitar el men cerrar (controlbox) de un formulario
Private Declare Function GetSystemMenu Lib "user32" _
(ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" _
(ByVal hWnd As Long) As Long
'
Private Const MF_BYCOMMAND = &H0&
Private Const MF_STRING = &H0&
Private Const MF_BITMAP = &H4&
Private Const MF_OWNERDRAW = &H100&
Private Const MF_ENABLED = &H0&
Private Const MF_GRAYED = &H1&

Private Const MF_DISABLED = &H2&


' Estos valores son para NT/2000
Private Const MFS_GRAYED = &H3&
Private Const MFS_DISABLED = MFS_GRAYED
Private Const MFT_STRING = MF_STRING
'
Private Const MIIM_STATE = &H1&
Private Const MIIM_ID = &H2&
Private Const MIIM_SUBMENU = &H4&
Private Const MIIM_CHECKMARKS = &H8&
Private Const MIIM_TYPE = &H10&
Private Const MIIM_DATA = &H20&
'
Private Const SC_SIZE = &HF000&
Private Const SC_MOVE = &HF010&
Private Const SC_MINIMIZE = &HF020&
Private Const SC_MAXIMIZE = &HF030&
Private Const SC_NEXTWINDOW = &HF040&
Private Const SC_PREVWINDOW = &HF050&
Private Const SC_CLOSE = &HF060&
Private Type MENUITEMINFO
cbSize As Long
fMask As Long
fType As Long
fState As Long
wID As Long
hSubMenu As Long
hbmpChecked As Long
hbmpUnchecked As Long
dwItemData As Long
dwTypeData As String
cch As Long

' La longitud de dwTypeData

End Type
Private Declare Function GetMenuItemInfo Lib "user32" Alias
"GetMenuItemInfoA" _
(ByVal hMenu As Long, ByVal uItem As Long, _
ByVal fByPosition As Boolean, lpMenuItemInfo As MENUITEMINFO) As
Long

Private Declare Function SetMenuItemInfo Lib "user32" Alias


"SetMenuItemInfoA" _
(ByVal hMenu As Long, ByVal uItem As Long, _
ByVal fByPosition As Boolean, lpcMenuItemInfo As MENUITEMINFO)
As Long
Private Sub Form_Load()
' Obtener el texto del men Cerrar
Dim hMenu As Long
Dim tMenuItemInfo As MENUITEMINFO
'
' Obtener el handle del men del sistema
hMenu = GetSystemMenu(hWnd, 0)
With tMenuItemInfo
.cbSize = Len(tMenuItemInfo)
.fMask = MIIM_DATA Or MIIM_ID Or MIIM_TYPE
.fType = MF_STRING
.cch = 50
.dwTypeData = String$(.cch, Chr$(0))
' Obtener la informacin del men cerrar
Call GetMenuItemInfo(hMenu, SC_CLOSE, False, tMenuItemInfo)
' El Caption del men cerrar (para que sirva para cualquier
idioma de Windows)
sTextClose = Left$(.dwTypeData, .cch)
'
'

' Para deshabilitarlo en el Form_Load

'

.fMask = MIIM_DATA Or MIIM_ID Or MIIM_STATE Or MIIM_TYPE

'

.fState = MF_GRAYED

'

.dwItemData = SC_CLOSE

'

.wID = hSC_Close

'

.fType = MF_STRING

'

.dwTypeData = sTextClose & Chr$(0)

'

.cch = Len(sTextClose) + 1

'

'

'

Call SetMenuItemInfo(hMenu, SC_CLOSE, False, tMenuItemInfo)


End With
'

End Sub

Private Sub cmdDeshabilitar_Click()


' Deshabilitar el botn de cerrar el formulario
Dim hMenu As Long
Dim tMenuItemInfo As MENUITEMINFO
'
hMenu = GetSystemMenu(hWnd, 0)
With tMenuItemInfo
.cbSize = Len(tMenuItemInfo)
.fMask = MIIM_DATA Or MIIM_ID Or MIIM_STATE Or MIIM_TYPE
.fState = MF_GRAYED
.dwItemData = SC_CLOSE
.wID = hSC_Close
.fType = MF_STRING
.dwTypeData = sTextClose & Chr$(0)
.cch = Len(sTextClose) + 1
'
Call SetMenuItemInfo(hMenu, SC_CLOSE, False, tMenuItemInfo)
End With
'
' Si esta llamada se hace dentro del Form_Load,
' no es necesario redibujar los mens
' Redibujar los mens, para que se muestre deshabilitado
Call DrawMenuBar(hWnd)
'
End Sub
Private Sub cmdHabilitar_Click()
' Habilitar el botn de cerrar el formulario
Dim hMenu As Long
Dim tMenuItemInfo As MENUITEMINFO
'
' Obtener el handle del men del sistema
hMenu = GetSystemMenu(hWnd, 0)
With tMenuItemInfo
.cbSize = Len(tMenuItemInfo)
.fMask = MIIM_DATA Or MIIM_ID Or MIIM_STATE Or MIIM_TYPE
.fState = MF_ENABLED
.dwItemData = hSC_Close
.wID = SC_CLOSE

.fType = MF_STRING
.dwTypeData = sTextClose & Chr$(0)
.cch = Len(sTextClose) + 1
End With
Call SetMenuItemInfo(hMenu, hSC_Close, False, tMenuItemInfo)
'
' Redibujar los mens, para que se muestre habilitado
Call DrawMenuBar(hWnd)
'
End Sub
Y eso es todo. Prueba a ejecutar el proyecto, vers que el botn se muestra
deshabilitado y no permite cerrar el formulario.
Pulsa en el botn Habilitar y ya estar habilitado y operativo, si pulsas en el botn
Deshabilitar, se deshabilitar el botn y se mostrar gris.
La explicacin de porqu se usa el valor -10, para cambiar de habilitado a
deshabilitado, est explicado en el artculo de la KB del que he usado parte de la
informacin aqu mostrada:
(Q82876) How to Disable Close Command in VB Control Menu (System Menu)
Bjate el cdigo de ejemplo de las dos formas mostradas: deshabilitarCerrar.zip
4.54 KB

API de Windows (4)


Algunas funciones interesantes del API de Windows
Actualizado el 15/Ago/2007

Funciones y ejemplos:
1. Cmo usar los objetos del Shell Windows (ShellObjects) (07/Sep/01)
2. Cambiar el texto de los botones de un MsgBox (14/Sep/01)
3. Saber el directorio de Windows y el System (15/Oct/01)
4. Especial Resolucin de Pantalla: (04/Nov/01)
Averiguar la resolucin actual y nmero de colores, usando API.
Cambiar/restaurar la resolucin de forma rpida.
Enumerar las resoluciones disponibles y cambiar/restaurar la
resolucin de la pantalla.

5. Convertir un Path de nombre largo a nombre corto (06/Nov/01)


6. Convertir un Path de nombre corto a nombre largo (06/Nov/01)
7. Recorrer un TextBox multiline lnea a lnea (09/Nov/01)
8. Temas de Windows XP: consejos para usarlos desde Visual Basic
clsico (30/Oct/02)
9. Copiar, cortar, pegar, deshacer... usando el API de Windows
(28/Dic/02)
10. Deshabilitar Alt+Tab y otras teclas en Windows NT/2000/XP
(09/Mar/03)
11. Revisin del cdigo para leer ficheros INI desde Visual Basic
(14/Sep/03)
12. Especial Docking:
Poner un formulario dentro de un control picture (25/Ene/04)
Cambiar el tamao de dos controles (split) (25/Ene/04)
13. Poner nuestra aplicacin en el inicio de Windows (registro)
(14/May/04)
14. Equivalencias del API de Windows con .NET (28/Feb/05)
15. Cerrar aplicaciones (clase cWindows) (02/Ene/99 - 12/Ago/05)
16. Averiguar la versin de Windows usando API (24/May/07)
17. Ejecutar un acceso directo desde VB6 con ShellExecute (15/Ago/07)
18. Averiguar la posicin del cursor con GetCursorPos (08/Jul/08)

1. Cmo usar los objetos del Shell Windows (ShellObjects)


(07/Sep/01)
Buscando informacin sobre cmo averiguar la velocidad del procesador
que hay instalado en un equipo, me top con los Shell Objects
proporcionados para la librera Shell32.dll. sta dll se suele usar para
acceder a funciones del API, pero no saba yo que tambin era una librera
ActiveX...
En este primer artculo vamos a ver los mtodos proporcionados por el
objeto Shell, as como un ejemplo de cmo usarlos.
Segn la MSDN Library, los objetos Shell representan los objetos en el
Shell, (as como te lo cuento).
Este objeto proporciona mtodos con los cuales se pueden hacer cosas
como: seleccionar un directorio, minimizar todas las ventanas, mostrar el

panel de control o cualquiera de sus componentes, reposicionar las


ventanas abiertas, mostrar el dilogo de buscar, explorar, ejecutar, etc.,
etc., etc.
Para poder usar dichos objetos en Visual Basic, hay que crear una
referencia a la librera Shell32.dll, para ello, en el men
Proyecto/Referencias, selecciona Microsoft Shell Controls And
Automation, si dicha referencia no se muestra, tendrs que usar el botn
de examinar para buscar la librera, que suele estar en el directorio System
o System32.
Algunos de los mtodos usan como parmetro una cadena con un
directorio, aunque tambin se le puede indicar un valor incluido en la
enumeracin ShellSpecialFoldersConstants, para indicarle que a lo que
queremos acceder es a un directorio especial, tal como el Escritorio,
Impresoras, etc.
En el cdigo de ejemplo te muestro dicha enumeracin para que te sea fcil
usarla.
Adems de ver los mtodos del objeto Shell, vamos a ver tambin el
mtodo GetSystemInformation accesible mediante la propiedad
Application del susodicho objeto Shell.
Precisamente la nica funcin que no funciona del mtodo
GetSystemInformation es precisamente lo que me llev a buscar la
informacin: saber la velocidad del procesador... en fin...
Aqu tienes el cdigo de ejemplo, en los cuales he dejado "la definicin" de
cada uno de los mtodos en el idioma original de la MSDN Library... confo
que no tendrs problemas para "entenderlos". A ver cuando se dignan la
gente de Microsoft a darnos la informacin en nuestro propio idioma... que
somos muchos los que nos comunicamos en la lengua de Cervantes...

Lista de los objetos del Shell explicados en esta


pgina:
Objeto

Explicacin

Application
Varias funciones...
GetSystemInformation Ver el cdigo de ejemplo
BrowseForFolder

Seleccionar directorios (carpetas)

CascadeWindows

Muestra las ventanas en cascada

TileHorzontally

Anida las ventanas horizontalmente

TileVertically

Idem, pero verticalmente

MinimizeAll

Minimiza todas las ventanas

UndoMinimizeAll

Deshace minimizar todas las ventanas

ControlPanelItem

Ejecuta un elemento del panel de control

EjectPC

Desacopla un ordenador que est acoplado

Explore

Explora una carpeta

FileRun

Muestra el dilogo de ejecutar

FindComputer

Muestra el dilogo de buscar un equipo

FindFiles

Muestra el dilogo de buscar ficheros


(archivos)

Help

Muestra la ayuda de Windows

Namespace

Devuelve una carpeta, (la creada con esta


funcin)

Open

Abre una carpeta

RefreshMenu

Actualiza el men de Inicio

SetTime

Muestra el dilogo de cambiar la


fecha/hora

ShutDownWindows

Muestra el dilogo de apagar el equipo

Suspend

"Suspende" el equipo

TryProperties

Muestra el dilogo de configurar la barra de


tareas

Windows

Crea y devuelve un objeto ShellWindows

'----------------------------------------------------------------------------' Ejemplo de cmo usar Shell Objects


(07/Sep/01)
'
' Hay que aadir una referencia a: Microsoft Shell
Controls And Automation
' Si dicha referencia no se muestra,
' tendrs que usar el botn de examinar para buscar la
librera,
' que estar en el directorio System o System32.
'
' Guillermo 'guille' Som, 2001
'----------------------------------------------------------------------------Option Explicit
'
' Objeto para manejar los Shell Objects
Private oShell As Shell32.Shell
Private Enum ShellSpecialFolderConstants
ssfDESKTOP = &H0
ssfPROGRAMS = &H2
ssfCONTROLS = &H3
ssfPRINTERS = &H4
ssfPERSONAL = &H5
ssfFAVORITES = &H6
ssfSTARTUP = &H7

ssfRECENT = &H8
ssfSENDTO = &H9
ssfBITBUCKET = &HA
ssfSTARTMENU = &HB
ssfDESKTOPDIRECTORY = &H10
ssfDRIVES = &H11
ssfNETWORK = &H12
ssfNETHOOD = &H13
ssfFONTS = &H14
ssfTEMPLATES = &H15
ssfCOMMONSTARTMENU = &H16
ssfCOMMONPROGRAMS = &H17
ssfCOMMONSTARTUP = &H18
ssfCOMMONDESKTOPDIR = &H19
ssfAPPDATA = &H1A
ssfPRINTHOOD = &H1B
ssfLOCALAPPDATA = &H1C
ssfALTSTARTUP = &H1D
ssfCOMMONALTSTARTUP = &H1E
ssfCOMMONFAVORITES = &H1F
ssfINTERNETCACHE = &H20
ssfCOOKIES = &H21
ssfHISTORY = &H22
ssfCOMMONAPPDATA = &H23
ssfWINDOWS = &H24
ssfSYSTEM = &H25
ssfPROGRAMFILES = &H26
ssfMYPICTURES = &H27
ssfPROFILE = &H28
End Enum
Private Sub cmdSalir_Click()
Unload Me
End Sub
Private Sub Form_Load()
Dim s As String
'
' Crear el objeto Shell

Set oShell = New Shell32.Shell


'
' Mostrar el copyright y adecuar el ao a la fecha
actual
lblInfo.Caption = " Guillermo 'guille' Som, 2001" &
IIf(Year(Now) > 2001, "-" & CStr(Year(Now)), "")
' Guardamos el copyright, por si lo necesitamos
posteriormente
lblInfo.Tag = lblInfo.Caption
'
' Asignamos los mens
AsignarMenuShellObjects
'
s = s & "Valores de GetSystemInformation:"
s = s & vbCrLf & "--------------------------------"
s = s & vbCrLf & "DirectoryServiceAvailable: " &
oShell.Application.GetSystemInformation("DirectoryServic
eAvailable")
s = s & vbCrLf & "DoubleClickTime: " &
oShell.Application.GetSystemInformation("DoubleClickTime
")
s = s & vbCrLf & "ProcessorLevel: " &
oShell.Application.GetSystemInformation("ProcessorLevel"
)
' Esta falla...
's = s & vbCrLf & "Velocidad del procesador: " &
oShell.Application.GetSystemInformation("ProcessorSpeed"
)
s = s & vbCrLf & "ProcessorArchitecture: " &
oShell.Application.GetSystemInformation("ProcessorArchit
ecture")
s = s & vbCrLf & "PhysicalMemoryInstalled: " &
oShell.Application.GetSystemInformation("PhysicalMemoryI
nstalled")
'
Text1 = s
'
End Sub
Private Sub Form_Resize()
If WindowState <> vbMinimized Then
lblInfo.Left = 60
lblInfo.Width = ScaleWidth - 120
End If

End Sub
Private Sub mnuShell_Click(Index As Integer)
lblInfo.Caption = mnuShell(Index).Tag
lblInfo.Refresh
EjecutarShellObject Trim$(mnuShell(Index).Caption)
End Sub
Private Sub mnuShell2_Click(Index As Integer)
lblInfo.Caption = mnuShell2(Index).Tag
lblInfo.Refresh
EjecutarShellObject Trim$(mnuShell2(Index).Caption)
End Sub
Private Sub AsignarMenuShellObjects()
' Asignar (y crear) los submens de Shell Objects
Dim i As Long
'
' Asignar al Caption lo que muestra el men y
' al Tag el texto a mostrar en la etiqueta de
informacin
'
' El ndice 0 ya estar creado
i = 0
With mnuShell(0)
.Caption = "BrowseForFolder"
.Tag = "Creates a dialog box that allows the
user to select a folder and then returns the selected
folder's Folder object."
End With
'
' Ir creando el resto de los elementos
' usamos deteccin de errores por si ya estuviesen
creados en tiempo de diseo
On Error Resume Next
'
i = i + 1
Load mnuShell(i)
With mnuShell(i)
.Caption = "MinimizeAll"

.Tag = "Minimizes all of the windows on the


desktop."
End With
i = i + 1
Load mnuShell(i)
With mnuShell(i)
.Caption = "CascadeWindows"
.Tag = "Cascadess all of the windows on the
desktop."
End With
i = i + 1
Load mnuShell(i)
With mnuShell(i)
.Caption = "TileHorizontally"
.Tag = "Tiles all of the windows on the desktop
horizontally."
End With
i = i + 1
Load mnuShell(i)
With mnuShell(i)
.Caption = "TileVertically"
.Tag = "Tiles all of the windows on the desktop
vertically."
End With
i = i + 1
Load mnuShell(i)
With mnuShell(i)
.Caption = "UndoMinimizeALL"
.Tag = "Restores all of the windows on the
desktop to the same state they were in before the last
MinimizeAll command."
End With
'
i = 0
With mnuShell2(i)
.Caption = "ControlPanelItem"
.Tag = "Runs a Control Panel application."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)

.Caption = "EjectPC"
.Tag = "Ejects the computer from its docking
station."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "Explore"
.Tag = "Explores a folder."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "FileRun"
.Tag = "Displays the Run dialog to the user."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "FindComputer"
.Tag = "Displays the Find: Computer dialog box."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "FindFiles"
.Tag = "Displays the Find: All Files dialog
box."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "Help"
.Tag = "Displays Microsoft Windows Help."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "NameSpace"

.Tag = "Creates and returns a Folder object for


the specified folder."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "Open"
.Tag = "Open Opens a folder."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "RefreshMenu"
Menu."

.Tag = "Refreshes the contents of the Start

End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "SetTime"
.Tag = "Displays the Date/Time Properties dialog
box."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "ShutdownWindows"
box."

.Tag = "Displays the Shut Down Windows dialog

End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "Suspend"
.Tag = "Suspends the computer."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "TrayProperties"

.Tag = "Displays the Taskbar Properties dialog


box."
End With
i = i + 1
Load mnuShell2(i)
With mnuShell2(i)
.Caption = "Windows"
.Tag = "Creates and returns a ShellWindows

object."

End With
'
Err = 0
End Sub
Private Sub EjecutarShellObject(ByVal sMethod As String)
Dim tFolder As Shell32.Folder
Dim s As String
'
On Error Resume Next
'
s = Text1
'
Select Case sMethod
Case "BrowseForFolder" ' Examinar Archivos de
Programas...
Set tFolder = oShell.BrowseForFolder(0,
"Seleccionar carpeta", 0) ', "C:\Program Files"
If Not tFolder Is Nothing Then
s = s & vbCrLf & vbCrLf & "Ttulo: " &
tFolder.Title & " Ttulo del padre: " &
tFolder.ParentFolder.Title
End If
Case "CascadeWindows"
oShell.CascadeWindows
Case "ControlPanelItem"
oShell.ControlPanelItem "" '"INETCPL.cpl"
Case "EjectPC"
oShell.EjectPC
Case "Explore"
oShell.Explore ssfDRIVES
'oShell.Explore "C:\"

Case "FileRun"
oShell.FileRun
Case "FindComputer"
oShell.FindComputer
Case "FindFiles"
oShell.FindFiles
Case "Help"
oShell.Help
Case "MinimizeAll"
oShell.MinimizeAll
Case "NameSpace"
oShell.NameSpace ssfPROGRAMS
'oShell.NameSpace "C:\"
Case "Open"
oShell.Open ssfSTARTUP
Case "RefreshMenu"
oShell.RefreshMenu
Case "SetTime"
oShell.SetTime
Case "ShutdownWindows"
oShell.ShutdownWindows
Case "Suspend"
oShell.Suspend
Case "TileHorizontally"
oShell.TileHorizontally
Case "TileVertically"
oShell.TileVertically
Case "TrayProperties"
oShell.TrayProperties
Case "UndoMinimizeALL"
oShell.UndoMinimizeALL
Case "Windows"
oShell.Windows
End Select
'
Text1 = s
'
Err = 0
End Sub

Para bajarte el cdigo de ejemplo, pulsa en este link. (ShellObjects.zip 3.98


KB)
2. Cambiar el texto de los botones de un MsgBox (14/Sep/2001)
Con el ejemplo que te muestro a continuacin, podrs cambiar el texto que
se muestra en los botones de un cuadro de dilogo, en este caso de un
MsgBox.
En este ejemplo, se cambia el texto YES/SI por "Alta" y el NO por
"Modificacin", que era lo que quera hacer el que hizo la consulta: *****
(he omitido el nombre de quin tena la duda, por peticin, (aunque no
directa a mi persona), del mismo).
Este cdigo es una "adaptacin" de un ejemplo en C que envi ?????
(puedo decir el nombre?) al grupo de noticias de VB.
Este es el cdigo en C:

LRESULT CALLBACK CallWndRetProc(


int nCode, WPARAM wParam, LPARAM lParam)
{
LRESULT lr = CallNextHookEx(hook, nCode, wParam, lParam);
if (nCode < 0) return lr;
CWPRETSTRUCT s = *((CWPRETSTRUCT *)lParam);
if (s.message == WM_INITDIALOG)
{
SetDlgItemText(s.hwnd, IDYES, "Alta");
SetDlgItemText(s.hwnd, IDNO, "Modificacin");
}
return lr;
}
...
HHOOK hook = SetWindowsHookEx(
WH_CALLWNDPROCRET,
CallWndRetProc,
NULL,
GetCurrentThreadId());
if (hook)
{

MessageBox(hwnd, "Texto del mensaje", "Ttulo", MB_YESNO);


UnhookWindowsHookEx(hook);
}

Para crear el proyecto, aade un formulario, inserta un botn y una


etiqueta. Inserta este cdigo en el formulario:

'----------------------------------------------------------------------------' Subclasificar un MsgBox para cambiar el texto de los botones


(13/Sep/01)
'
' Versin en Visual Basic de un ejemplo en C enviado a las news
por:
' "Hernn" el 13/Sep/2001
'
' Guillermo 'guille' Som, 2001
'----------------------------------------------------------------------------Option Explicit
Private Sub Command1_Click()
Dim hInst As Long
Dim Thread As Long
Dim i As Long
' Crear el hook para subclasificar el MsgBox
hInst = GetWindowLong(Me.hWnd, GWL_HINSTANCE)
Thread = GetCurrentThreadId()
hHook = SetWindowsHookEx(WH_CALLWNDPROCRET,
AddressOf CallWndRetProc, hInst, Thread)
' Mostrar el MsgBox
i = MsgBox("Pulsa en Alta o Modificacin.", vbYesNo)
' Alta ser vbYes, Modificacin ser vbNo
If i = vbYes Then
Label1 = "Has pulsado en Alta"
ElseIf i = vbNo Then

Label1 = "Has pulsado en Modificacin"


End If
End Sub

Aade tambin un mdulo BAS y aade el siguiente cdigo:

'----------------------------------------------------------------------------' MSubclassMsgBox

(14/Sep/01)

' Mdulo para subclasificar un MsgBox y cambiar el texto de los


botones
'
' Versin en Visual Basic de un ejemplo en C enviado a las news
por:
' "Hernn" el 13/Sep/2001
'
' Guillermo 'guille' Som, 2001
'----------------------------------------------------------------------------Option Explicit
'
Public hHook As Long
Public Const WH_CALLWNDPROCRET = 12
Public Const GWL_HINSTANCE = (-6)
Private Type tagCWPRETSTRUCT
lResult As Long
lParam As Long
wParam As Long
message As Long
hWnd As Long
End Type
Private Const WM_INITDIALOG = &H110
Public Declare Function GetWindowLong Lib "user32" Alias
"GetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long) As Long

Public Declare Function GetCurrentThreadId Lib "kernel32" () As


Long
Public Declare Function SetWindowsHookEx Lib "user32" Alias
"SetWindowsHookExA" _
(ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long
Private Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Long) As Long
Private Declare Function CallNextHookEx Lib "user32" _
(ByVal hHook As Long, ByVal nCode As Long, _
ByVal wParam As Long, lParam As Any) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias
"RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)
' Dialog Box Command IDs
Private Const IDOK = 1
Private Const IDCANCEL = 2
Private Const IDABORT = 3
Private Const IDRETRY = 4
Private Const IDIGNORE = 5
Private Const IDYES = 6
Private Const IDNO = 7
Private Declare Function SetDlgItemText Lib "user32" Alias
"SetDlgItemTextA" _
(ByVal hDlg As Long, ByVal nIDDlgItem As Long, _
ByVal lpString As String) As Long
Public Function CallWndRetProc(ByVal nCode As Long, ByVal
wParam As Long, ByVal lParam As Long) As Long
Dim lr As Long
Dim s As tagCWPRETSTRUCT
'
lr = CallNextHookEx(hHook, nCode, wParam, lParam)
If (nCode < 0) Then
CallWndRetProc = lr

Exit Function
End If
'
' Convertir lParam en una estructura
Call CopyMemory(s, ByVal lParam, Len(s))
'
If (s.message = WM_INITDIALOG) Then
' Modificar el texto de los botones
Call SetDlgItemText(s.hWnd, IDYES, "Alta")
Call SetDlgItemText(s.hWnd, IDNO, "Modificacin")
' Release the CBT hook
UnhookWindowsHookEx hHook
lr = 0&
End If
CallWndRetProc = lr
End Function
Ejectalo con F5 y pulsa en el botn, te mostrar un cuadro de dilogo
como este que te muestro:

Y ya est.
Precauciones:
No interrumpas el programa mientras se muestra el cuadro de
dilogo, (aunque en teora no debera pasar nada si lo hicieras), pero
sobre todo "procura" que el Visual Basic no se pare dentro de la
funcin CallWndRetProc, ya que se colgara dando error de
proteccin o algo por el estilo.
Tampoco aadas ningn Debug.Print ni nada que pudiera hacer que
se detenga el VB.
Una vez compilado estas precauciones no son aplicables.
Gracias Hernn por ese cdigo en C.

3. Saber el directorio de Windows y el System (15/Oct/2001)


Esto ya lo sabrs o lo has podido ver en otras pginas, incluso lo de
averiguar el directorio Windows ya tiene un link en la pgina de API, pero
no para el System, as que, como los dos estn bastante relacionados, los
pongo juntos, ejemplo incluido, por supuesto.
Es que me hizo falta y tuve que rebuscar un poco para encontrarlo... 8-)
Para saber cual es el directorio de Windows, usaremos esta funcin del API:
GetWindowsDirectory y para el directorio System, esta otra:
GetSystemDirectory.
La forma de usarlas es prcticamente igual, pero an as temuestro un
ejemplo para cada una de ellas.
Primero las declaraciones de estas dos funciones del API y despus el
cdigo de ejemplo.

' Funciones del API:


Private Declare Function GetWindowsDirectory Lib "kernel32" Alias
"GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
Private Declare Function GetSystemDirectory Lib "kernel32" Alias
"GetSystemDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
Dim buf As String
Dim ret As Long
'
' Obtener el directorio de windows
buf = String$(260, Chr$(0))
ret = GetWindowsDirectory(buf, Len(buf))
Text1.Text = Left$(buf, ret)
'
' Obtener el directorio de System
buf = String$(260, Chr$(0))
ret = GetSystemDirectory(buf, Len(buf))
Text2.Text = Left$(buf, ret)

4. Especial Resolucin de Pantalla: (04/Nov/01)

Aqu tienes tres cosillas relacionadas con la resolucin de la pantalla usando


el API de Windows.

Averiguar la resolucin actual y nmero de colores, usando


API.

Cambiar/restaurar la resolucin de forma rpida.

Enumerar las resoluciones disponibles y cambiar/restaurar la


resolucin de la pantalla.

Si quieres bajarte el cdigo de ejemplo, usa este link. cambiarres2.zip (6.72


KB)

Nota:
He probado el cdigo en Windows XP y no funciona, al
menos no cambia la resolucin.
Aunque si que muestra la resolucin actual, as como el
nmero de colores.
Tambin muestra todos los valores disponibles, antes te
coment que no lo haca y era porque hice la prueba desde
un "terminal" y no desde el equipo en el que est instalado
el XP, y al estar el equipo conectado al Windows XP slo
trabaja con una resolucin!
Al final de la pgina te muestro la imagen del formulario
funcionando en el Windows XP.

He estado mirando el Platform SDK a ver si dice algo al respecto, pero no


he encontrado nada especial.
Lo nico que he encontrado al revisar la documentacin de estas funciones
es que hay que asignar a DevM.dmSize con el tamao de la variable:
DevM.dmSize = Len(DevM)
En fin, si me entero de algo nuevo...
He cambiado el contenido del fichero ZIP, pero puede que el mostrado en
esta pgina no tenga esa asignacin que acabo de comentarte:
DevM.dmSize = Len(DevM)
Nota: Si funciona en Windows XP

Averiguar la resolucin actual y nmero de


colores, usando API:
Seguramente sabrs cmo averiguar la resolucin de la pantalla usando el
objeto Screen.
De esa forma podemos averiguar el alto y ancho de la misma, pero no el
nmero de colores.
Se usara de esta forma:
With Screen
mResAlto = (.Height \ .TwipsPerPixelY)
mResAncho = (.Width \ .TwipsPerPixelX)
End With
Para poder saber el nmero de colores, usa este cdigo, (tendrs que usar
las declaraciones del API de Windows que te indico un poco ms abajo)
' Esta llamada a EnumDisplay es para obtener la
resolucin actual
' mediante una llamada al API.
Call EnumDisplaySettings(0&, ENUM_CURRENT_SETTINGS,
DevM)
'
mResAncho = DevM.dmPelsWidth
mResAlto = DevM.dmPelsHeight
mResBits = DevM.dmBitsPerPel
' Cdigo a poner en las declaraciones del formulario
Private mResAlto As Long
Private mResAncho As Long
Private mResBits As Long
Private DevM As DevMode
' API para cambiar la resolucin de la pantalla
Private Declare Function ChangeDisplaySettings Lib
"user32" Alias "ChangeDisplaySettingsA" _
(lpDevMode As Any, ByVal dwFlags As Long) As Long
' API para saber los formatos de resoluciones posibles
Private Declare Function EnumDisplaySettings Lib
"user32" Alias "EnumDisplaySettingsA" _

(ByVal lpszDeviceName As Long, ByVal iModeNum As


Long, _
lpDevMode As DevMode) As Boolean
Const ENUM_CURRENT_SETTINGS As Long = -1&
Const CCHDEVICENAME = 32
Const CCHFORMNAME = 32
' Las declaraciones de estas constantes estn en:
Wingdi.h
Const DM_BITSPERPEL = &H40000
Const DM_PELSWIDTH = &H80000
Const DM_PELSHEIGHT = &H100000
Private Type DevMode
dmDeviceName As String * CCHDEVICENAME
dmSpecVersion As Integer
dmDriverVersion As Integer
dmSize As Integer
dmDriverExtra As Integer
'
dmFields As Long
dmOrientation As Integer
dmPaperSize As Integer
dmPaperLength As Integer
dmPaperWidth As Integer
dmScale As Integer
dmCopies As Integer
dmDefaultSource As Integer
dmPrintQuality As Integer
dmColor As Integer
dmDuplex As Integer
dmYResolution As Integer
dmTTOption As Integer
dmCollate As Integer
'
dmFormName As String * CCHFORMNAME
dmUnusedPadding As Integer
dmBitsPerPel As Integer
dmPelsWidth As Long
dmPelsHeight As Long
dmDisplayFlags As Long

dmDisplayFrequency As Long
End Type

Cambiar / restaurar la resolucin de forma rpida:


Rpida, lo que se dice rpida es tambin la siguiente que an te tengo que
mostrar, sta lo que hace es cambiar la resolucin a los valores que tu le
indiques, ya que en ocasiones lo que realmente te puede interesar es poner
la resolucin que tu programa necesita.
Es decir, hacer algo tan sencillo como esto:
CambiarRes 800, 600, 32
En el cdigo de ejemplo, te muestro todos los pasos, para saber cual es la
resolucin actual para despus volver a ponerla.
El formulario de prueba tiene este aspecto:

Y este es el cdigo completo del formulario de prueba:

'----------------------------------------------------------------------------' Cambiar y restaurar la resolucin de la pantalla,


(mtodo rpido) (04/Nov/01)
'
' Guillermo 'guille' Som, 2001

'----------------------------------------------------------------------------Option Explicit
Private mResolucionCambiada As Boolean
Private mResAlto As Long
Private mResAncho As Long
Private mResBits As Long
Private DevM As DevMode
' API para cambiar la resolucin de la pantalla
Private Declare Function ChangeDisplaySettings Lib
"user32" Alias "ChangeDisplaySettingsA" _
(lpDevMode As Any, ByVal dwFlags As Long) As Long
' API para saber los formatos de resoluciones posibles
Private Declare Function EnumDisplaySettings Lib
"user32" Alias "EnumDisplaySettingsA" _
(ByVal lpszDeviceName As Long, ByVal iModeNum As
Long, _
lpDevMode As DevMode) As Boolean
Const ENUM_CURRENT_SETTINGS As Long = -1&
Const ENUM_REGISTRY_SETTINGS As Long = -2&
'
Const CCHDEVICENAME = 32
Const CCHFORMNAME = 32
' Las declaraciones de estas constantes estn en:
Wingdi.h
Const DM_BITSPERPEL = &H40000
Const DM_PELSWIDTH = &H80000
Const DM_PELSHEIGHT = &H100000
Private Type DevMode
dmDeviceName As String * CCHDEVICENAME
dmSpecVersion As Integer
dmDriverVersion As Integer
dmSize As Integer
dmDriverExtra As Integer
'
dmFields As Long

dmOrientation As Integer
dmPaperSize As Integer
dmPaperLength As Integer
dmPaperWidth As Integer
dmScale As Integer
dmCopies As Integer
dmDefaultSource As Integer
dmPrintQuality As Integer
dmColor As Integer
dmDuplex As Integer
dmYResolution As Integer
dmTTOption As Integer
dmCollate As Integer
'
dmFormName As String * CCHFORMNAME
dmUnusedPadding As Integer
dmBitsPerPel As Integer
dmPelsWidth As Long
dmPelsHeight As Long
dmDisplayFlags As Long
dmDisplayFrequency As Long
End Type
Private Sub cmdCambiarRes_Click()
' Cambiar la resolucin a 800x600 colores de 32bits
' o a la indicada en los textboxes
CambiarRes txtResNueva(0), txtResNueva(1),
txtResNueva(2)
End Sub
Private Sub cmdRestaurarRes_Click()
' Poner la resolucin que haba antes,
' si se ha cambiado...
If mResolucionCambiada Then
CambiarRes mResAncho, mResAlto, mResBits
End If
End Sub
Private Sub cmdSalir_Click()
Unload Me

End Sub
Private Sub Form_Load()
mResolucionCambiada = False
'
' Esta llamada a EnumDisplay es para obtener la
resolucin actual
' mediante una llamada al API.
DevM.dmSize = Len(DevM)
Call EnumDisplaySettings(0&, ENUM_CURRENT_SETTINGS,
DevM)
'
mResAncho = DevM.dmPelsWidth
mResAlto = DevM.dmPelsHeight
mResBits = DevM.dmBitsPerPel
'
' La he probado en Windows 2000,
' si no funciona en Windows 9x usa esta otra forma:
'

With Screen

'

mResAlto = (.Height \ .TwipsPerPixelY)

'

mResAncho = (.Width \ .TwipsPerPixelX)

'

End With

'

mResBits = 32&
'
'
' Mostrar la resolucin actual

txtResActual.Text = CStr(mResAncho) & " x " &


CStr(mResAlto) & " x " & CStr(mResBits)
'
' Asignar la resolucin a la que se cambiar:
txtResNueva(0).Text = "800"
txtResNueva(1).Text = "600"
txtResNueva(2).Text = "16"
'
' Si queremos cambiar en el form_load
'CambiarRes 800, 600, 16
End Sub
Private Sub CambiarRes(ByVal Ancho As Long, ByVal Alto
As Long, ByVal Colores As Long)

' Cambiar la resolucin de la pantalla


(04/Nov/01)
'
' Lo que se va a cambiar
DevM.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT Or
DM_BITSPERPEL
'
DevM.dmSize = Len(DevM)
DevM.dmPelsWidth = Ancho
DevM.dmPelsHeight = Alto
DevM.dmBitsPerPel = Colores
'
Call ChangeDisplaySettings(DevM, 0)
'
End Sub
Private Sub Form_QueryUnload(Cancel As Integer,
UnloadMode As Integer)
' Poner la resolucin que haba antes,
' si se ha cambiado...
If mResolucionCambiada Then
CambiarRes mResAncho, mResAlto, mResBits
End If
End Sub
Private Sub txtResActual_GotFocus()
' Seleccionar el texto al entrar en el textbox
With txtResActual
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub
Private Sub txtResNueva_GotFocus(Index As Integer)
' Seleccionar el texto al entrar en el textbox
With txtResNueva(Index)
.SelStart = 0
.SelLength = Len(.Text)
End With
End Sub

Enumerar las resoluciones disponibles y


cambiar/restaurar la resolucin de la pantalla:
En esta ocasin, el ejemplo que te voy a mostrar adems de permitir
averiguar la resolucin actual y cambiar a una nueva, te muestra todas las
resoluciones posibles, incluido el nmero de colores.
Te comento que he probado este cdigo en Windows XP y no funciona lo de
cambiar la resolucin.
Aunque si lista todos los valores posibles y por tanto muestra los valores
actuales, tanto en el tamao como en el nmero de colores.
Al final de la pgina tienes una captura del formulario corriendo en el
Windows XP.
Este es el aspecto del formulario de pruebas (en Windows 2000):

Y este es el cdigo completo:

'----------------------------------------------------------------------------' Prueba para cambiar y restaurar la resolucin de la


pantalla
(04/Nov/01)
'
' Guillermo 'guille' Som, 2001
'
' Parte del cdigo es del ejemplo de cambiar la
resolucin
' publicado en mis pginas:
http://www.elguille.info/vb/utilidades/cambiar_res.htm

' Tambin tengo que reconocer que dicha informacin la


bas en un artculo
' de la Knowledge Base de Microsoft:
' Changing the Screen Resolution at Run Time in Visual
Basic 4.0
'----------------------------------------------------------------------------Option Explicit
Private mResolucionCambiada As Boolean
Private mResAlto As Long
Private mResAncho As Long
Private mResBits As Long
Private DevM As DevMode
' Tipo y array para guardar las resoluciones disponibles
Private Type tResol
Width As Long
Height As Long
Bits As Integer
End Type
Private Disponibles() As tResol
Private mNuevaRes As Long
' API para cambiar la resolucin de la pantalla
Private Declare Function ChangeDisplaySettings Lib
"user32" Alias "ChangeDisplaySettingsA" _
(lpDevMode As Any, ByVal dwFlags As Long) As Long
' API para saber los formatos de resoluciones posibles
Private Declare Function EnumDisplaySettings Lib
"user32" Alias "EnumDisplaySettingsA" _
(ByVal lpszDeviceName As Long, ByVal iModeNum As
Long, _
lpDevMode As DevMode) As Boolean
Const ENUM_CURRENT_SETTINGS As Long = -1&
Const ENUM_REGISTRY_SETTINGS As Long = -2&
'
Const CCHDEVICENAME = 32
Const CCHFORMNAME = 32

' Las declaraciones de estas constantes estn en:


Wingdi.h
Const DM_BITSPERPEL = &H40000
Const DM_PELSWIDTH = &H80000
Const DM_PELSHEIGHT = &H100000
Private Type DevMode
dmDeviceName As String * CCHDEVICENAME
dmSpecVersion As Integer
dmDriverVersion As Integer
dmSize As Integer
dmDriverExtra As Integer
'
dmFields As Long
dmOrientation As Integer
dmPaperSize As Integer
dmPaperLength As Integer
dmPaperWidth As Integer
dmScale As Integer
dmCopies As Integer
dmDefaultSource As Integer
dmPrintQuality As Integer
dmColor As Integer
dmDuplex As Integer
dmYResolution As Integer
dmTTOption As Integer
dmCollate As Integer
'
dmFormName As String * CCHFORMNAME
dmUnusedPadding As Integer
dmBitsPerPel As Integer
dmPelsWidth As Long
dmPelsHeight As Long
dmDisplayFlags As Long
dmDisplayFrequency As Long
End Type
Private Sub CambiarRes(ByVal Ancho As Long, ByVal Alto
As Long, ByVal Colores As Long)

' Cambiar la resolucin de la pantalla


(04/Nov/01)
'
' Lo que se va a cambiar
DevM.dmFields = DM_PELSWIDTH Or DM_PELSHEIGHT Or
DM_BITSPERPEL
'
DevM.dmSize = Len(DevM)
DevM.dmPelsWidth = Ancho
DevM.dmPelsHeight = Alto
DevM.dmBitsPerPel = Colores
'
Call ChangeDisplaySettings(DevM, 0)
'
End Sub
Private Sub cmdCambiarRes_Click()
CambiarRes Disponibles(mNuevaRes).Width,
Disponibles(mNuevaRes).Height,
Disponibles(mNuevaRes).Bits
mResolucionCambiada = True
End Sub
Private Sub cmdRestaurarRes_Click()
' Poner la resolucin que haba antes
'
' Quita los comentarios para slo hacerlo si se ha
cambiado antes
'If mResolucionCambiada Then
CambiarRes mResAncho, mResAlto, mResBits
'End If
End Sub
Private Sub cmdSalir_Click()
Unload Me
End Sub
Private Sub Form_Load()
mResolucionCambiada = False
'

' Esta llamada a EnumDisplay es para obtener la


resolucin actual
' mediante una llamada al API.
DevM.dmSize = Len(DevM)
Call EnumDisplaySettings(0&, ENUM_CURRENT_SETTINGS,
DevM)
'
mResAncho = DevM.dmPelsWidth
mResAlto = DevM.dmPelsHeight
mResBits = DevM.dmBitsPerPel
'
' La he probado en Windows 2000,
' si no funciona en Windows 9x usa esta otra forma:
'

With Screen

'

mResAlto = (.Height \ .TwipsPerPixelY)

'

mResAncho = (.Width \ .TwipsPerPixelX)

'

End With

'

mResBits = 32&
'
' La posicin cero es para la resolucin actual
ReDim Disponibles(0)
With Disponibles(0)
.Width = mResAncho
.Height = mResAlto
.Bits = mResBits
End With
'
' Mostrar la resolucin actual

txtResActual.Text = CStr(mResAncho) & " x " &


CStr(mResAlto) & " x " & CStr(mResBits)
'
txtResNueva.Text = ""
'
' Llenar el listbox con las resoluciones posibles
ResolucionesDisponibles
End Sub
Private Sub Form_Unload(Cancel As Integer)
If chkRestaurarAlCerrar.Value Then
CambiarRes mResAncho, mResAlto, mResBits

End If
End Sub
Private Sub ResolucionesDisponibles()
Dim i As Long
Dim a As Long
Dim s As String
'
' Vaciar el combo y el array
lstResoluciones.Clear
' El valor de la posicin CERO es la actual
ReDim Preserve Disponibles(0)
'
DevM.dmSize = Len(DevM)
i = 0
Do
a = EnumDisplaySettings(0&, i&, DevM)
i = i + 1
If a Then
disponibles
" & _

' Mostrar en el listbox las resoluciones


s = Format$(DevM.dmPelsWidth, " @@@@") & " x
Format$(DevM.dmPelsHeight, "@@@@") & " " &

Format$(DevM.dmBitsPerPel, "@@") & " bits"


'
lstResoluciones.AddItem s
' Guardar esos datos en nuestro array
' de las resoluciones disponibles
ReDim Preserve Disponibles(i)
With Disponibles(i)
.Width = DevM.dmPelsWidth
.Height = DevM.dmPelsHeight
.Bits = DevM.dmBitsPerPel
End With
End If
Loop While a
End Sub

Private Sub lstResoluciones_Click()


Dim i As Long
i = lstResoluciones.ListIndex + 1
mNuevaRes = i
' Mostrar en el label la resolucin seleccionada
txtResNueva.Text = Disponibles(i).Width & " x " & _
Disponibles(i).Height & "

" & _

Disponibles(i).Bits & " bits"


End Sub

El segundo ejemplo funcionando en el Windows XP

5. Convertir un Path de nombre largo a nombre corto


(06/Nov/2001)
Pues eso... hay veces que necesitamos esta conversin, aunque menos que
la contraria... de nombre corto a nombre largo, la cual te muestro en el
siguiente punto: usando API! ya que antes la usaba haciendo
malabarismos para poder sacar el nombre largo... pero eso te lo explico en
el mencionado siguiente punto.
Esta es la declaracin del API para 32 bits y un ejemplo de cmo usarla.

Private Declare Function GetShortPathName Lib "kernel32" Alias


"GetShortPathNameA" _
(ByVal lpszLongPath As String, ByVal lpszShortPath As String, _
ByVal cchBuffer As Long) As Long
Dim sBuf As String * 260
Dim i As Long
i = GetShortPathName(Text1, sBuf, Len(sBuf))
Text2 = Left$(sBuf, i)
Esto ya lo sabrs o lo has podido ver en otras pginas, incluso lo de
averiguar el directorio Windows ya tiene un link en la pgina de API,

6. Convertir un Path de nombre corto a nombre largo


(06/Nov/2001)
Esta conversin es ms utilizada, sobre todo si nuestra aplicacin recibe
parmetros de ficheros, tal es el caso en el que se suelte un fichero sobre el
icono de la aplicacin, en esos casos, el Windows suele "soltar" el nombre
corto del fichero y la verdad es que lo justo sera saber el nombre largo.
Esta conversin antes la haca mediante cdigo, cdigo que saqu de algn
artculo de la Knowledge Base, pero el otro da, mirando en los ficheros de
cabecera de Windows, me top con la declaracin en C de dicha funcin, as
que la convert para usarla en Visual Basic.
Y esta es la forma de declarar dicha funcin, (tambin acompao un
ejemplo de cmo usarla).

Private Declare Function GetLongPathName Lib "kernel32" Alias


"GetLongPathNameA" _
(ByVal lpszShortPath As String, ByVal lpszLongPath As String, _
ByVal cchBuffer As Long) As Long
Dim sBuf As String * 260
Dim i As Long
i = GetLongPathName(Text2, sBuf, Len(sBuf))
Text1 = Left$(sBuf, i)

7. Recorrer un TextBox multiline lnea a lnea (09/Nov/2001)


Esto ya estaba puesto en la entrega 32 del Curso Bsico, pero lo pongo por
separado para que lo puedas encontrar ms rpido.

La declaracin tendrs que ponerla en la parte general del formulario y para


usar el procedimiento, tendrs que pasar en el parmetro el textbox
multiline que quieras imprimir.
Por supuesto, si no lo quieres imprimir, puedes usarlo para leer lnea a
lnea.

' Funcin del API de Windows de 32 bits de mltiple uso


Private Declare Function SendMessage Lib "user32" Alias
"SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Private Sub ImprimirPorLinea(qControl As TextBox)
' Este procedimiento tomar cada lnea de un textbox multiline
(23/Ene/00)
' y lo imprimir en la impresora predeterminada
'
' El parmetro qControl, ser el TextBox a usar, en este caso no
es necesario
' ya que slo tenemos un TextBox, pero si se usaran varios...
' sera un procedimiento de uso genrico...
'
Dim i As Long, k As Long
Dim L1 As Long, L2 As Long
' Constantes para usar con SendMessage
Const EM_GETLINECOUNT = &HBA
Const EM_LINEFROMCHAR = &HC9
Const EM_LINELENGTH = &HC1
Const EM_LINEINDEX = &HBB
(21/Abr/00)

'--- Faltaba esta declaracin

' Nmero de lneas del TextBox


k = SendMessage(qControl.hWnd, EM_GETLINECOUNT, 0, 0&)
Printer.Print ""
For i = 0 To k - 1
' Primer carcter de la lnea actual
1

L1 = SendMessage(qControl.hWnd, EM_LINEINDEX, i, 0&) +


' Longitud de la lnea actual

L2 = SendMessage(qControl.hWnd, EM_LINELENGTH, L1, 0&)


' Imprimimos el trozo de texto que representa a una lnea
Printer.Print Mid$(qControl.Text, L1, L2)
Next
' Le indicamos que ya no hay ms que imprimir
Printer.EndDoc
End Sub

8. Temas de Windows XP: consejos para usarlos desde Visual Basic


clsico (30/Oct/02)
He creado esta nueva pgina sobre cmo usar los temas de Windows XP
desde Visual Basic clsico, porque creo que vale la pena que conozcas los
"problemillas" con los que te puedes encontrar y, lo mejor, cmo
solventarlos.
No quiero con esto quitar mritos a la colaboracin de Vctor Snchez, pero
creo que es conveniente "ahondar" un poco ms.

Sigue este link si quieres saber cmo usar los temas de Windows XP
desde los lenguajes de .NET Framework (Visual Basic .NET, C#,
etc.).
21/Ago/2003:
Si quieres "automatizar" tus formularios de VB para usar los temas
de Windows XP, sigue este link.

A continuacin te relato algunos puntos que debes tener en cuenta, para


evitar problemas:
1. Como sabrs, (si has ledo la colaboracin de Vctor Snchez o lo que
coment sobre este tema en la pgina de .NET Framework), para
poder usar desde Visual Basic los temas de Windows XP, hay que
hacer una llamada a InitCommonControls.
2. Esa llamada al API de Windows hay que hacerla antes de que se haya
mostrado ningn formulario de la aplicacin, por tanto, si utilizas

como punto de inicio de la aplicacin el procedimiento Main, (el cual


debe estar declarado en un mdulo de tipo BAS), puedes hacerla
dentro de ese procedimiento, pero antes de mostrar ningn
formulario.
3. Si no tienes un procedimiento Main y quieres usarla desde un
formulario, slo es necesario hacer esa llamada desde el formulario
principal (el que se inicia con la aplicacin), pero, (esto es muy
importante), se debe hacer desde el evento Form_Initialize, es decir
antes de que el formulario se muestre, si no lo hicieras as, la
aplicacin no se cargara y con toda seguridad te dara un error.
4. Slo es necesario hacer la llamada a la funcin del API en un
formulario, el resto de formularios no deberan llamar a esa funcin,
adems de que tampoco es necesario, por la sencilla razn de que los
temas se aplicarn a todos los formularios de la aplicacin.
5. Los OptionButtons (Options o RadioButtons) que se incluyan dentro
de un Frame, se vern negros... por lo tanto no podr leerse el texto,
sin embargo si esos Options estn fuera del Frame se vern bien.
Para solventar este inconveniente, los Options deberan estar
contenidos en un PictureBox.
6. Los botones (CommandButton) contenidos en un Frame se vern con
un borde negro... por tanto, al igual que con los OptionButtons, lo
recomendable es que estn contenidos dentro de un PictureBox, el
cual a su vez estar contenido en el Frame.
7. Los Frames que estn dentro de otro Frame, tendrn un problema si
muestran un texto en el caption: ese texto se ver en un tamao
ms grande del que hayamos usado en el diseo, adems de que no
ocupar todo el frame, sino que seguir usando el mismo espacio,
con lo cual el texto se ver cortado.
Al igual que en los dos puntos anteriores, la forma de solucionarlo es
"metiendo" esos frames dentro de un PictureBox.
8. Los botones que contengan imgenes, no se mostrarn con el estilo
que est usando Windows XP.
Da igual que esas imgenes sean iconos o bitmaps.

9. El fichero .manifest que se use para que Windows XP sepa que tiene
que usar los temas en la aplicacin no debe contener caracteres
acentuados ni ees... si no tienes esto en cuenta, la aplicacin no se
cargar, adems de que dar error.
10. La declaracin de la funcin del API es:
Private Declare Sub InitCommonControls Lib "comctl32.dll" ()
11. En Visual Basic podemos usar dos libreras de controles comunes de
Windows: Windows Common Controls 6.0 basados en mscomctl.ocx y
Windows Common Controls 5.0 basados en Comctl32.ocx, pues bien,
los basados en mscomctl.ocx no "adoptarn" el look de los temas de
XP, para que as sea, habr que usar los basados en Comctl32.ocx.
Aqu tienes un par de capturas para que veas lo que ocurre al usar los
Options, Commands y Frames dentro de un Frame:
(y cmo quedara si esos controles estuviesen contenidos en un picture)

Usando los controles directamente Metiendo los controles en un picture


en el Frame

Si te fijas en la imagen de la izquierda, comprobars que los dos Options


estn negros y que el Command2 tiene un borde, adems de que el texto
del Frame2 tiene un tamao ms grande y no se muestra completo.
Por otro lado, en la imagen de la derecha, esos controles se ven bien,
gracias a que estn contenidos en un control picture, para que veas que no
es un "cuento", te muestro ese segundo formulario en tiempo de diseo.

El segundo formulario en tiempo de diseo

No sabes qu es eso del fichero .manifest?


En el mismo path que el ejecutable debe existir un fichero que tendr el
mismo nombre que el ejecutable, incluyendo la extensin .exe y que
acabar con la extensin .manifest.
Por ejemplo, si el ejecutable se llama PruebaXP.exe, el fichero .manifest
debe llamarse: PruebaXP.exe.manifest
El contenido de ese fichero .manifest ser el siguiente:
(en negrita te resalto los datos que puedes rellenar, pero recuerda, sin
usar acentos ni ees!)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity
name="TemasXP2.exe"
processorArchitecture="x86"
version="1.0.0.1000"
type="win32"
/>
<description>Descripcion del programa, sin usar acentos,
etc. Guillermo 'guille' Som, 2002</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"

version="6.0.0.0"
processorArchitecture="x86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

Como detalle, si creas un fichero .manifest para Visual Basic 6.0


(vb6.exe.manifest), y lo copias en el directorio del VB6, podrs ver cmo
quedaran los controles en tiempo de diseo, as te resultar ms fcil saber
cuando debes incluir esos controles problemticos dentro de un picture.
Las siguientes imgenes est capturadas en tiempo de diseo:

Usando los controles directamente en Metiendo los controles en un picture


el Frame
(en tiempo de diseo)
(en tiempo de diseo)

Recuerda que si esas aplicaciones se usan en otro sistema operativo distinto


de Windows XP, no les afectar en nada y se vern de la forma clsica, por
tanto no est de ms que dejes tus ejecutables "preparados" para quin
prefiera usar los temas, ya que slo tendrn que aadir el fichero .manifest
al path del ejecutable.
Espero que con todos estos consejillos, no tengas problemas para aadir los
temas de Windows XP a tus aplicaciones.

Aqu tienes el zip con el cdigo del ejecutable con los dos formularios de
ejemplo, as como el .manifest correspondiente, tanto al del ejecutable
como al del Visual Basic 6.0: temasXPvb.zip 12.3 KB

9. Copiar, cortar, pegar, deshacer... usando el API de Windows


(28/Dic/02)
En VB6:
La funcin del API usada para la operacin deshacer es la misma que la
mostrada en el punto anterior.
El problema con VB6 es que para poder deshacer, hay que utilizar tambin
el API de Windows para realizar las acciones de cortar y borrar, si no se
hace as, no podremos deshacer.
Aqu te muestro las operaciones de Copiar, Cortar, Pegar y Seleccionar todo
usando el API de Windows. Si usas el API no deberas usar el mtodo
normal, ya que no son compatibles.

Copiar usando API


' copiar lo seleccionado, usando el API
Const WM_COPY As Long = &H301
Const EM_SETSEL As Long = &HB1
'
' si no hay seleccin, seleccionarlo
todo
If Text1.SelLength = 0 Then
SendMessage Text1.hWnd, EM_SETSEL,
0&, -1&
End If
SendMessage Text1.hWnd, WM_COPY, 0&, 0&

Cortar usando API


' cortar el texto seleccionado, usando
API
' slo, si hay algo seleccionado...
Const WM_CUT As Long = &H300
SendMessage Text1.hWnd, WM_CUT, 0&, 0&

Pegar usando API


' pegar usando el API
Const WM_PASTE As Long = &H302
'
SendMessage Text1.hWnd, WM_PASTE, 0&,
0&

Seleccionar todo usando API


Const EM_SETSEL As Long = &HB1
'
' seleccionarlo todo
SendMessage Text1.hWnd, EM_SETSEL, 0&,
-1&

Deshacer
Private Sub deshacerCmd_Click()
Const EM_CANUNDO As Long = &HC6
Const EM_UNDO As Long = &HC7
'
' se comprueba si se puede deshacer
If SendMessage(Text1.hWnd, EM_CANUNDO, 0&, 0&) Then
' si es as, se deshace
SendMessage Text1.hWnd, EM_UNDO, 0&, 0&
End If
End Sub

10. Deshabilitar Alt+Tab y otras teclas en Windows NT/2000/XP


(09/Mar/03)
Como sabrs, sobre todo si lo has intentado, en Windows NT/2000/XP no se
puede deshabilitar la combinacin de teclas Ctrl+Alt+Supr, esto es por
razones de seguridad, ya que es el propio sistema operativo el que se encarga de
que esa combinacin de teclas no sea interceptable. La razn principal es que esa
es la combinacin de teclas que se utiliza antes de iniciar una sesin, al menos en

los servidores Windows siempre es as, ya que tanto en Windows XP como en


Windows 2000 Pro, podemos indicar que no nos pregunte por el usuario que inicia
el equipo y por tanto no se mostrara la pantalla que nos indica que pulsemos esa
combinacin antes de indicar nuestro nombre y contrasea.
Por eso, salvo que alguien encuentre un cdigo de cmo hacerlo, y si lo encuentra
es posible que no lo difunda... no es ese mi caso, pero... para que no salte el listillo
de siempre y diga: "yo se cmo evitar que se pulsen esas teclas".
Antes de que alguien lo diga, voy a decirlo... en Windows 9x s es posible
deshabilitar esa combinacin de teclas, la forma de hacerlo la tienes en este link:
Cmo evitar el uso de CTRL+ALT+SUPR y ALT+TAB?
Un poco de historia para alargar el artculo:
Ayer estaba revisando las consultas de los grupos de noticias, como suelo hacer
casi todos los das, y me encontr con un mensaje que preguntaba precisamente
cmo evitar esa triple combinacin de teclas en los Windows tipo NT, la respuesta
que le dieron fue de que buscara en Google (indicando el grupo de noticias
microsoft.public.vb.es) ya que en muchas ocasiones se haba dado respuesta a esa
pregunta. Como un servidor no se las sabe todas, pues segu el susodicho link y
busqu en Google y uno de los mensajes que encontr fue precisamente mo, con
fecha de Enero del 2000, (del que yo, ni me acordaba), el link a ese "hilo" es este:
Re- aclaracin sobre inhabilitar CTRL+ALT+SUPR y ALT+TAB. Aunque no daba la
solucin, pero si mostraba un cdigo creado en C++ que "casi" haca eso...
realmente slo evitaba las tres combinaciones que indico en el ttulo de este
artculo. As que, me puse a convertirlo a Visual Basic (clsico, para que el personal
no se me queje de que ahora slo publico cosas sobre punto NET) y el resultado es
el que te presento aqu.
Espero que te sea de utilidad, que de eso es de lo que al fin y al cabo se trata y
tambin te invito a que hagas pruebas con otras combinaciones de teclas, para que
practiques.
Una cosa muy importante es que en el cdigo que te muestro se hace un
"gancho" (hook) al teclado, por tanto no te recomiendo que hagas las pruebas en
el IDE de Visual Basic, ya que si por casualidad se produjera un error mientras el
gancho est activo, es posible que se te quede colgado el sistema... o al menos el
IDE y pierdas lo que no hayas guardado. Avisado ests.

Funciones del API usadas en este artculo:


Te resumo las funciones del API y tcnicas que se utilizan en este artculo para que
tengas un visin general de lo que el cdigo te va a mostrar.

Funcin

Comentario

SetWindowsHookEx

para crear un gancho

UnhookWindowsHookEx

para quitar el gancho creado con


SetWindowsHookEx

CallNextHookEx

para llamar al siguiente gancho

GetAsyncKeyState

para saber el estado de pulsacin de una

tecla
CopyMemory

para copiar una estructura a partir de un


puntero

Constante

Valor

WH_KEYBOARD_LL
VK_TAB

13&
9&

Comentario
para indicar a SetWindowsHookEx que el
gancho ser para el teclado
la tecla TAB

VK_CONTROL

&H11

la tecla Control (CTRL)

VK_ESCAPE

&H1B

la tecla Esc (Escape)

LLKHF_ALTDOWN

&H20

usada para indicar si tambin est pulsada la


tecla ALT

0&

si la funcin del gancho recibe este cdigo es


cuando hay que actuar...

HC_ACTION

Tambin se utiliza la siguiente estructura, que es la que recibe la informacin de la


tecla pulsada, te muestro la informacin indicada en la documentacin que
acompaa al Visual Basic 6.0:
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
// virtual key code
DWORD scanCode;
// scan code
DWORD flags;
// flags
DWORD time;
// time stamp for this message
DWORD dwExtraInfo; // extra info from the driver or keybd_event
}
En esta misma documentacin puedes encontrar informacin sobre el resto de las
funciones del API, as como de la forma en que habr que declarar la funcin que
ser llamada cada vez que se pulse una tecla, en esta ocasin, la funcin tiene este
formato:
LRESULT CALLBACK
int nCode,
WPARAM wParam,
LPARAM lParam
);

LowLevelKeyboardProc(
// hook code
// message identifier
// pointer to structure with message data

Ahora veremos cmo declararla y usarla desde una aplicacin de VB6 o VB5, pero
no anteriores, ya que se utiliza AddressOf y esa instruccin no existe en las
versiones anteriores a Visual Basic 5.0
Vamos a crear un nuevo proyecto, al cual aadiremos un mdulo BAS.
En el formulario que se incluye, aade dos botones que tendrn estos nombres:
cmdHook y cmdUnHook, desde estos botones se activar y desactivar la
intercepcin de las teclas.
En el mdulo BAS incluiremos las declaraciones del API y dems constantes y tipos,
as como la funcin que ser llamada por el sistema operativo cuando el gancho del
teclado est activo.

Segn la documentacin el cdigo incluido en esa funcin no debera alargarse ms


de lo debido, por tanto, en esa funcin haz slo lo que debas hacer y no lo alargues
innecesariamente.
Los pasos que daremos para activar/desactivar el gancho sern:
1- Iniciar el gancho del teclado (podemos hacerlo al iniciarse la aplicacin, pero en
este ejemplo se har cuando pulsemos en el botn cmdHook.
2- Quitar el gancho del teclado cuando la aplicacin se cierre. Aqu adems de eso,
vamos a quitar el mencionado gancho cuando pulsemos en el botn cmdUnHook.
Una vez activado el gancho, cada vez que se pulse una tecla, se filtrar por la
funcin gancho que tenemos en el mdulo BAS a la que he llamado
LLKeyBoardProc, en esa funcin se comprobarn las combinaciones de teclas y se
devolver un valor 1, si queremos que sean "obviadas" por el sistema, en caso de
que no sea una de las teclas que queremos interceptar, la funcin debe devolver el
valor indicado por CallNextHookEx, de forma que se pueda si hay otras
aplicaciones que tambin han puesto un gancho, sean llamadas.
Todo esto lo vers en el cdigo, as que, no me enrollo ms y veamos ese cdigo.
El cdigo
Empezaremos por el cdigo del formulario que es mucho ms simple.
'----------------------------------------------------------------------------' Formulario de prueba para desactivar algunas teclas especiales
(08/Mar/03)
' en Windows NT/2000/XP
'
' Guillermo 'guille' Som, 2003
'----------------------------------------------------------------------------Option Explicit
Private Sub cmdCerrar_Click()
Unload Me
End Sub
Private Sub cmdHook_Click()
' iniciar el gancho para el teclado
HookKeyB App.hInstance
End Sub
Private Sub cmdUnHook_Click()
' quitar el gancho del teclado
UnHookKeyB
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
' al cerrar el formulario, quitar el gancho del teclado
UnHookKeyB
End Sub
Como puedes comprobar, en la aplicacin tambin he aadido un botn para
cerrarla.
Ahora veamos el cdigo a incluir en el mdulo BAS:

'----------------------------------------------------------------------------' Para bloquear algunas teclas en Windows NT/2000/XP


(08/Mar/03)
' Para NT debe tener el SP3 como mnimo
'
' NO FUNCIONA para Ctrl+Alt+Supr !!!
'
' En este ejemplo se bloquean las siguientes teclas:
'
Ctrl+Esc, Alt+Tab y Alt+Esc
'
' Guillermo 'guille' Som, 2003
'----------------------------------------------------------------------------Option Explicit
' para guardar el gancho creado con SetWindowsHookEx
Private mHook As Long
'
' para indicar a SetWindowsHookEx que tipo de gancho queremos instalar
Private Const WH_KEYBOARD_LL As Long = 13&
' este es para el ratn
'Private Const WH_MOUSE_LL As Long = 14&
'
Private Type tagKBDLLHOOKSTRUCT
vkCode As Long
scanCode As Long
flags As Long
time As Long
dwExtraInfo As Long
End Type
'
Private Const VK_TAB As Long = &H9
Private Const VK_CONTROL As Long = &H11
' tecla Ctrl
'Private Const VK_MENU As Long = &H12
' tecla Alt
Private Const VK_ESCAPE As Long = &H1B
'Private Const VK_DELETE As Long = &H2E
' tecla Supr (Del)
'
Private Const LLKHF_ALTDOWN As Long = &H20&
'
' cdigos para los ganchos (la accin a tomar en el gancho del
teclado)
Private Const HC_ACTION As Long = 0&
'----------------------------' Funciones del API de Windows
'----------------------------' para asignar un gancho (hook)
Private Declare Function SetWindowsHookEx Lib "user32" Alias
"SetWindowsHookExA" _
(ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hMod As Long, ByVal dwThreadId As Long) As Long
' para quitar el gancho creado con SetWindowsHookEx
Private Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Long) As Long
' para llamar al siguiente gancho

Private Declare Function CallNextHookEx Lib "user32" _


(ByVal hHook As Long, ByVal nCode As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
' para saber si se ha pulsado en una tecla
Private Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Long) As Integer
' para copiar la estructura en un long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)

' La funcin a usar para el gancho del teclado


Public Function LLKeyBoardProc(ByVal nCode As Long, _
ByVal wParam As Long, _
ByVal lParam As Long _
) As Long
Dim pkbhs As tagKBDLLHOOKSTRUCT
Dim ret As Long
'
ret = 0
'
' copiar el parmetro en la estructura
CopyMemory pkbhs, ByVal lParam, Len(pkbhs)
'
If nCode = HC_ACTION Then
'
' si se pulsa Ctrl+Esc
If pkbhs.vkCode = VK_ESCAPE Then
If (GetAsyncKeyState(VK_CONTROL) And &H8000) Then
ret = 1
End If
End If
'
' si se pulsa Alt+Tab
If pkbhs.vkCode = VK_TAB Then
If (pkbhs.flags And LLKHF_ALTDOWN) <> 0 Then
ret = 1
End If
End If
'
' si se pulsa Alt+Esc
If pkbhs.vkCode = VK_ESCAPE Then
If (pkbhs.flags And LLKHF_ALTDOWN) <> 0 Then
ret = 1
End If
End If
'
End If
'
If ret = 0 Then
ret = CallNextHookEx(mHook, nCode, wParam, lParam)
End If
'
LLKeyBoardProc = ret
'
'
' El cdigo C++ en el que he basado (o casi) el de VB

'LRESULT CALLBACK LowLevelKeyboardProc (INT nCode, WPARAM wParam,


LPARAM lParam)
'{
'
// By returning a non-zero value from the hook procedure, the
'
// message does not get passed to the target window
'
KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT *) lParam;
'
BOOL bControlKeyDown = 0;
'
'
switch (nCode)
'
{
'
case HC_ACTION:
'
{
'
// Check to see if the CTRL key is pressed
'
bControlKeyDown = GetAsyncKeyState (VK_CONTROL) >>
((sizeof(SHORT) * 8) - 1);
'
'
// Disable CTRL+ESC
'
if (pkbhs->vkCode == VK_ESCAPE && bControlKeyDown)
'
return 1;
'
'
// Disable ALT+TAB
'
if (pkbhs->vkCode == VK_TAB && pkbhs->flags &
LLKHF_ALTDOWN)
'
return 1;
'
'
// Disable ALT+ESC
'
if (pkbhs->vkCode == VK_ESCAPE && pkbhs->flags &
LLKHF_ALTDOWN)
'
return 1;
'
'
break;
'
}
'
'default:
'
break;
'
} return CallNextHookEx (hHook, nCode, wParam, lParam);
'}
End Function
Public Sub HookKeyB(ByVal hMod As Long)
' instalar el gancho para el teclado
' hMod ser el valor de App.hInstance de la aplicacin
mHook = SetWindowsHookEx(WH_KEYBOARD_LL, AddressOf LLKeyBoardProc,
hMod, 0&)
End Sub
Public Sub UnHookKeyB()
' desinstalar el gancho para el teclado
' Es importante hacerlo antes de finalizar la aplicacin,
' normalmente en el evento Unload o QueryUnload
If mHook <> 0 Then
UnhookWindowsHookEx mHook
End If
End Sub
Te he dejado algunas declaraciones de constantes comentadas, para que sepas los
valores y puedas intentar otras cosas.
Tambin he dejado el cdigo C/C++ en el que est basado el cdigo de VB, el cual

era el que puse en el mensaje ese que te coment al principio, el cual a su vez
estaba tomado de un artculo de la Knowledge Base titulado: HOWTO: Disable Task
Switching on Win32 Platforms.
Para terminar, decirte que si este cdigo lo usas en Windows NT, debes tener como
mnimo el SP3.
11. Revisin del cdigo para leer ficheros INI desde Visual Basic
(14/Sep/03)
Introduccin:
Pues eso... que intentando mejorar el acceso a los ficheros INIs me "entretuve" en
leer la ayuda del API para 32 bits y vi un par de funciones que... al menos en
teora, slo servan para el Windows NT... y mira t por dnde... tambin sirven
para el Windows 98... no las he probado para el Windows 95, as que si lo haces y
funciona, me lo cuentas... vale? Gracias.
El cdigo:
El cdigo que te voy a mostrar ahora tiene dos funciones que ya habrs visto en
algn otro sitio de mis pginas, son las funciones para leer y escribir en ficheros
INIs; pero ahora te voy a dar otras tres que de seguro te interesarn:
Borrar claves o secciones de un fichero INI.
Leer todos las claves y valores de una seccin.
Leer todas las secciones.
Decirte que de esta ltima, no viene la declaracin del API en el fichero que se
incluye con el VB, as que toma nota, porque es interesante.
Las declaraciones de las funciones del API
'
'--- Declaraciones para leer ficheros INI --'
' Leer todas las secciones de un fichero INI, esto seguramente no
funciona en Win95
' *** Esta funcin no estaba en las declaraciones del API que se
incluye con el VB ***
Private Declare Function GetPrivateProfileSectionNames Lib
"kernel32" Alias "GetPrivateProfileSectionNamesA" _
(ByVal lpszReturnBuffer As String, ByVal nSize As Long, _
ByVal lpFileName As String) As Long
' Leer una seccin completa
Private Declare Function GetPrivateProfileSection Lib "kernel32"
Alias "GetPrivateProfileSectionA" _
(ByVal lpAppName As String, ByVal lpReturnedString As String, _

ByVal nSize As Long, ByVal lpFileName As String) As Long


' Leer una clave de un fichero INI
Private Declare Function GetPrivateProfileString Lib "kernel32"
Alias "GetPrivateProfileStringA" _
(ByVal lpApplicationName As String, ByVal lpKeyName As Any, _
ByVal lpDefault As String, ByVal lpReturnedString As String, _
ByVal nSize As Long, ByVal lpFileName As String) As Long
' Escribir una clave de un fichero INI (tambin para borrar claves y
secciones)
Private Declare Function WritePrivateProfileString Lib "kernel32"
Alias "WritePrivateProfileStringA" _
(ByVal lpApplicationName As String, ByVal lpKeyName As Any, _
ByVal lpString As Any, ByVal lpFileName As String) As Long

Ahora las funciones y despus vendr un ejemplo de cmo usarlas.


'
Private Function IniGet(ByVal lpFileName As String, ByVal lpAppName
As String, _
ByVal lpKeyName As String, _
Optional ByVal lpDefault As String = "") As

String
'

'Los parmetros son:


'lpFileName:

La Aplicacin (fichero INI)

'lpAppName:

La seccin que suele estar entrre corchetes

'lpKeyName:

Clave

'lpDefault:
la clave.

Valor opcional que devolver si no se encuentra

'
Dim LTmp As Long
Dim sRetVal As String
sRetVal = String$(255, 0)
LTmp = GetPrivateProfileString(lpAppName, lpKeyName, lpDefault,
sRetVal, Len(sRetVal), lpFileName)
If LTmp = 0 Then

IniGet = lpDefault
Else
IniGet = Left(sRetVal, LTmp)
End If
End Function

Private Sub IniWrite(ByVal lpFileName As String, ByVal lpAppName As


String, _
ByVal lpKeyName As String, ByVal lpString As

String)
'

'Guarda los datos de configuracin


'Los parmetros son los mismos que en IniGet
'Siendo lpString el valor a guardar
'
Call WritePrivateProfileString(lpAppName, lpKeyName, lpString,
lpFileName)
End Sub

Private Sub IniDelete(ByVal sIniFile As String, ByVal sSection As


String, _
Optional ByVal sKey As String = "")
'
' Borrar una clave o entrada de un fichero INI
(16/Feb/99)
' Si no se indica sKey, se borrar la seccin indicada en
sSection
' En otro caso, se supone que es la entrada (clave) lo que se
quiere borrar
'
If Len(sKey) = 0 Then
' Borrar una seccin
Call WritePrivateProfileString(sSection, 0&, 0&, sIniFile)
Else
' Borrar una entrada
Call WritePrivateProfileString(sSection, sKey, 0&, sIniFile)
End If
End Sub

Private Function IniGetSection(ByVal lpFileName As String, _


ByVal lpAppName As String) As Variant
'
' Lee una seccin entera de un fichero INI
(27/Feb/99)
'
' Usando Collection en lugar de cParrafos y cContenido
(06/Mar/99)
'
' Esta funcin devolver una coleccin con cada una de las
claves y valores
' que haya en esa seccin.
' Parmetros de entrada:
'

lpFileName

Nombre del fichero INI

'

lpAppName

Nombre de la seccin a leer

' Devuelve:
'

Una coleccin con el Valor y el contenido

'

Para leer los datos:

'

For i = 1 To tContenidos Step 2

'

sClave = tContenidos(i)

'

sValor = tContenidos(i+1)

'

Next

'
Dim tContenidos As Collection
Dim nSize As Long
Dim i As Long
Dim j As Long
Dim sTmp As String
Dim sClave As String
Dim sValor As String

' El tamao mximo para Windows 95


sBuffer = String$(32767, Chr$(0))
nSize = GetPrivateProfileSection(lpAppName, sBuffer,
Len(sBuffer), lpFileName)
If nSize Then
Set tContenidos = New Collection

' Cortar la cadena al nmero de caracteres devueltos


sBuffer = Left$(sBuffer, nSize)
' Quitar los vbNullChar extras del final
i = InStr(sBuffer, vbNullChar & vbNullChar)
If i Then
sBuffer = Left$(sBuffer, i - 1)
End If
' Cada una de las entradas estar separada por un Chr$(0)
Do
i = InStr(sBuffer, Chr$(0))
If i Then
sTmp = LTrim$(Left$(sBuffer, i - 1))
If Len(sTmp) Then
' Comprobar si tiene el signo igual
j = InStr(sTmp, "=")
If j Then
sClave = Left$(sTmp, j - 1)
sValor = LTrim$(Mid$(sTmp, j + 1))
' Asignar la clave y el valor
tContenidos.Add sClave
tContenidos.Add sValor
End If
End If
sBuffer = Mid$(sBuffer, i + 1)
End If
Loop While i
' Por si an queda algo...
If Len(sBuffer) Then
j = InStr(sBuffer, "=")
If j Then
sClave = Left$(sBuffer, j - 1)
sValor = LTrim$(Mid$(sBuffer, j + 1))
tContenidos.Add sClave
tContenidos.Add sValor
End If
End If
End If

Set IniGetSection = tContenidos


End Function

Private Function IniGetSections(ByVal lpFileName As String) As


Variant
'
' Devuelve todas las secciones de un fichero INI
(27/Feb/99)
'
' Usando Collection en lugar de cParrafos y cContenido
'
' Esta funcin devolver una coleccin con todas las secciones
del fichero
' Parmetros de entrada:
'

lpFileName

Nombre del fichero INI

' Devuelve:
'

Una coleccin con los nombres de las secciones

'
Dim tContenidos As Collection
Dim nSize As Long
Dim i As Long
Dim sTmp As String
' El tamao mximo para Windows 95
sBuffer = String$(32767, Chr$(0))
' Esta funcin del API no est definida en el fichero TXT
nSize = GetPrivateProfileSectionNames(sBuffer, Len(sBuffer),
lpFileName)
If nSize Then
' Crear una coleccin del tipo cParrafos que es una
coleccin
' con elementos del tipo cContenido
Set tContenidos = New Collection
' Cortar la cadena al nmero de caracteres devueltos
sBuffer = Left$(sBuffer, nSize)
' Quitar los vbNullChar extras del final
i = InStr(sBuffer, vbNullChar & vbNullChar)

If i Then
sBuffer = Left$(sBuffer, i - 1)
End If
' Cada una de las entradas estar separada por un Chr$(0)
Do
i = InStr(sBuffer, Chr$(0))
If i Then
sTmp = LTrim$(Left$(sBuffer, i - 1))
If Len(sTmp) Then
tContenidos.Add sTmp
End If
sBuffer = Mid$(sBuffer, i + 1)
End If
Loop While i
If Len(sBuffer) Then
tContenidos.Add sBuffer
End If
End If
Set IniGetSections = tContenidos
End Function
Bueno... eso es todo lo que se necesita para usar esas funciones, en este caso, si
te fijas, las funciones son Private, ya que las he "modificado" para usar en un Form
directamente, pero si las quieres "encapsular" en una clase o en un mdulo BAS,
deberas cambiar las declaraciones a Public, pero no las de las funciones del API...
esas pueden (y deberan) seguir siendo Privadas.
Del cdigo para usar estas funciones slo te voy a ensear parte... el resto te lo
imaginas... que con un poco de imaginacin seguro que eres capaz de crear el
ejemplo para que funcione... je, je... no es mala "leche", es que as te esfuerzas
un poco... que os estoy acostumbrando a darlo todo hecho y eso no ayuda
demasiado a aprender...

Aqu estn los trozos del Form y una imagen de cmo queda en modo de diseo.

El form de prueba

'
' Leer todas las secciones del fichero indicado y guardarlas en el
cboSecciones
Private Sub LeerSecciones()
Dim tContenidos As Collection
Dim i As Long
' Llenar las secciones de este fichero
Set tContenidos = IniGetSections(txtFicIni)
If Not tContenidos Is Nothing Then
cboSecciones.Clear
For i = 1 To tContenidos.Count
cboSecciones.AddItem tContenidos(i)
Next
cboSecciones.ListIndex = 0
txtValor = ""
End If
End Sub

' Leer las claves de la seccin seleccionada


Private Sub cboSecciones_Click()
' Mostrar las claves de esta seccin
Dim tContenidos As Collection
Dim i As Long

Set tContenidos = IniGetSection(txtFicIni, cboSecciones.Text)


If Not tContenidos Is Nothing Then
cboClaves.Clear
For i = 1 To tContenidos.Count Step 2
cboClaves.AddItem tContenidos(i)
Next
cboClaves.ListIndex = 0
txtValor = ""
End If
End Sub

Private Sub cmdBorrar_Click(Index As Integer)


' Borrar seccin o clave
Dim sFicINI As String
Dim sSeccion As String
Dim sClave As String
sFicINI = Trim$(txtFicIni)
sSeccion = Trim$(cboSecciones.Text)
sClave = Trim$(cboClaves.Text)
If Index = 0 Then
' Borrar seccin
IniDelete sFicINI, sSeccion
' Releer las secciones disponibles
LeerSecciones
Else
' Borrar clave
IniDelete sFicINI, sSeccion, sClave
' Leer las claves de esta seccin
cboSecciones_Click
End If
End Sub

Private Sub cmdLeer_Click()


' Leer del fichero INI
Dim sFicINI As String

Dim sSeccion As String


Dim sClave As String
Dim sValor As String
sFicINI = Trim$(txtFicIni)
sSeccion = Trim$(cboSecciones)
sClave = Trim$(cboClaves.Text)
sValor = Trim$(txtValor)
txtValor = IniGet(sFicINI, sSeccion, sClave, sValor)
End Sub
Private Sub cmdAdd_Click()
' Aadir la seccin, clave y/o valor
Dim sFicINI As String
Dim sSeccion As String
Dim sClave As String
Dim sValor As String
sFicINI = Trim$(txtFicIni)
sSeccion = Trim$(cboSecciones)
sClave = Trim$(cboClaves.Text)
sValor = Trim$(txtValor)
IniWrite sFicINI, sSeccion, sClave, sValor
End Sub
Pues esto es todo... que no es poco... a disfrutar y... a completar el programilla de
ejemplo.
Aunque todo sea dicho... te he dejado poco que hacer... pero...
Nos vemos.
Guillermo
Nota del 14/Sep/2003:
El cdigo aqu mostrado sirve igualmente para VB5 como para VB6, pero en VB6 se
podra cambiar el tipo de datos devuelto por las funciones IniGetSection e
IniGetSections, para que en lugar de devolver un valor de tipo Variant, devuelva
un array de tipo String. En el cdigo que usa el Array de tipo String no existe la
funcin IniDelete, sino que hay dos funciones, una para borrar una clave:
IniDeleteKey y otra para borrar una seccin: IniDeleteSection.

En estos dos ZIPs tienes los dos ejemplos, uno usando Variant y otro usando un
Array del tipo String:
Usando Variant: INIVariant.zip 9.62 KB
Usando Array de String: INIArray.zip 13.7 KB
12. Especial Docking:
Poner un formulario dentro de un control picture (25/Ene/04)
En este ejemplo te voy a mostrar cmo hacer que un formulario se pueda "meter"
en un control PictureBox, con idea de poder simular acoplamiento de formularios
con otros controles, es decir, tener un formulario con varios controles, por ejemplo
un ToolBar, StatusBar, un TreeView, etc. y en otro de los controles tener un
formulario.
De paso veremos cmo hacer que se pueda cambiar el tamao de dos de esos
controles (los que estn en el centro del formulario) mediante una barra de
divisin (Split). Aunque esto ltimo est en otra pgina/artculo, sigue este link
para verlo.
Normalmente tendremos un formulario principal, el cual puede tener un men
principal y tambin en la parte superior un ToolBar. Por otro lado, en la parte
inferior tambin tendremos un StatusBar. En el centro, (en la parte principal del
formulario), tendremos dos "paneles", uno a la izquierda y otro a la derecha. En el
panel izquierdo podramos insertar un control al estilo del Outlook con una serie de
botones de opciones, etc. y en el panel derecho podramos tener el contenido de
otro formulario de nuestra aplicacin.
Todo esto son "deseos", ya que en el ejemplo que te voy a mostrar, todos estos
"posibles" controles sern del tipo PictureBox, aunque realmente el nico que
"debera" ser un PictureBox es el panel de la derecha, el que contendr el "otro"
formulario. Pero, para no complicar demasiado el ejemplo, he preferido dejarlos
todos como controles Picture. Sigue las "recomendaciones" de los comentarios del
ejemplo y vers que te resultar fcil usar otros controles.

Como te deca, el ejemplo tiene dos "trucos".


El primero es: incrustar (o meter) un formulario dentro de un control de tipo
Picture.
Para lograr esto, usaremos una (realmente varias) funcin del API de Windows:
SetParent.
De forma simple te dir los pasos que habra que seguir:
Tendremos dos funciones (procedimientos) que se encargarn de meter el
formulario dentro del control y otro que ajustar el tamao del formulario al que
tenga dicho control.
Este es el cdigo de esos dos procedimientos, los cuales estarn declarados en el
mismo formulario principal, el que har de contenedor:
' Mostrar el formulario indicado, dentro de picDock
Private Sub dockForm(ByVal formhWnd As Long, _
ByVal picDock As PictureBox, _

Optional ByVal ajustar As Boolean = True)


' Hacer el formulario indicado, un hijo del picDock
' Si Ajustar es True, se ajustar al tamao del contenedor,
' si Ajustar es False, se quedar con el tamao actual.
Call SetParent(formhWnd, picDock.hWnd)
posDockForm formhWnd, picDock, ajustar
Call ShowWindow(formhWnd, NORMAL_eSW)
End Sub
' Posicionar el formulario indicado dentro de picDock
Private Sub posDockForm(ByVal formhWnd As Long, _
ByVal picDock As PictureBox, _
Optional ByVal ajustar As Boolean = True)
' Posicionar el formulario indicado en las coordenadas del
picDock
' Si Ajustar es True, se ajustar al tamao del contenedor,
' si Ajustar es False, se quedar con el tamao actual.
Dim nWidth As Long, nHeight As Long
Dim wndPl As WINDOWPLACEMENT
'
If ajustar Then
nWidth = picDock.ScaleWidth \ Screen.TwipsPerPixelX
nHeight = picDock.ScaleHeight \ Screen.TwipsPerPixelY
Else
' el tamao del formulario que se va a posicionar
Call GetWindowPlacement(formhWnd, wndPl)
With wndPl.rcNormalPosition
nWidth = .Right - .Left
nHeight = .Bottom - .Top
End With
End If
Call MoveWindow(formhWnd, 0, 0, nWidth, nHeight, True)
End Sub
Para usar estos procedimientos necesitaremos unas declaraciones de funciones del
API de Windows, as como unos tipos definidos:
'----------------------------------------------------------------------------' APIS para incluir las ventanas en un PictureBox

'----------------------------------------------------------------------------'
' Para hacer ventanas hijas
Private Declare Function SetParent Lib "user32" _
(ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
'
' Para mostrar una ventana segn el handle (hwnd)
' ShowWindow() Commands
Private Enum eShowWindow
HIDE_eSW = 0&
SHOWNORMAL_eSW = 1&
NORMAL_eSW = 1&
SHOWMINIMIZED_eSW = 2&
SHOWMAXIMIZED_eSW = 3&
MAXIMIZE_eSW = 3&
SHOWNOACTIVATE_eSW = 4&
SHOW_eSW = 5&
MINIMIZE_eSW = 6&
SHOWMINNOACTIVE_eSW = 7&
SHOWNA_eSW = 8&
RESTORE_eSW = 9&
SHOWDEFAULT_eSW = 10&
MAX_eSW = 10&
End Enum
Private Declare Function ShowWindow Lib "user32" _
(ByVal hWnd As Long, ByVal nCmdShow As eShowWindow) As Long
'
' Para posicionar una ventana segn su hWnd
Private Declare Function MoveWindow Lib "user32" _
(ByVal hWnd As Long, ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint As
Long) As Long
'
' Para cambiar el tamao de una ventana y asignar los valores
mximos y mnimos del tamao
Private Type POINTAPI
x As Long
y As Long

End Type
Private Type RECTAPI
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type WINDOWPLACEMENT
Length As Long
Flags As Long
ShowCmd As Long
ptMinPosition As POINTAPI
ptMaxPosition As POINTAPI
rcNormalPosition As RECTAPI
End Type
Private Declare Function GetWindowPlacement Lib "user32" _
(ByVal hWnd As Long, ByRef lpwndpl As WINDOWPLACEMENT) As Long
Ahora, simplemente haremos que el formulario se "meta" en el contenedor y que
al cambiar el tamao del formulario, tambin se cambie el tamao del formulario
"incrustado".
En el siguiente cdigo se supone que el formulario que queremos mostrar se llama
"Form2" y que el control que contendr dicho formulario se llama Picture1.
Tambin tendremos un botn que se usar para mostrar dicho formulario.
Para que se vea el "efecto", tendremos a la izquierda un segundo control, en este
ejemplo es otro PictureBox, pero, como te comentaba antes, puede ser cualquier
otro control que queramos tener acoplado a la parte izquierda del formulario. En
este caso ese control se llama picIzq y haremos que se acople a la izquierda,
asignando a la propiedad Align el valor vbAlignLeft.

Private Sub cmdMostrarForm2_Click()


'
' Asignar el Tag del formulario para saber que est incluido en
el Picture
Form2.Tag = valorDock
'
dockForm Form2.hWnd, Picture1, True
End Sub
Private Sub Form_Resize()
' ajustar el tamao del Picture al del formulario

' slo si no est minimizado


If WindowState <> vbMinimized Then
' ajustar el Picture a todo el formulario
Picture1.Move picIzq.Width, 0, ScaleWidth - picIzq.Width,
ScaleHeight
End If
End Sub
Private Sub Picture1_Resize()
' slo si no est minimizado
If WindowState <> vbMinimized Then
' posicionar el botn en el centro
With cmdMostrarForm2
.Left = (Picture1.ScaleWidth - .Width) \ 2
.Top = (Picture1.ScaleHeight - .Height) \ 2
End With
'
Dim oForm As Form
'
For Each oForm In Forms
' El tag del formulario incluido en el picture tendr el
Tag asignado
' con el valor "valorDock"
If CStr(oForm.Tag) = valorDock Then
posDockForm oForm.hWnd, Picture1
End If
Next
End If
End Sub
En la parte de declaraciones del formulario tendremos una constante llamada
valorDock que simplemente nos servir para saber si un formulario est o no
incluido dentro del picture.
Cuando queramos "meter" un formulario en el picture, asignaremos esa constante
a la propiedad Tag y seguidamente llamaremos al procedimiento dockForm
indicndole el formulario que queremos meter, adems del control en el que
queremos que se meta.
Cuando cambia el tamao del formulario, el Picture1 se ajusta a ese nuevo
tamao. En este cdigo slo tenemos en cuenta que hay otro control a la
izquierda, pero no arriba ni abajo, en caso de que tengamos otros controles arriba
y/o abajo, tendremos que tener en cuenta esos controles a la hora de posicionar y
dar tamao al Picture1.

En el evento Resize del Picture1 se centra el botn y posteriormente recorremos


todos los formularios que tenemos en la aplicacin para comprobar si hay alguno
que tenga ese valor "mgico" que indica que el formulario debe estar dentro del
Picture. En caso de que as sea, llamamos al procedimiento posDockForm para
que ajuste el tamao y posicin del formulario "incrustado".
En las siguientes capturas, puedes ver el formulario principal sin y con otro
formulario dentro.

El formulario "normal"

El formulario con el otro formulario "incrustado"


Y eso es todo.
Para saber cmo agregar una barra que te permita cambiar el tamao de los dos
paneles, chale un vistazo a este otro ejemplo.
Nos vemos.
Guillermo
Pulsa aqu si quieres bajarte el cdigo con el proyecto completo: dockVB6.zip 3.74
KB

En el cdigo de completo se muestra cmo posicionar el botn Cerrar del segundo


formulario en la parte inferior derecha.
Si quieres ver funcionando este ejemplo y el de cambiar el tamao de los paneles
centrales, puedes bajarte el cdigo completo, en el que se incluye un ejemplo de
cmo poner cualquier ventana (de la que sabemos el caption o ttulo) en un
picture: DockSplitVB6.zip 5.42 KB
Nota del 26/May/2004:
En el cdigo de ejemplo tengo puesto como programa a incluir, el bloc de notas,
pero como mi sistema operativo est en ingls, utilizo "Untitled - Notepad", as que
tendrs que cambiar el nombre al que corresponda en espaol, que me imagino
que es: "Sin ttulo - Bloc de notas".
En el zip ya est incluido un comentario en el sitio que hay que hacer el cambio.
Este "bug" (si es que se le puede llamar as), ya me lo reportaron hace tiempo,
pero ahora no recuerdo quin fue... cuando encuentre el mensaje, lo pondr...
Gracias a los que reportis los fallos que de vez en cuando cometo... que yo no
soy perfecto!

El formulario con el Notepad dentro de un picture.

Cambiar el tamao de dos controles (split) (25/Ene/04)


En este ejemplo, veremos cmo dividir un formulario en dos partes y poder
cambiar el tamao de las mismas en tiempo de ejecucin (Split).
Como te comentaba en el ejemplo de cmo incluir un formulario dentro de
un control Picture...
Normalmente tendremos un formulario principal, el cual puede tener un
men principal y tambin en la parte superior un ToolBar. Por otro lado, en
la parte inferior tambin tendremos un StatusBar. En el centro, (en la parte
principal del formulario), tendremos dos "paneles", uno a la izquierda y otro
a la derecha. En el panel izquierdo podramos insertar un control al estilo

del Outlook con una serie de botones de opciones, etc. y en el panel


derecho podramos tener el contenido de otro formulario de nuestra
aplicacin.
Todo esto son "deseos", ya que en el ejemplo que te voy a mostrar, todos
estos "posibles" controles sern del tipo PictureBox, aunque realmente el
nico que "debera" ser un PictureBox es el panel de la derecha, el que
contendr el "otro" formulario. Pero, para no complicar demasiado el
ejemplo, he preferido dejarlos todos como controles Picture. Sigue las
"recomendaciones" de los comentarios del ejemplo y vers que te resultar
fcil usar otros controles.
En este ejemplo no vamos a "incrustar" ningn formulario en el picture de
la derecha, ya que la forma de hacerlo te la he mostrado en ese otro
artculo, aunque al final encontrars el cdigo de un ejemplo completo en el
que se pondr en prctica lo explicado en aqul artculo as como en este,
es decir: un formulario principal que tendr cuatro "paneles" y en uno de
ellos meteremos un segundo formulario.
Pero en este ejemplo tendremos los siguientes controles:
Cuatro controles del tipo Picture, uno que estar alineado en la parte
superior, otro en la parte inferior, otro en la parte izquierda y el ltimo en la
parte derecha. Estos controles se llaman picU, picD, picL y picR
respectivamente, aunque en el formulario se usarn otros nombres,
simplemente para facilitar el poder usar otros controles de otros tipos y no
tener que cambiar "todo" el cdigo.
Tambin tenemos un control Image y otro Picture que sern los que harn
de barra divisoria (split). Los dos controles que se usarn para la barra
divisoria se llaman: imgSplitter y picSplitter.
Lo primero que haremos es asignar a las variables definidas en el
formulario los controles que tenemos en el formulario, adems de ocultar el
picSplitter y asignar los valores de alineacin (docking) de los dos paneles
del centro:
Private Sub Form_Load()
' asignar los controles que realmente se usarn
' En caso de que no sean Pictures, cambiarlos en la
declaracin
Set objIzq = picL
Set objDer = picR
Set objSup = picT
Set objInf = picD
'
' asignar el "acoplamiento"
objIzq.Align = vbAlignLeft
objDer.Align = vbAlignRight
'
picSplitter.Visible = False

'
End Sub
Para poder hacer que se pueda cambiar el tamao, necesitamos que alguno
de los controles se entere de que estamos moviendo la barra de
desplazamiento, adems de que debemos mostrar grficamente que
estamos "moviendo" dicha barra de desplazamiento. Esto ltimo lo haremos
en los eventos MosuseDown, MouseUp y MouseMove del control imgSplitter.
Private Sub imgSplitter_MouseDown(Button As Integer,
Shift As Integer, x As Single, y As Single)
With imgSplitter
picSplitter.Move .Left, .Top, .Width \ 3,
.Height - 20
End With
picSplitter.Visible = True
moviendo = True
End Sub
Private Sub imgSplitter_MouseMove(Button As Integer,
Shift As Integer, x As Single, y As Single)
Dim sglPos As Single
'
If moviendo Then
sglPos = x + imgSplitter.Left
If sglPos < splitLimit Then
picSplitter.Left = splitLimit
ElseIf sglPos > Me.Width - splitLimit Then
picSplitter.Left = Me.Width - splitLimit
Else
picSplitter.Left = sglPos
End If
End If
End Sub
Private Sub imgSplitter_MouseUp(Button As Integer, Shift
As Integer, x As Single, y As Single)
sizeControls picSplitter.Left
picSplitter.Visible = False
moviendo = False
End Sub

Por otro lado cuando el control de la izquierda "recibe" un control, si ese


control es el imgSplitter tendremos que ajustar los tamaos dependiendo
de la nueva posicin de dicho control. Veamos el cdigo para aclarar este
punto.
Private Sub picL_DragDrop(Source As Control, x As
Single, y As Single)
If Source = imgSplitter Then
sizeControls x
End If
End Sub
Y este es el cdigo del procedimiento que se encarga de ajustar el tamao y
posicin de los controles.
' Este procedimiento se usar para ajustar los tamaos
de los paneles
Private Sub sizeControls(ByVal x As Long)
Dim tMinWidth As Long
'
On Error Resume Next
'
' el ancho mnimo que tendr cada panel
tMinWidth = Screen.TwipsPerPixelY * 90
'
' asignar el ancho
If x < tMinWidth Then x = tMinWidth
If x > (Me.Width - tMinWidth) Then x = Me.Width tMinWidth
objIzq.Width = x
imgSplitter.Left = x
objDer.Left = x + 90
objDer.Width = Me.ScaleWidth - (objIzq.Width +
imgSplitter.Width)
'
' asignar la parte superior
' aqu se puede usar otro control para saber dnde
situar la parte superior
objIzq.Top = objSup.Top + objSup.Height
objDer.Top = objIzq.Top
'
' asignar la altura

' aqu se puede usar otro control para saber dnde


situar la parte inferior
If objInf.Visible Then
objIzq.Height = Me.ScaleHeight - (objSup.Top +
objSup.Height + objInf.Height)
Else
objIzq.Height = Me.ScaleHeight - (objSup.Top +
objSup.Height)
End If
'
objDer.Height = objIzq.Height
imgSplitter.Top = objIzq.Top
imgSplitter.Height = objIzq.Height
End Sub
Aqu tienes unas capturas de la aplicacin en ejecucin:

El formulario antes de hacer nada...

Moviendo la barra para cambiar el tamao.

El formulario con el nuevo tamao de los paneles.


Aqu tienes el cdigo con el formulario de ejemplo: dockVB6Split.zip 2.86
KB
Si quieres ver funcionando este ejemplo y el de incluir un formulario dentro
de un control Picture, puedes bajarte el cdigo completo, en el que se
incluye un ejemplo de cmo poner cualquier ventana (de la que sabemos el
caption o ttulo) en un picture: DockSplitVB6.zip 5.92 KB

El formulario con el Notepad dentro de un picture.

Rev. 22/Sep/04: Un pequeo bug


Reportado por Adolfo Pereiro el 1 de Agosto: (pego parte del mensaje con
el bug y la solucin)
Al grano, el caso es que en tu cdigo si minimizas la ventana, hayas
puesto como hayas puesto el separador de los dos PictureBox
centrales, (picL, picR), al restaurar, el separador aparece en la
posicin mas a la izquierda posible. Espero haberme explicado bien.
Yo lo he resuelto almacenando en la propiedad Tag del formulario
(podra haber usado una variable global) la propiedad Left del objeto
imgSplitter:
Me.Tag = imgSplitter.Left
Esto lo hago en dos eventos, en el load del form1, y en el MouseUp
del imgSplitter. Posteriormente en el evento Resize del form1, asigno
el valor almacenado en el Tag a la propiedad Left del imgSplitter:
imgSplitter.Left = Me.Tag
De esta manera queda solucionado el problema.

13. Poner nuestra aplicacin en el inicio de Windows (registro)


(14/May/04)
Debido a que he recibido varios comentarios de cmo usar de forma prctica la
clase cQueryReg, con la que podemos manejar el registro de Windows, voy a
empezar a poner algunos ejemplos de cmo usarla, y en esta ocasin ser para
poder poner nuestra aplicacin en el Inicio de Windows, de forma que al iniciarse

la sesin de un usuario, nuestra aplicacin tambin se inicie.


Por supuesto estoy hablando de incluir nuestra aplicacin dentro del propio registro
de Windows, no del men de inicio de la carpeta de programas.
La clave en la que se incluir ser en:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
Si a esta clave aadimos una nueva entrada (o valor) de tipo String que apunte a
nuestro ejecutable, ste se iniciar junto con el Sistema Operativo.
Para facilitar las cosas, usaremos la clase cQueryReg que nos servir para
manipular el registro.
Por supuesto, si usamos las funciones del API que en dicha clase se utilizan, no
necesitaremos usar la clase... pero de lo que se trata es de "reutilizar" algo que ya
est hecho, y eso es lo que vamos a hacer.
Ms abajo podrs bajarte un ejecutable que har tres cosas:
-Comprobar si una aplicacin est ya en esa clave
-Aadir una aplicacin a la clave Run
-Quitar nuestra aplicacin para que no se inicie con Windows
Veamos qu cdigo habra que usar para cada una de esas acciones.
Preparativos o valores usados en estos ejemplos:
Te recuerdo que en nuestro proyecto tendremos aadida la clase cQueryReg, que
en el cdigo que te mostrar la tengo declarada como mReg.
Tambin tengo una constante (cvRun) con el valor de la clave que queremos
manipular, en este caso, estas seran las declaraciones de estas dos variables:
Private mReg As cQueryReg
Private Const cvRun As String =
"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\"
Adems de estas declaraciones, se utilizan dos cajas de texto, una de ellas con el
nombre del valor que queremos aadir (txtClave) y el otro con el nombre del
ejecutable (txtExe). Por otro lado, la aplicacin tambin usa una etiqueta
(LabelInfo) en la que se mostrar si todo fue bien o no, ya que esto puede que no
funcione si no tenemos los privilegios adecuados para poder manipular el registro.
Comprobar si una aplicacin est ya en el inicio de Windows
' comprobar si la clave ya existe
Dim s As String
'
s = mReg.GetRegString(cvRun, txtClave.Text)
If s <> "" Then
LabelInfo.Caption = "SI existe la clave indicada" & vbCrLf & s
Else
LabelInfo.Caption = "NO existe la clave indicada"
End If

Aadir una aplicacin a la clave Run del registro


' Poner la clave
Dim s As String
'
s = mReg.GetRegString(cvRun, txtClave.Text)
If s <> "" Then
LabelInfo.Caption = "La clave ya estaba asignada." & vbCrLf & s
Else
If mReg.SetReg(cvRun, txtClave.Text, txtExe.Text) = ERROR_NONE
Then
LabelInfo.Caption = "La clave se ha asignado correctamente."
Else
LabelInfo.Caption = "ERROR al crear la clave."
End If
End If
Quitar nuestra aplicacin para que no se inicie con Windows
' Quitar la clave
Dim s As String
'
s = mReg.GetRegString(cvRun, txtClave.Text)
If s <> "" Then
If mReg.DeleteValue(cvRun, txtClave.Text) = ERROR_NONE Then
LabelInfo.Caption = "La clave se ha eliminado del registro."
Else
LabelInfo.Caption = "ERROR al eliminar la clave."
End If
Else
LabelInfo.Caption = "La clave NO estaba creada."
End If
Aqu tienes el enlace al cdigo de ejemplo, ExeEnCurrentVersionRun.zip (43.7 KB),
en el que se incluye tambin la clase cQueryReg.
Espero que con esto te resulte ms fcil usar la clase cQueryReg o al menos te
puede servir cmo poder aadir tu aplicacin al registro de Windows para que se
inicie automticamente.
14. Equivalencias del API de Windows con .NET (28/Feb/05)

En esta pgina encontrars las declaraciones tanto para VB .NET como para
C# de algunas de las funciones del API de Windows, adems de las
constantes y tipos de datos usados habitualmente por el API de Windows.
Si viene al caso, tambin te pondr un link al artculo en el que se utiliza
dicha API, para que tengas un ejemplo prctico y "realista" de cmo usarla,
ya que aqu simplemente te mostrar la forma de declarar la funcin,
constante o tipo de datos, sin ningn ejemplo de cmo usarla.
En el caso de que no exista un artculo publicado que la utilice, procurar
aadir un link a una pgina con un ejemplo "prctico" para usar la funcin
del API y, si es posible o viene al caso, tambin te indicar la clase de .NET
que podemos usar para realizar la misma tarea.
En cada funcin del API de Windows he incluido la definicin de la misma
que aparece en la documentacin del SDK de Windows, as como las
declaraciones para VB6, VB .NET y C#.
Estas son las declaraciones de funciones del API de Windows equivalentes
para Visual Basic .NET y C# (y la misma declaracin para Visual Basic 6.0
-clsico-).

Equivalencias en los tipos de datos del API de Windows

EnumChildWindows

EnumChildWindowsProc (delegado)

EnumWindows

EnumWindowsProc (delegado)

ExtractIcon

ExtractIconEx

FindWindow

GetActiveWindow

GetClassLong

GetClassWord

GetDriveType (*)

GetForegroundWindow

GetLogicalDrives (*)

GetVersion

GetVersionEx

GetLongPathName

GetShortPathName

GetWindowText

IsWindowVisible

MoveWindow

OSVERSIONINFO (tipo)

SendMessage

SetForegroundWindow

SetParent

SetWindowPos

SetWindowText

ShowWindow

En la nueva pgina/seccin hay ms funciones y tipos del API de


Windows y la forma de usarlo.

Nota:
En las declaraciones aqu mostradas, salvo descuido, he usado la forma
"recomendada" de .NET, es decir: aplicando el atributo DllImport, (definido
en System.Runtime.InteropServices) en caso de que usemos la forma
"clsica" de VB6, el compilador de VB crear una declaracin "parecida" a
la de DllImport, pero usando los valores predeterminados.
A continuacin te muestro la forma de usar el atributo DllImport y los
campos que puedes indicar. En su versin simple, funciona de la siguiente
forma (segn los parmetros usados):
DllImport(String) Se indica el nombre de la Librera de Windows que
contiene la funcin, esta es la forma "recomendable" de usar este atributo
si queremos usar lo valores predeterminados del resto de campos.
DllImport(String, EntryPoint := String) Se indica el nombre de la
librera en el primer parmetro, en el segundo se indicar el nombre de la
funcin tal y como est definida en el fichero de cabecera (.H)
correspondiente.
Habitualmente existen dos nombres de para cada funcin del API,
una acabada con W y otra acabada con A, segn se utilice un
sistema operativo Unicode (Windows NT/2000/XP/2003/CE) o no
(Win95/98/ME). Ver ms abajo la descripcin de CharSet,
EntryPoint y ExactSpelling.
Los otros parmetros de este atributo, que podemos indicar (usando la
forma: nombre_campo := valor en VB o nombre_campo = valor en C#)
son:

CharSet:
El tipo de juego de caracteres a usar.
Puede ser:

--Auto, se elegir el juego de caracteres adecuado segn el sistema


operativo.
--Ansi para sistemas Windows 98 / ME o
--Unicode para sistemas Windows NT / 2000/ XP / 2003 / CE

El valor predeterminado es Auto, por tanto si no se indica ningn


valor especial en EntryPoint, se elegir la funcin que mejor se
adecue al sistema operativo.

EntryPoint:
El valor del campo EntryPoint indica el nombre "exacto" de la
funcin del API que queremos usar.
Si el nombre de la funcin del API tiene el mismo nombre que el
usado en la declaracin, no es necesario indicar este valor.
Como he indicado antes, ese nombre puede acabar con A o W,
segn el tipo de funcin a usar: ANSI o UNICODE respectivamente.
Si no indicamos en EntryPoint el nombre de la funcin, se usar la
que "coincida" con el nombre de la funcin indicada en la
declaracin y segn el valor asignado al campo CharSet (o
ExactSpelling) se aadir la letra A o W (si es que existen dos
versiones diferentes).
Si no se indica ningn valor especfico en CharSet, (el valor
realmente ser Auto), se usar la versin ANSI (acabada en A) o
UNICODE (acabada en W) segn el sistema operativo en el que se
est ejecutando la aplicacin.

ExactSpelling:
Indica si el nombre de la funcin se ha escrito de la misma forma
que en la librera del API. Esto es para cuando no se usa EntryPoint,
ya que en caso de que el valor de este campo sea False, se aadir
una A al nombre de la funcin si CharSet es Ansi y una W si el valor
de Charset es Unicode.

El valor predeterminado para C# es false.


El valor predeterminado para VB depender del valor de CharSet,
para Ansi y Unicode es True, para Auto es False.

SetLastError:
Ver la documentacin en lnea para ms informacin.
El valor predeterminado para VB y C# es False.
Si en VB utilizamos Declare en lugar de DllImport para declarar la
funcin, el valor de SetLastError es True.

PreserveSig:
Ver la documentacin en lnea para ms informacin.
El valor predeterminado es True.

CallingConvention:
Ver la documentacin en lnea para ms informacin.
El valor predeterminado es WinAPI que se convertir en una llamada
StdCall para plataformas Windows o en Cdecl para CE.

BestFitMapping:
Ver la documentacin en lnea para ms informacin.
El valor predeterminado es True.

Si quieres ver las declaraciones para .NET de muchas de las funciones del
API de Windows, no debes dejar de visitar este sitio: PINVOKE.NET.

Equivalencias en los tipos de datos


Los tipos de datos equivalentes entre el API de Windows y los tipos de .NET,
los podramos resumir segn la siguiente tabla, en la que se muestran
tambin los de VB6.

Nota:
Debido a que prcticamente todos los tipos del API, salvo
excepciones, se corresponden con un entero de .NET
(Int32), para que la tabla no sea demasiado montona, de
los tipos de .NET slo mostrar los tipos definidos en el
CTS (Common Type System), sistema de tipos comunes
de .NET, es decir los tipos de datos del propio .NET
Framework, ya que no creo que te resulte difcil saber a
que tipo de VB o C# equivale.
Como podrs comprobar, bsicamente con saber que el
tipo Int32 (Integer en VB, int en C#) de .NET equivale a
un LONG del API de Windows, tendramos ms que
suficiente, ya que ese es el tipo de datos (incluso
camuflado) es el que se suele usar en las libreras del API
que estn escritas en C "normal".
Tambin comprobars que .NET dispone de "tipos" para lo
que en el API de Windows se soluciona con "apaos de
andar por casa", tal es el caso de los punteros de C, que
en .NET se solucionan mediante delegados.
Algunos tipos, como el UINT se podran cambiar por el "especfico", pero
tambin funcionar si se cambia por un Int32. El caso de HWND que es el
"handle" de una ventana, se puede cambiar indistintamente por un Int32 o
preferiblemente por System.IntPtr.

Tipo del API

Descripcin

.NET

VB6

HWND

Handle de la ventana de
destino

Int32
System.IntPtr
(4)

Long

Int32

Long

UINT
WPARAM

Cuando se usa en
Send/PeekMessage, el
primer mensaje a enviar a
la ventana

Int32

Long

LPARAM

El segundo mensaje a
enviar a la ventana

Int32

Long

DWORD

Int32

Long

BOOL

Boolean

Long/Boolean

HRESULT

Int32

Long

LPCTSTR

Direccin de una cadena


(no modificable)

String o
ByVal String
StringBuilder (2)

LPTSTR

Direccin de una cadena


(modificable)

String o
ByVal String
StringBuilder (2)

LPMSG

Puntero a una estructura


del tipo MSG (1)

--

LPDEVMODE

Puntero a una estructura

--

del tipo DEVMODE (1)


WNDENUMPROC Puntero a una funcin
CallBack (3)

Llamada a una Long


funcin definida (AddressOf)
por un delegado

CALLBACK

Definicin de un -delegado

Definicin de la funcin
CallBack (3)

1. Los punteros a estructuras normalmente se definen usando la


estructura como parmetro por referencia (ByRef en VB, ref en C#)
2. Cuando enviamos cadenas al API de Windows, es preferible indicar
un tipo StringBuilder ya que de esta forma evitamos la recreacin
de una nueva cadena, que es lo que ocurrira si usamos un tipo de
datos String.
3. Las funciones CallBack son punteros a otras funciones, en .NET todo
esto se maneja usando delegados (en VB .NET en los punteros a
funciones se usar AddressOf para realizar la llamada al delegado).
4. Es recomendable que para los manejadores de las ventanas utilices
un tipo IntPtr en lugar de un Int32, ya que la propiedad Handle de
los controles de Windows.Forms es del tipo IntPtr, y esa propiedad
precisamente se puede usar para casi todas las funciones del API de
Windows.

SendMessage
Esta funcin del API se utiliza para muchsimas cosas diferentes y
dependiendo del uso, puede que los parmetros empleados sean de
diferentes tipos, pero gracias a que los lenguajes de .NET permiten la
sobrecarga de funciones, podremos usar el mismo nombre de funcin sin
necesidad de que importe de que tipos son dichos parmetros.
Sin embargo en VB6 al no permitir el mismo nombre de funcin, en
ocasiones tenamos que definirla usando nombres diferentes para usos
diferentes, esa es la razn principal de que los dos ltimos parmetros de
esta funcin se definieran con As Any, que es la declaracin que con ms
frecuencia se utiliza en VB6, pero que en otros casos se puede utilizar con
parmetros diferentes, segn el uso que le fusemos a dar.
Ejemplo (usando una estructura como ltimo parmetro)

Declaracin en el API de Windows:


LRESULT SendMessage(
HWND hWnd,

// handle of destination window

UINT Msg,

// message to send

WPARAM wParam,

// first message parameter

LPARAM lParam

// second message parameter

);

VB6 genrico:
Private Declare Function SendMessage Lib "user32" Alias
"SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByRef lParam As Any) As Long

Si queremos indicar un valor de tipo Long en el ltimo parmetro (para que


se "iguale" la declaracin con las dos de .NET, habra que declararla de esta
forma:
Private Declare Function SendMessage Lib "user32" Alias
"SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Si en el ltimo parmetro queremos usar un String y queremos usarlo al


mismo tiempo que la anterior:
Private Declare Function SendMessageStr Lib "user32" Alias
"SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByVal lParam As String) As Long

VB .NET:
<System.Runtime.InteropServices.DllImport("user32.DLL")> _
Private Shared Function SendMessage( _
ByVal hWnd As System.IntPtr, ByVal wMsg As Integer, _
ByVal wParam As Integer, ByVal lParam As Integer _

) As Integer
End Sub

Si queremos usar un String en el ltimo parmetro y queremos usarla al


mismo tiempo que la anterior, slo hay que declararla nuevamente con los
parmetros diferentes, (sin necesidad de cambiar el nombre, tal como se
hace en VB6):
<System.Runtime.InteropServices.DllImport("user32.DLL")> _
Private Shared Function SendMessage( _
ByVal hWnd As System.IntPtr, ByVal wMsg As Integer, _
ByVal wParam As Integer, ByVal lParam As String _
) As Integer
End Sub

C#:
[System.Runtime.InteropServices.DllImport("user32.DLL")]
private extern static int SendMessage(
System.IntPtr hWnd, int wMsg,
int wParam, int lParam);
[System.Runtime.InteropServices.DllImport("user32.DLL")]
private extern static int SendMessage(
System.IntPtr hWnd, int wMsg,
int wParam, string lParam);

GetLongPathName
Esta funcin la podemos usar para convertir un nombre corto (tipo MS-DOS
8.3) en uno largo.
Declaracin en el API de Windows:
DWORD GetLongPathName(
LPCTSTR lpszShortPath,
to be converted

// Pointer to a null-terminated path

LPTSTR lpszLongPath,
the long path.

// Pointer to the buffer to receive

// You can use the same buffer you


used for the lpszShortPath parameter
DWORD cchBuffer
in characters

// Specifies the size of the buffer,

);

Declaracin para VB6:


Private Declare Function GetLongPathName Lib "kernel32" Alias
"GetLongPathNameA" _
(ByVal lpszShortPath As String, _
ByVal lpszLongPath As String, _
ByVal cchBuffer As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("kernel32.dll")> _
Private Shared Function GetLongPathName( _
ByVal lpszShortPath As String, _
ByVal lpszLongPath As System.Text.StringBuilder, _
ByVal cchBuffer As Integer) As Integer
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private extern static int GetLongPathName(
string lpszShortPath,
System.Text.StringBuilder lpszLongPath,
int cchBuffer);

GetShortPathName
Esta funcin la podemos usar para convertir un nombre largo en uno corto
(tipo MS-DOS 8.3).
Declaracin en el API de Windows:
DWORD GetShortPathName(
LPCTSTR lpszLongPath,
string
LPTSTR lpszShortPath,

// pointer to a null-terminated path


// pointer to a buffer to receive the
// null-terminated short form of the

path
DWORD cchBuffer
pointed

// specifies the size of the buffer


// to by lpszShortPath

);

Declaracin para VB6:


Private Declare Function GetShortPathName Lib "kernel32" Alias
"GetShortPathNameA" _
(ByVal lpszLongPath As String, _
ByVal lpszShortPath As String, _
ByVal cchBuffer As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("kernel32.dll")> _
Private Shared Function GetShortPathName( _
ByVal lpszLongPath As String, _
ByVal lpszShortPath As System.Text.StringBuilder, _
ByVal cchBuffer As Integer) As Integer
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private extern static int GetShortPathName(
string lpszLongPath,
System.Text.StringBuilder lpszShortPath,
int cchBuffer);

SetForegroundWindow
Trae la ventana indicada al frente y la activa.
Si se pudo traer al frente, devolver un valor Verdadero (<>0 en VB6).
En ocasiones podramos sustituir esta funcin del API usando el mtodo
BringToFront que todos los controles (y/o clases derivadas de Control)
tienen.
Declaracin en el API de Windows:
BOOL SetForegroundWindow(
HWND hWnd

// handle to window to bring to foreground

);

Declaracin para VB6:


Private Declare Function SetForegroundWindow Lib "user32"
(ByVal hWnd As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function SetForegroundWindow(ByVal hWnd As
System.IntPtr) As Boolean
End Function

Declaracin para C#:


{System.Runtime.InteropServices.DllImport("user32.dll")]
public extern static bool SetForegroundWindow(System.IntPtr
hWnd);

FindWindow
Devuelve el "handle" de la ventana que coincida con la clase y nombre
indicados.
Normalmente el primer parmetro se puede omitir.

Ejemplos: VB6, .NET (VB y C#)

Declaracin en el API de Windows:


HWND FindWindow(
LPCTSTR lpClassName,

// pointer to class name

LPCTSTR lpWindowName

// pointer to window name

);

Declaracin para VB6:


Private Declare Function FindWindow Lib "user32" Alias
"FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _

Private Shared Function FindWindow( _


ByVal lpClassName As String, _
ByVal lpWindowName As String) As System.IntPtr
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static System.IntPtr FindWindow(
string lpClassName,
string lpWindowName);

ShowWindow
Muestra (cambia) la ventana actual al estado indicado.
Si la ventana ya estaba visible devuelve un valor distinto de cero.
El estado puede ser: maximizada, minimizada, etc.
En .NET sera el equivalente a asignar un valor a la propiedad WindowState
de un formulario.

Declaracin en el API de Windows:


BOOL ShowWindow(
HWND hWnd,

// handle to window

int nCmdShow

// show state of window

);

Declaracin para VB6:


Private Declare Function ShowWindow Lib "user32" _
(ByVal hWnd As Long, ByVal nCmdShow As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function ShowWindow( _
ByVal hWnd As System.IntPtr, _
ByVal nCmdShow As Integer) As Integer
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static int ShowWindow(
System.IntPtr hWnd,
int nCmdShow);

IsWindowVisible
Utilizada para saber si una ventana est visible.

Declaracin en el API de Windows:


BOOL IsWindowVisible(
HWND hWnd

// handle to window

);

Declaracin para VB6:


Private Declare Function IsWindowVisible Lib "user32" (ByVal
hWnd As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function IsWindowVisible(ByVal hWnd As
System.IntPtr) As Boolean
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
public extern static bool IsWindowVisible(System.IntPtr hWnd);

GetWindowText
Recupera el texto de la barra de ttulos de una ventana o el texto de un
control.
Devuelve el nmero de caracteres copiados en el segundo parmetro o cero
si la ventana o el control no tienen texto que copiar.

Ejemplos:

Con EnumWindows,
con GetForegroundWindow

Declaracin en el API de Windows:


int GetWindowText(
// handle to window or control with text

LPTSTR lpString,

// address of buffer for text

int nMaxCount

// maximum number of characters to copy

);

HWND hWnd,

Declaracin para VB6:


Private Declare Function GetWindowText Lib "user32" Alias
"GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal
cch As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function GetWindowText( _
ByVal hWnd As System.IntPtr, _
ByVal lpString As System.Text.StringBuilder, _
ByVal cch As Integer) As Integer
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static int GetWindowText(
System.IntPtr hWnd,
System.Text.StringBuilder lpString,
int cch);

SetWindowText
Cambia el texto de la barra de ttulos de una ventana o el texto de un
control.
Devuelve un valor verdadero (distinto de cero) si todo funcion bien.

Declaracin en el API de Windows:


BOOL SetWindowText(

HWND hWnd,

// handle to window or control

LPCTSTR lpString

// address of string

);

Declaracin para VB6:


Private Declare Function SetWindowText Lib "user32" Alias
"SetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function SetWindowText( _
ByVal hWnd As System.IntPtr, _
ByVal lpString As String) As Boolean
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static bool SetWindowText(
System.IntPtr hWnd,
string lpString);

EnumWindows
Se usa para iniciar la enumeracin de ventanas de "mayor" nivel (top level).
En el primer parmetro se indicar un "puntero a una funcin", (ver
EnumWindowsProc), que en .NET se declara como un delegado.

Ejemplo

Declaracin en el API de Windows:


BOOL EnumWindows(
WNDENUMPROC lpEnumFunc,

// pointer to callback function

LPARAM lParam

// application-defined value

);

Declaracin para VB6:


Private Declare Function EnumWindows Lib "user32" _
(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function EnumWindows( _
ByVal lpfn As EnumWindowsDelegate, _
ByVal lParam As Integer) As Boolean
End Function
Private Delegate Function EnumWindowsDelegate _
(ByVal hWnd As System.IntPtr, ByVal parametro As Integer) As
Boolean

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static bool EnumWindows(EnumWindowsDelegate
lpfn, int lParam);

private delegate bool EnumWindowsDelegate (System.IntPtr hWnd,


int parametro);

EnumChildWindows
Enumera las "ventanas" de una ventana "top-level". Es decir las ventanas
pertenecientes a una ventana, por ejemplo los controles que contiene una
ventana... hay que recordar que para Windows todo lo que se muestra es
una ventana (o casi).
En el segundo parmetro se indicar un "puntero a una funcin", (ver
EnumChildProc), que en .NET se declara como un delegado.

Ejemplo
Declaracin en el API de Windows:
BOOL EnumChildWindows(
HWND hWndParent,

// handle to parent window

WNDENUMPROC lpEnumFunc,

// pointer to callback function

LPARAM lParam

// application-defined value

);

Declaracin para VB6:


Private Declare Function EnumChildWindows Lib "user32" _
(ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal
lParam As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function EnumChildWindows _
(ByVal hWndParent As System.IntPtr, _
ByVal lpEnumFunc As EnumWindowsDelegate, _
ByVal lParam As Integer) As Integer
End Function

Private Delegate Function EnumWindowsDelegate _


(ByVal hWnd As System.IntPtr, ByVal parametro As Integer) As
Boolean

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static int EnumChildWindows(
System.IntPtr hWndParent,
EnumWindowsDelegate lpEnumFunc,
int lParam);
private delegate bool EnumWindowsDelegate (System.IntPtr hWnd,
int parametro);

EnumWindowsProc, EnumChildProc
Estas funciones son funciones callback, es decir "un puntero a una funcin".
Realmente no es que se use para "crear" ventanas "callback", sino que es la
"definicin" de cmo debe ser la funcin callback usada con las dos
primeras funciones. Sera el equivalente a un delegado de .NET.
Para usar estos delegados habra que usar las funciones EnumWindows o
EnumChildWindows de la siguiente forma, suponiendo que tenemos un
mtodo con la misma firma del delegado llamado EnumWindowsProc:
En VB .NET:
EnumWindows(AddressOf EnumWindowsProc, 0)
EnumChildWindows(handleParent, AddressOf EnumWindowsProc, 0)

En C#:
EnumWindows(new EnumWindowsDelegate(EnumWindowsProc), 0)

EnumChildWindows(handleParent, new
EnumWindowsDelegate(EnumWindowsProc), 0)

Ejemplo

Declaracin en el API de Windows:


BOOL CALLBACK EnumWindowsProc(
HWND hwnd,
LPARAM lParam

// handle to parent window


// application-defined value

);
BOOL CALLBACK EnumChildProc(
HWND hwnd,
LPARAM lParam

// handle to child window


// application-defined value

);

Declaracin para VB6:


La funcin "callback" se debera declarar (en un mdulo BAS) como:
Public Function EnumWindowsProc(ByVal hWnd As Long, ByVal
parametro As Long) As Long
'...
End Function

Declaracin para VB .NET:


Private Delegate Function EnumWindowsDelegate _
(ByVal hWnd As System.IntPtr, ByVal parametro As Integer) As
Boolean

Declaracin para C#:


private delegate bool EnumWindowsDelegate (System.IntPtr hWnd,
int parametro);

ExtractIcon y ExtractIconEx
Extraer iconos de una aplicacin o fichero (.exe, .dll,. etc.)

Ejemplos: .NET (VB y C#) y VB6

Declaracin en el API de Windows:

Declaracin para VB6:


Private Declare Function ExtractIcon Lib "shell32.dll" Alias
"ExtractIconA" _
(ByVal hInst As Long, ByVal lpszExeFileName As String, _
ByVal nIconIndex As Long) As Long

Declaracin para VB .NET:


' Declaraciones para extraer iconos de los programas
<DllImport("shell32.dll")> _
Private Shared Function ExtractIconEx( _
ByVal lpszFile As String, ByVal nIconIndex As Integer,
_
ByRef phiconLarge As Integer, ByRef phiconSmall As
Integer, _
ByVal nIcons As UInteger) As Integer
End Function
<DllImport("shell32.dll")> _
Private Shared Function ExtractIcon( _
ByVal hInst As Integer, ByVal lpszExeFileName As
String, _
ByVal nIconIndex As Integer) As IntPtr
End Function

Declaracin para C#:


[DllImport("shell32.dll")]
private extern static int ExtractIconEx(string lpszFile, int
nIconIndex,
ref int phiconLarge, ref int phiconSmall, uint
nIcons);
[DllImport("shell32.dll")]
private extern static IntPtr ExtractIcon(int hInst,
string lpszExeFileName, int nIconIndex);

GetClassLong
Devuelve un valor de 32 bits (long) de la ventana indicada por el handle del
primer parmetro.

Ejemplo para .NET (VB y C#)


Declaracin en el API de Windows:
DWORD GetClassLong(
HWND hWnd,
int nIndex
);

Declaracin para VB .NET:


<DllImport("user32.dll")> _
Private Shared Function GetClassLong( _
ByVal hWnd As IntPtr, ByVal nIndex As Integer) As
Integer
End Function

Declaracin para C#:


[DllImport("user32.dll")]
private extern static int GetClassLong(IntPtr hWnd, int
nIndex);

GetForegroundWindow
Esta funcin devuelve el Handle de la venta que est actualmente activa,
(la que el usuario est usando en ese momento).
GetForegroundWindow no es lo mismo que GetActiveWindow, ya que esta
ltima devuelve la relacionada con el mismo thread (hilo) de la aplicacin
actual (la nuestra).

Ejemplo
Declaracin en el API de Windows:
HWND GetForegroundWindow(VOID);

Declaracin para VB6:


Private Declare Function GetForegroundWindow Lib "user32" () As
Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function GetForegroundWindow() As System.IntPtr
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static System.IntPtr GetForegroundWindow();

GetActiveWindow
GetActiveWindow devuelve el Handle de la venta activa de nuestra
aplicacin, sera el equivalente a ActiveForm de VB6.
Si quieres averiguar cual es la ventana que actualmente est activa (no
necesariamente de nuestra aplicacin), puedes usar GetForegroundWindow.

Ejemplo
Declaracin en el API de Windows:
HWND GetActiveWindow(VOID);

Declaracin para VB6:


Private Declare Function GetActiveWindow Lib "user32" () As
Long

Declaracin para VB .NET:

<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Shared Function GetActiveWindow() As System.IntPtr
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private extern static System.IntPtr GetActiveWindow();

GetVersion
Ejemplo para Visual Basic 6.0, ejemplo para Visual Basic .NET
y C#
Devuelve la versin actual del sistema operativo.
Declaracin en el API de Windows:
DWORD GetVersion(void);

Declaracin para VB6:


Private Declare Function GetVersion Lib "kernel32" () As Long

Declaracin para VB .NET:


<DllImport("kernel32.dll")> _
Public Function GetVersion() As Integer
End Function

Declaracin para C#:


[DllImport("kernel32.dll")]
static extern public int GetVersion();

GetVersionEx

Ejemplo para Visual Basic 6.0,

Ejemplo para Visual Basic .NET y C#

Nota:
En los ejemplos anteriores se explica cmo usar la funcin y
se muestra el cdigo de la declaracin, tanto de la funcin
como de los tipos usados para obtener la informacin.

OSVERSIONINFO y OSVERSIONINFOEX

Ejemplo para Visual Basic 6.0,

Ejemplo para Visual Basic .NET y C#

Nota:
En los ejemplos anteriores se explica cmo usar la funcin y
se muestra el cdigo de la declaracin, tanto de la funcin
como de los tipos usados para obtener la informacin.

MoveWindow
Esta funcin la usaremos para cambiar el tamao y la posicin de una
ventana de la que conocemos el handle.
Si esa ventana est contenida en otra, la posicin ser relativa a la ventana
que la contiene, no al escritorio.

Ejemplos: VB6, .NET (VB y C#)


Declaracin en el API de Windows:

BOOL MoveWindow(
HWND hWnd,

// handle to window

int X,

// horizontal position

int Y,

// vertical position

int nWidth,
int nHeight,

// width
// height

BOOL bRepaint // repaint flag


);

Declaracin para VB6:


Private Declare Function MoveWindow Lib "user32" _
(ByVal hWnd As Long, ByVal x As Long, ByVal y As Long, _
ByVal nWidth As Long, ByVal nHeight As Long, ByVal bRepaint
As Long) As Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Function MoveWindow(ByVal hWnd As IntPtr, ByVal x As
Integer, ByVal y As Integer, _
ByVal nWidth As Integer, ByVal
nHeight As Integer, _
ByVal bRepaint As Integer) As
Integer
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
public static int MoveWindow(IntPtr hWnd, int x, int y, int
nWidth, int nHeight, int bRepaint);

SetParent
Asigna a una ventana el padre o contenedor.

Ejemplos: VB6, .NET (VB y C#)


Declaracin en el API de Windows:
HWND SetParent(
HWND hWndChild,
changing

// handle to window whose parent is

HWND hWndNewParent

// handle to new parent window

);

Declaracin para VB6:


Private Declare Function SetParent Lib "user32" _
(ByVal hWndChild As Long, ByVal hWndNewParent As Long) As
Long

Declaracin para VB .NET:


<System.Runtime.InteropServices.DllImport("user32.dll")> _
Private Function SetParent(ByVal hWndChild As IntPtr, ByVal
hWndNewParent As IntPtr) As IntPtr
End Function

Declaracin para C#:


[System.Runtime.InteropServices.DllImport("user32.dll")]
private IntPtr SetParent(IntPtr hWndChild, IntPtr
hWndNewParent);

SetWindowPos
Esta funcin sirve para cambiar el tamao y posicin de una ventana (la
indicada por el handle del primer parmetro), pero tambin se usa para
poner esa venta encima (o debajo) del resto de ventanas.

Ejemplos: .NET (VB y C#) (para poner siempre encima) y VB6


Declaracin en el API de Windows:
BOOL SetWindowPos(
HWND hWnd,
HWND hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
UINT uFlags

);

Declaracin para VB6:


Private Declare Function SetWindowPos Lib "user32" _
(ByVal hWnd As Long, ByVal hWndInsertAfter As Long, _
ByVal X As Long, ByVal Y As Long, ByVal cX As Long, ByVal
cY As Long, _
ByVal wFlags As Long) As Long

Declaracin para VB .NET:


La declaracin se puede hacer como Sub (void en C#) o como Function,
devolviendo un Integer o un Boolean, pero normalmente no se utiliza el
valor devuelto.
<DllImport("user32.DLL")> _
Private Shared Sub SetWindowPos( _
ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, _
ByVal X As Integer, ByVal Y As Integer, _
ByVal cx As Integer, ByVal cy As Integer, _
ByVal wFlags As Integer)
End Sub

Declaracin para C#:


[DllImport("user32.DLL", EntryPoint="SetWindowPos")]
static extern bool SetWindowPos(
int hWnd,
int hWndInsertAfter,

// handle to window
// placement-order handle

int X,
position

// horizontal

int Y,
position

// vertical

int cx,

// width

int cy,

// height

uint uFlags
options
);

// window-positioning

GetLogicalDrives
The GetLogicalDrives function retrieves a bitmask representing the
currently available disk drives.
Que ms o menos quieres decir que devuelve una mscara de bits con los
discos disponibles.

Nota:
Existe un mtodo con el mismo nombre definido en la clase
Environment que sirve para casi lo mismo, bueno, en
realidad es parecido a la funcin del API
GetLogicalDriveStrings.

Ejemplos:

Para Visual Basic 6

Para Visual Basic .NET y C#

Declaracin en el API de Windows:


DWORD GetLogicalDrives(void);

Declaracin para VB6:


Private Declare Function GetLogicalDrives Lib "kernel32" () As
Long

Declaracin para VB .NET:


<DllImport("kernel32.dll")> _
Private Function GetLogicalDrives() As Integer
End Function

Declaracin para C#:

[DllImport("kernel32.dll")]
private extern static int GetLogicalDrives();

GetDriveType
The GetDriveType function determines whether a disk drive is a removable,
fixed, CD-ROM, RAM disk, or network drive.
Pues eso, que esta funcin nos indica el tipo de disco.

Nota:
El valor devuelto por esta funcin usa los valores de las
enumeracin indicada en el ejemplo de VB.NET y C#.
Si quieres ms detalle sobre los tipos de discos USB, busca
info sobre SetupDiGetDeviceRegistryProperty (yo an no
lo he probado, si lo hago, pues te lo pondr por esta pgina
o seccin)

Ejemplos:

Para Visual Basic 6

Para Visual Basic .NET y C#

Declaracin en el API de Windows:


UINT GetDriveType(
LPCTSTR lpRootPathName
);

Declaracin para VB6:


Private Declare Function GetDriveType Lib "kernel32" Alias
"GetDriveTypeA" _
(ByVal nDrive As String) As Long

Declaracin para VB .NET:

<DllImport("kernel32.dll")> _
Private Function GetDriveType(ByVal nDrive As String) As
TipoUnidades
End Function

La declaracin de la enumeracin TipoUnidades:


Enum TipoUnidades As Integer
' Indico tambin los valores del API (empiezan por cero y
van de uno en uno)
Desconocido
' 0 DRIVE_UNKNOWN
cannot be determined.

The drive type

No_montado
invalid;

The root path is

' 1 DRIVE_NO_ROOT_DIR

'
is no volume mounted at the path.

for example, there

Extraible
removable media;

The drive has

' 2 DRIVE_REMOVABLE

'
floppy drive or flash card reader.

for example, a

'
da como extraibles.

Las llaves USB los

Fijo
media;

' 3 DRIVE_FIXED

The drive has fixed

'
drive, flash drive, or thumb drive.

for example, a hard

'
normales enchufados por USB son fijos.

Los discos duros

Remoto
' 4 DRIVE_REMOTE
remote (network) drive.

The drive is a

CDROM
ROM drive.

' 5 DRIVE_CDROM

The drive is a CD-

RAMDISK
disk.

' 6 DRIVE_RAMDISK

The drive is a RAM

End Enum

Declaracin para C#:


[DllImport("kernel32.dll")]
private extern static TipoUnidades GetDriveType(string nDrive);

La declaracin de la enumeracin TipoUnidades:


enum TipoUnidades : int

{
// Indico tambin los valores del API (empiezan por cero y
van de uno en uno)
Desconocido,
// 0 DRIVE_UNKNOWN
cannot be determined.

The drive type

No_montado,
invalid;

The root path is

// 1 DRIVE_NO_ROOT_DIR

//
is no volume mounted at the path.

for example, there

Extraible,
removable media;

The drive has

// 2 DRIVE_REMOVABLE

//
floppy drive or flash card reader.

for example, a

//
da como extraibles.

Las llaves USB los

Fijo,
fixed media;

// 3 DRIVE_FIXED

The drive has

//
hard drive, flash drive, or thumb drive.

for example, a

//
normales enchufados por USB son fijos.

Los discos duros

Remoto,
// 4 DRIVE_REMOTE
remote (network) drive.

The drive is a

CDROM,
ROM drive.

// 5 DRIVE_CDROM

The drive is a CD-

RAMDISK,
disk.

// 6 DRIVE_RAMDISK

The drive is a RAM

};

15. Cerrar aplicaciones (clase cWindows) (02/Ene/99 - 12/Ago/05)


Este ejemplo usa la clase cWindows para cerrar ventanas, obtener el
ClassName de una ventana, etc.
Estos son los mtodos de esa clase:
Mtodo
ClassName
CloseApp
EnumTopWindows

MinimizeAll

Utilidad
Devuelve el ClassName del nombre de la ventana
indicada
Cierra la ventana indicada en el parmetro,
opcionalmente se puede especificar el ClassName.
Enumera las ventanas que tienen ttulo y son
visibles, devuelve una coleccin (Variant) con el
ttulo y hWnd de cada ventana, opcionalmente
aade esta informacin a un ListBox (o ComboBox)
indicado en el segundo parmetro.
Minimiza todas las ventanas, si se indica el
ClassName, slo minimiza las ventanas que tengan
ese ClassName.

El primer ejemplo que vamos a ver simplemente cierra todas las carpetas
abiertas, para ello se usa CloseApp con el ClassName "CabinetWClass", que
es como se llaman este tipo de "ventanas", hay que hacer notar que el
navegador Internet Explorer, al menos el que viene con Windows 98, tiene
tambin ese ClassName, por tanto tambin se cerrar, si es que est
abierto.
Veamos el cdigo:
Para crear este ejecutable, aade la clase cWindows y un mdulo BAS, en
las propiedades del proyecto selecciona Sub Main como el objeto de entrada
al programa, e inserta el siguiente cdigo en el mdulo BAS:
'
Public Sub Main()
Dim m_UtilVentanas As cWindows
Dim col As Collection
Dim numItems As Long
Dim i As Long
Dim sTitulo As String
Set m_UtilVentanas = New cWindows
Set col = New Collection
Set col = m_UtilVentanas.EnumTopWindows
numItems = col.Count
For i = 1 To numItems Step 2
sTitulo = col.Item(i)
Call m_UtilVentanas.CloseApp(sTitulo, "CabinetWClass")
Next
Set col = Nothing
Set m_UtilVentanas = Nothing
End
End Sub

Como puedes ver, no tiene mayor misterio, ya que casi todo el trabajo lo
hace la clase.
An as, te explicar un poco el cdigo:
La funcin EnumTopWindows de la clase cWindows, devuelve una coleccin
con el ttulo de cada ventana adems del hWnd, aunque esto ltimo no lo
usamos en este caso.
Como lo que guarda EnumTopWindows son dos datos: el nombre de la
ventana y el handle, los items de la coleccin hay que recorrerlos de dos en
dos, pero como lo que nos interesa es slo el ttulo de la ventana, tomamos
ese valor y se lo pasamos a CloseApp, con el ClassName de las ventanas
del Explorer, para que slo cierre ese tipo de ventanas.

Veamos ahora una utilidad que nos mostrar las ventanas abiertas y nos
dar cierta informacin de cada una de esas ventanas, cosa que se hace al
seleccionar una de las ventanas de la lista, la informacin que nos da es:
El ttulo de la ventana (normalmente es el Caption),
el handle de esa ventana,
el ClassName de la ventana.
Adems del ListBox con los nombres de las ventanas, hay una serie de
botones para cerrar las ventanas seleccionadas del ListBox, Refrescar la
informacin de las ventanas abiertas (cosa que se podra solucionar con un
Timer, pero...), Minimizar las ventanas del IExplorer (incluidas las carpetas)
y otro para Cerrar esas carpetas.
Este es el aspecto del Formulario, (el item seleccionado es el nombre de un
directorio o carpeta):

Cuando lo pruebes, te dars cuenta de que el VB usa nombres de clase


diferentes para los ejecutables que para el tiempo de diseo, adems,
cuando es un form del programa tiene este "ClassName": ThunderForm y
para la aplicacin ThunderMain, esto en tiempo de diseo, en un ejecutable
se cambian a estos nombres: ThunderRT5Form y ThunderRT5Main (para el
VB5)
Para el VB6, los nombres de los formularios cambian a ThunderFormDC y
ThunderFormRT6DC, para el programa en s ser igual en tiempo de diseo
y ThunderRT6Main en tiempo de ejecucin.
Para cerrar una ventana de VB, hay que cerrar la que indica Main en el
Class Name, ya que el Formulario no se cierra...

Ahora es tiempo de seguir viendo cdigo,


Como ya te coment antes, casi todo el trabajo lo hace la clase, por tanto
en el formulario poco se hace, pero vamos a verlo:
'

'--------------------------------------------------------------------------------'Varias pruebas con ventanas


(01/Ene/99)
'Para enumerar las ventanas visibles y poder cerrarlas.
'
'Guillermo 'guille' Som, 1999
'--------------------------------------------------------------------------------Option Explicit
Dim m_UtilVentanas As cWindows
Private Sub cmdCerrarCarpetas_Click()
'Cierra las carpetas abiertas, el ClassName es:
CabinetWClass
'Nota: el IE4 tambin tiene ese ClassName
Dim sTitulo As String
Dim i As Long
With List1
For i = 0 To .ListCount - 1
sTitulo = .List(i)
Call m_UtilVentanas.CloseApp(sTitulo,
"CabinetWClass")
DoEvents
Next
End With
'No se refresca bien, as que seguramente tendrs que
pulsar en el botn...
cmdRefrescar_Click
End Sub
Private Sub cmdCerrarVentanas_Click()
'Cerrar las ventanas seleccionadas del ListBox
'
Dim sTitulo As String
Dim i As Long
With List1
For i = 0 To .ListCount - 1
If .Selected(i) Then

sTitulo = .List(i)
'No cerrar esta aplicacin
If (sTitulo <> App.Title) And (sTitulo <>
Caption) Then
Call m_UtilVentanas.CloseApp(sTitulo)
DoEvents
End If
End If
Next
End With
'No se refresca bien, as que seguramente tendrs que
pulsar en el botn...
cmdRefrescar_Click
End Sub
Private Sub cmdMinimizeAll_Click()
'Cuidado!
'Si no se especifica el ClassName se minimizan todas las
ventanas,
'si tienes alguna aplicacin de VB, se minimiza una ventana
que no es el form
'y despus no se puede restaurar...
'
'por eso en este ejemplo uso "CabinetWClass" para minimizar
las carpetas
'
Call m_UtilVentanas.MinimizeAll("CabinetWClass")
End Sub
Private Sub cmdRefrescar_Click()
Call m_UtilVentanas.EnumTopWindows(List1)
With List1
Label1(1) = .ListCount
If .ListCount Then
.ListIndex = 0
End If
End With
End Sub
Private Sub cmdSalir_Click()
Unload Me
End

End Sub
Private Sub Form_Load()
Set m_UtilVentanas = New cWindows

If App.PrevInstance Then
Caption = Caption & " (otra ms)"
App.Title = App.Title & " (otra ms)"
End If
Show
cmdRefrescar_Click
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set m_UtilVentanas = Nothing
Set Form1 = Nothing
End Sub
Private Sub List1_Click()
Dim i As Long
With List1
i = .ListIndex
If i > -1 Then
Label2(0) = .List(i)
Label2(1) = .ItemData(i)
Label2(2) = m_UtilVentanas.ClassName(Label2(0))
Else
Label2(0) = ""
Label2(1) = ""
Label2(2) = ""
End If
End With
End Sub

Por ltimo, el cdigo de la clase cWindows:


Si te preguntas por qu no he usado EnumWindows para enumerar las
ventanas, te dir que es porque no es necesario, ya que con GetWindow se

hace lo mismo y adems no es necesario crear un mdulo BAS para la


funcin "CallBack", si quieres ver un ejemplo de cmo usar EnumWindows,
puedes ver la colaboracin de Nacho Cassou que est en la seccin de
Colaboraciones y en VBAvanzado: Enumerando ventanas (Subclasificacin)

'
'--------------------------------------------------------------------------------'Clase para manipular ventanas de Windows
(01/Ene/99)
'
'Esta clase enumera las ventas visibles, cierra la indicada,
etc.
'
'Guillermo 'guille' Som, 1999
'--------------------------------------------------------------------------------Option Explicit
Private Declare Function IsWindowVisible Lib "user32" _
(ByVal hWnd As Long) As Long
Private Declare Function GetWindowTextLength Lib "user32" Alias
"GetWindowTextLengthA" _
(ByVal hWnd As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias
"GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As
Long) As Long
Private Declare Function GetDesktopWindow Lib "user32" () As
Long
' GetWindow() Constants
Private Const GW_HWNDFIRST = 0&
Private Const GW_HWNDNEXT = 2&
Private Const GW_CHILD = 5&
Private Declare Function GetWindow Lib "user32" _
(ByVal hWnd As Long, ByVal wFlag As Long) As Long
'
Private Declare Function FindWindow Lib "user32" Alias
"FindWindowA" _

(ByVal lpClassName As String, ByVal lpWindowName As String)


As Long
Private Declare Function SendMessage Lib "user32" Alias
"SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As
Long, lParam As Any) As Long
Private Const SC_MINIMIZE = &HF020&
Private Const SC_CLOSE = &HF060&
Private Const WM_SYSCOMMAND = &H112
Private Const WM_CLOSE = &H10
Private Declare Function GetClassName Lib "user32" Alias
"GetClassNameA" _
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal
nMaxCount As Long) As Long

Public Sub CloseApp(ByVal Titulo As String, Optional ClassName


As String)
'Cerrar la ventana indicada, mediante el men del sistema
(o de windows)
'Esto funcionar si la aplicacin tiene men de sistema
'(aunque lo he probado con una utilidad sin controlBox y la
cierra bien)
'
'Si se especifica ClassName, se cerrarn la ventana si es
de ese ClassName
'
Dim hWnd As Long
'No cerrar la ventana "Progman"
If Titulo <> "Progman" Then
hWnd = FindWindow(ClassName, Titulo)
Call SendMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, ByVal
0&)
End If
End Sub
Private Function WindowTitle(ByVal hWnd As Long) As String
'Devuelve el ttulo de una ventana, segn el hWnd indicado
'
Dim sTitulo As String
Dim lenTitulo As Long
Dim ret As Long

'Leer la longitud del ttulo de la ventana


lenTitulo = GetWindowTextLength(hWnd)
If lenTitulo > 0 Then
lenTitulo = lenTitulo + 1
sTitulo = String$(lenTitulo, 0)
'Leer el ttulo de la ventana
ret = GetWindowText(hWnd, sTitulo, lenTitulo)
WindowTitle = Left$(sTitulo, ret)
End If
End Function
Public Function EnumTopWindows(Optional unListBox As Control)
As Variant
'Enumera las ventanas que tienen ttulo y son visibles
'Devuelve un array del tipo Variant con los nombres de las
ventanas
'y su hWnd
'Por tanto la forma de acceder a este array sera:
'

Set col = EnumTopWindows

'

numItems = col.Count

'

For i = 1 To numItems Step 2

'

With List2

'

.AddItem col.Item(i)

'

.ItemData(.NewIndex) = col.Item(i + 1)

'
'

End With
Next

'
'Opcionalemente se puede especificar como parmetro un
ListBox o ComboBox
'y los datos se aadirn a ese control
'
Dim sTitulo As String
Dim hWnd As Long
Dim col As Collection
Set col = New Collection
If Not unListBox Is Nothing Then
unListBox.Clear
End If
'Primera ventana

hWnd = GetWindow(GetDesktopWindow(), GW_CHILD)


'Recorrer el resto de las ventanas
Do While hWnd <> 0&
'Si la ventana es visible
If IsWindowVisible(hWnd) Then
'Leer el caption de la ventana
sTitulo = WindowTitle(hWnd)
If Len(sTitulo) Then
'Aadimos el ttulo
col.Add sTitulo
'y el hWnd por si fuese til
col.Add hWnd
'Si se especifica el ListBox
If Not unListBox Is Nothing Then
With unListBox
.AddItem sTitulo
.ItemData(.NewIndex) = hWnd
End With
End If
End If
End If
'Siguiente ventana
hWnd = GetWindow(hWnd, GW_HWNDNEXT)
Loop
Set EnumTopWindows = col
End Function
Public Function ClassName(ByVal Title As String) As String
'Devuelve el ClassName de una ventana, indicando el ttulo
de la misma
Dim hWnd As Long
Dim sClassName As String
Dim nMaxCount As Long
hWnd = FindWindow(sClassName, Title)
nMaxCount = 256
sClassName = Space$(nMaxCount)
nMaxCount = GetClassName(hWnd, sClassName, nMaxCount)
ClassName = Left$(sClassName, nMaxCount)
End Function

Public Sub MinimizeAll(Optional ClassName As String)


'Minimizar todas las ventanas
'
Dim col As Collection
Dim numItems As Long
Dim i As Long
Dim sTitulo As String
Dim hWnd As Long
Set col = New Collection
Set col = Me.EnumTopWindows
numItems = col.Count
For i = 1 To numItems Step 2
sTitulo = col.Item(i)
hWnd = FindWindow(ClassName, sTitulo)
Call SendMessage(hWnd, WM_SYSCOMMAND, SC_MINIMIZE,
ByVal 0&)
Next
Set col = Nothing
End Sub

Espero que todo esto te pueda ser de utilidad... si no es as, lo siento...

16. Averiguar la versin de Windows usando API (24/May/07)

Introduccin:
Aqu tienes la forma de averiguar la versin de Windows en la que se est
ejecutando tu aplicacin.
Te muestro dos formas de averiguarlo, una es usando la funcin GetVersion y la
otra es usando GetVersionEx que utiliza el tipo de datos OSVERSIONINFOEX.
Aunque en realidad son tres formas, pero dos de ellas usan la misma funcin
GetVersionEx aunque con dos versiones diferentes del tipo OSVERSIONINFO.

Las definiciones de las funciones, tipos y constantes


La funcin GetVersion es muy simple, solo devuelve un valor de tipo Long en el
que estn los tres valores tpicos de la versin: Mayor, Menor y Build. Lo que pasa
es que est todo como "aglomerado" y hace falta desglosarlo, para ese desglose se
deben tomar los valores dividiendo (o troceando) el valor en distintas partes, para

eso se necesitan de otras funciones, ahora lo vers en la seccin de cmo usar la


funcin.
La definicin de la funcin:
Private Declare Function GetVersion Lib "kernel32" () As Long

La funcin GetVersionEx utiliza un tipo de datos en el que se indican los


diferentes valores. Esa versin puede usar el mismo tipo pero con ms o menos
informacin, por tanto, aqu te muestro dos definiciones segn se quiera usar la
versin reducida o extendida.
La definicin de los tipos y las funciones:
' La versin simple
Private Type OSVERSIONINFO
dwOSVersionInfoSize As Long
dwMajorVersion As Long
dwMinorVersion As Long
dwBuildNumber As Long
dwPlatformId As Long
szCSDVersion As String * 128
End Type
Private Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" _
(lpVersionInformation As OSVERSIONINFO) As Long

' La versin extendida


Private Type OSVERSIONINFOEX
dwOSVersionInfoSize As Long
dwMajorVersion As Long
dwMinorVersion As Long
dwBuildNumber As Long
dwPlatformId As Long
szCSDVersion As String * 128
wServicePackMajor As Integer
wServicePackMinor As Integer
wSuiteMask As Integer
wProductType As Byte
wReserved As Byte
End Type

Private Declare Function GetVersionEx2 Lib "kernel32" Alias "GetVersionExA"


_
(lpVersionInformation As OSVERSIONINFOEX) As Long

Cmo usar las funciones y tratar los resultados


Para usar la funcin GetVersion no hay que hacer nada especial, salvo guardar el
valor en una variable de tipo Long, y como te comentaba antes, ese valor hay que
"desgranarlo" para obtener los valores correspondientes.
Veamos cmo se usa y ms abajo puedes ver las funciones "extras" que he usado:

Dim ret As Long


Dim s As String
' Usando GetVersion
ret = GetVersion
Dim vMajor As Long, vMinor As Long, vBuild As Long
vMajor = LoByte(LoWord(ret))
vMinor = HiByte(LoWord(ret))
' A partir de NT 4 tiene el Build (no en Me/9x)
vBuild = HiWord(ret)
s = "Versin: " & _
vMajor & "." & vMinor & "." & vBuild

Para usar la versin GetVersionEx debemos declarar una variable del tipo
OSVERSIONINFO que es donde nos indica los valores de la versin. Aqu te
muestro el cdigo de forma simple, pero en el ZIP con el proyecto completo para
Visual Basic 6.0 tienes ms informacin de cmo identificar el sistema operativo
segn los valores de la versin.
Dim OSInfo As OSVERSIONINFO
Dim ret As Long
Dim s As String
OSInfo.szCSDVersion = Space$(128)
OSInfo.dwOSVersionInfoSize = Len(OSInfo)
ret = GetVersionEx(OSInfo)

s = "MajorVersion

" & OSInfo.dwMajorVersion & vbCrLf & _

"MinorVersion

" & OSInfo.dwMinorVersion & vbCrLf & _

"BuildNumber

" & OSInfo.dwBuildNumber & vbCrLf & _

"PlatformId

" & OSInfo.dwPlatformId & vbCrLf & _

"CSDVersion

" & szTrim(OSInfo.szCSDVersion)

Me.txtOSVersion.Text = s

Las versiones de Windows dependen del valor de Mayor y Menor, (y la combinacin


de los dos), y ms o menos te puede servir la siguiente tabla, que es aplicable a
los valores de las dos funciones comentadas:

Sistema operativo

Mayor

Windows
Windows
Windows
Windows

3
4
5
6

NT 3.51
95, 98, Me y NT 4.0
2000, XP y 2003
Vista/Longhorn

Sistema operativo

Menor

Windows
Windows
Windows
Windows
Windows
Windows
Windows
Windows
Windows

51
0
10
90
0
0
1
2
0

NT 3.51
95
98
Me
NT 4.0
2000
XP
2003
Vista/Longhorn

Por ejemplo, si el valor de Mayor es 5 y el de Menor es 2 es un Windows 2003


Server.
Si el valor de Mayor es 5 y el de Menor es 1 es un Windows XP.
Para averiguar cosas de Windows 9x o de Windows 2000 Server, si es XP Home o
Profesional, lo ves en el ejemplo completo, que estn casi todas las explicaciones
necesarias y que son aburridas y no es plan de rellenar con cosas que a lo mejor
no es lo que te interesa.

El cdigo para Visual Basic 6.0


Private Function szTrim(ByVal s As String) As String
' Quita los caracteres en blanco y los Chr$(0)
(13/Dic/01)

Dim i As Long
i = InStr(s, vbNullChar)
If i Then
s = Left$(s, i - 1)
End If
s = Trim$(s)
szTrim = s
End Function
Private Function LoWord(ByVal Numero As Long) As Long
' Devuelve el LoWord del nmero pasado como parmetro
LoWord = Numero And &HFFFF&
End Function
Private Function HiWord(ByVal Numero As Long) As Long
' Devuelve el HiWord del nmero pasado como parmetro
HiWord = Numero \ &H10000 And &HFFFF&
End Function
Private Function LoByte(ByVal Numero As Integer) As Integer
' Devuelve el LoByte del nmero pasado como parmetro
LoByte = Numero And &HFF
End Function
Private Function HiByte(ByVal Numero As Integer) As Integer
' Devuelve el HiByte del nmero pasado como parmetro
HiByte = Numero \ &H100 And &HFF
End Function

17. Ejecutar un acceso directo desde VB6 con ShellExecute


(15/Ago/07)

Introduccion:
Pues eso, aunque estoy seguro que esto que te voy a explicar ya estoy publicado
en otra parte de mi sitio, hace un rato me hizo falta, y la verdad es que no
encontrado nada concreto, o al menos que dejara claro que era para ejecutar un
acceso directo.

De todas formas, si que encontrado un ejemplo de mi amigo Joe LeVasseur sobre


como usar la funcion del API ShellExecute, que es en realidad lo unico que
hace falta para ejecutar un acceso directo, aunque esa misma funcion sirve para
ejecutar lo que quieras, ya que como dice mi amigo Joe, con esa funcion es "como
si hicieras doble clic".

Un ejemplo para ejecutar accesos directos y ms...


Ya puestos a probar, me he dicho, vamos a hacer las cosas "bien", o al menos
como a mi me gustaria que fuera esa "utilidad" o ejemplo para ejecutar cualquier
acceso directo.
Y lo que he hecho es agregarle funcionalidad al formulario para que haga lo
siguiente:
1. Acepte ficheros con Drag & Drop
2. Adapte los controles al cambiar el tamao del formulario
3. Y, por supuesto, que ejecute lo que indiquemos
El aspecto del formulario en ejecucion es como el mostrado en la figura 1:

Figura 1. La aplicacion en funcionamiento


Y como puedes adivinar solo tiene dos controles: un boton (btnEjecutar) y una
caja de textos (Text1).
Te lo digo mas que nada por si quieres crear el ejemplo por ti mismo, ya que aqui
abajo solo te muestro el codigo, pero no hay ningun ZIP con el proyecto completo.
Un detalle, aunque no es "crucial" para lo que vamos a ver, pero al menos te
serviria de informacion:
Los accesos directos en realidad son ficheros con la extension .lnk que contiene
informacion del programa que queremos ejecutar, y el ShellExecute lo maneja
perfectamente.
Y otro detalle mas, aunque este si que es mas importante:
Cuando ejecutas cualquier cosa con esta aplicacion de ejemplo, debes saber que
aunque cierres la aplicacion, si eso que ejecutas sigue funcionando, el sistema
operativo mantendria "pillado" al ejecutable, por tanto no te permitiria eliminarlo,

moverlo ni nada que altere el path o ruta en la que esta.


Eso se soluciona cerrando todas las aplicaciones que se hayan abierto desde ese
programa.

Nota:
Aclarar, que al menos en teoria, este codigo vale para cualquier version de
Visual Basic de 32 bits, es decir: Visual Basic 4.0, Visual Basic 5.0 o Visual
Basic 6.0.
Si quieres ver un ejemplo para hacer lo mismo pero con Visual Basic
para .NET (o con C#), mira esto:
Ejecutar un acceso directo desde Visual Basic o C# con la clase
Process

El codigo
Y una vez hechas estas aclaraciones, veamos el codigo para que esto funcione.
Empecemos viendo la declaracion de la funcion del API ShellExecute, que como la
vamos a declarar en el propio formulario, debe estar definida como "privada":
Private Declare Function ShellExecute Lib "shell32.dll" Alias _
"ShellExecuteA" (ByVal hwnd As Long, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, _
ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

Ahora veamos el codigo del boton que ejecutaria lo que haya en la caja de textos
Text1:
Private Sub btnEjecutar_Click()
' Ejecutar un acceso directo
Call ShellExecute(Me.hWnd, "Open", Text1.Text, "", "", 1)
End Sub

A continuacion veamos el codigo que permite aceptar ficheros con Drag & Drop,
para que esto funcione, la propiedad OLEDropMode del formulario y del textBox
debe tener el valor 1 'Manual.
Private Sub Form_OLEDragDrop(Data As DataObject, Effect As Long, _
Button As Integer, Shift As Integer, X As Single, Y As Single)
Text1.Text = Data.Files(1)
End Sub
Private Sub Text1_OLEDragDrop(Data As DataObject, Effect As Long, _
Button As Integer, Shift As Integer, X As Single, Y As Single)

Text1.Text = Data.Files(1)
End Sub

Por Ultimo veremos el codigo del evento Resize del formulario en el que
posicionaremos el boton para que este en la parte de la derecha y el textBox para
que se "alargue". Los valores usados son "manuales", es decir, mira los que tenia
que usar con cada uno de los controles... es lo malo de Visual Basic 6 comparado
con Visual Basic .NET en que esto se hace automticamente, pero... eso es lo que
hay...
Private Sub Form_Resize()
If Me.WindowState = vbNormal Then
Me.Text1.Width = Me.ScaleWidth - 405
Me.btnEjecutar.Left = Me.ScaleWidth - 1470
End If
End Sub

18. Averiguar la posicin del cursor con GetCursorPos (08/Jul/08)

GetCursorPos
Esta funcion sirve para saber la posicion actual del cursor, es una funcion del API
de Windows, en el caso de Visual Basic 6.0 (VB6) o anterior es la unica forma que
tenemos de averiguar ese dato, pero con los lenguajes de .NET Framework,
tambien podemos usar el metodo compartido Position de la clase Cursor que
est definida en System.Windows.Form.
La funcion del API utiliza un parametro por referencia del tipo POINT (POINT_API
que lo llamo en estos ejemplos para que no entre en conflicto con el tipo definido
en System.Drawing).
Mas abajo tienes el codigo de ejemplo para Visual Basic .NET, Visual C# y Visual
Basic 6.0 o anterior (VB6).
Las declaraciones en los tres lenguajes y segun el API de Windows son:

Declaracin en el API de Windows:


BOOL GetCursorPos
(
LPPOINT lpPoint
);

Declaracin para VB6:


Private Declare Function GetCursorPos Lib "user32.dll" ( _
ByRef lpPoint As POINT_API _
) As Long

Declaracin para VB .NET:


' Hay que tener una importacin a System.Runtime.InteropServices
<DllImport("user32.dll")> _
Public Function GetCursorPos(ByRef lpPoint As POINT_API) As Boolean
End Function
' Usando el formato compatible con VB6
Declare Function GetCursorPos Lib "user32.dll" ( _
ByRef lpPoint As POINT_API _
) As Boolean

Declaracin para C#:


[DllImport("user32.dll")]
public static extern bool GetCursorPos(out POINT_API lpPoint);

POINT / POINT_API
Esta es una estructura (Type en VB6) para usar con la funcin GetCursorPos.
Las declaraciones en los tres lenguajes y segn el API de Windows son:

Declaracin en el API de Windows:


typedef struct tagPOINT {
LONG x;
LONG y;
} POINT_API, *PPOINT;

Declaracin para VB6:

Private Type POINT_API


X As Long
Y As Long
End Type

Declaracin para VB .NET:


Public Structure POINT_API
Public X As Integer
Public Y As Integer
End Structure

Declaracin para C#:


public struct POINT_API
{
public int X;
public int Y;
}

Ejemplos
Para crear un proyecto que use este codigo debes tener un formulario con tres
etiquetas en el caso de Visual Basic .NET y Visual C# o con dos etiquetas en el
caso de Visual Basic 6.0 o anterior.
En estos codigos de ejemplo se supone que la definicion de la funcion del API y el
tipo (estructura), estn definidos en un modulo (Module) en el caso de Visual Basic
.NET, en una clase estatica en el caso de Visual C#. En el cdigo de Visual Basic
6.0 (o anterior) incluyo el cdigo completo.
Para interceptar los eventos en Visual C# debes indicarlo expresamente para que
tanto el evento MouseMove del formulario y de las tres etiquetas apunten al
mismo metodo.
En Visual Basic 6.0 y Visual Basic .NET no es necesario hacer nada especial, solo
tienes que copiar el cdigo y pegarlo en un formulario.

Ejemplo para VB6:


'-----------------------------------------------------------------------------

' GetCursorPos_VB6
(08/Jul/08)
' Ejemplo de la funcion del API GetCursorPos
'
' Guillermo 'guille' Som, 2008
'----------------------------------------------------------------------------Option Explicit
Private Type POINT_API
X As Long
Y As Long
End Type
Private Declare Function GetCursorPos Lib "user32.dll" ( _
ByRef lpPoint As POINT_API _
) As Long
Private Sub Form_MouseMove( _
Button As Integer, Shift As Integer, _
X As Single, Y As Single)
Dim p As POINT_API
GetCursorPos p
Label1.Caption = _
"Posicion del cursor: " & vbCrLf & _
"x= " & p.X & ", Y = " & p.Y
Label2.Caption = _
"Usando los valores de los parametros: " & vbCrLf & _
"x= " & X & ", Y = " & Y
End Sub
Private Sub Label1_MouseMove( _
Button As Integer, Shift As Integer, _
X As Single, Y As Single)
Form_MouseMove Button, Shift, X, Y
End Sub
Private Sub Label2_MouseMove( _

Button As Integer, Shift As Integer, _


X As Single, Y As Single)
Form_MouseMove Button, Shift, X, Y
End Sub

Ejemplo para VB .NET:


'----------------------------------------------------------------------------' GetCursorPos_VB
(08/Jul/08)
' Ejemplo de la funcion del API GetCursorPos
' y el equivalente en .NET Framework: Cursor.Position
'
' Guillermo 'guille' Som, 2008
'----------------------------------------------------------------------------Option Strict On
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Public Class Form1
Private Sub Form1_MouseMove(ByVal sender As Object, _
ByVal e As MouseEventArgs) _
Handles Me.MouseMove, _
Label1.MouseMove, Label2.MouseMove, _
Label3.MouseMove
Dim x As Integer = Cursor.Position.X
Dim y As Integer = Cursor.Position.Y
Label1.Text = _
"Usando Cursor.Position: " & vbCrLf & _
"x= " & x & ", Y = " & y
Dim p As POINT_API
GetCursorPos(p)

Label2.Text = _
"Usando GetCursorPos: " & vbCrLf & _
"x= " & p.X & ", Y = " & p.Y

Label3.Text = _
"Usando los valores del parmetro: " & vbCrLf & _
"x= " & e.X & ", Y = " & e.Y
End Sub
End Class
Module WinAPI
Public Structure POINT_API
Public X As Integer
Public Y As Integer
End Structure
'' Usando el formato compatible con VB6
'Declare Function GetCursorPos Lib "user32.dll" ( _
'

ByRef lpPoint As POINT_API _

'

) As Boolean

' Hay que tener una importacion a System.Runtime.InteropServices


<DllImport("user32.dll")> _
Public Function GetCursorPos(ByRef lpPoint As POINT_API) As Boolean
End Function
End Module

Ejemplo para C#:


//---------------------------------------------------------------------------// GetCursorPos_CS
(08/Jul/08)
// Ejemplo de la funcion del API GetCursorPos

// y el equivalente en .NET Framework: Cursor.Position


//
// Guillermo 'guille' Som, 2008
//---------------------------------------------------------------------------using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace GetCursorPos_CS
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
int x = Cursor.Position.X;
int y = Cursor.Position.Y;
Label1.Text =
"Usando Cursor.Position: \n" +
"x= " + x + ", Y = " + y;
WinAPI.POINT_API p;
WinAPI.GetCursorPos(out p);
label2.Text =
"Usando GetCursorPos: \n" +
"x= " + p.X + ", Y = " + p.Y;
label3.Text =
"Usando los valores del parmetro: \n" +
"x= " + e.X + ", Y = " + e.Y;
}
}

static class WinAPI


{
public struct POINT_API
{
public int X;
public int Y;
}
[DllImport("user32.dll")]
public static extern bool GetCursorPos(out POINT_API lpPoint);
}
}

Das könnte Ihnen auch gefallen