Excel VBA Macro Programming
Here are some examples of API calls and how to use them within your VBA code.
Getting Disk Space
For this example, you are going to use an API call that gets the spare disk space from a disk device. For the record, this particular API call gets around the 2GB problem. The original Microsoft API call only read disk drives up to 2GB ‚ after that it gave up and every result was capped at 2GB. As hard disk drive sizes, increased, Microsoft had to come up with a new API call.
First of all, you must make the declaration. You do this in the declarations section of a module. The syntax for this particular declaration is as follows :
Private Declare Function GetDiskFreeSpaceEx Lib "kernel32" _ Alias "GetDiskFreeSpaceExA" (ByVal lpDirectoryName As _ String, lpFreeBytesAvailableToCaller As Currency, _ lpTotalNumberOfBytes As Currency, lpTotalNumberOfFreeBytes _ As Currency) As Long
This is quite a long statement, and it has to be completely accurate to work. If you have access to Visual Basic, all the API declarations are contained in a file called API32.TXT and can easily be copied and pasted into your declarations. Further information can be obtained from the Microsoft Developer Network (MSDN) at microsoft.msdn.com, which is the perfect place to find more advanced information on Excel VBA and API calls.
Basically, this statement sets up a reference to the kernel32.dll that resides in the Windows system directory. It specifies the way parameters must be used and what they are called. Its purpose is to put a function into your code that you can use to call this library function from kernel32.dll.
The next step is to put in code to call this function:
Sub Test_Api() Dim FreeBytesAvailableToCaller As Currency, TotalNumberOfBytes As _ Currency, TotalNumberOfFreeBytes As Currency x = GetDiskFreeSpaceEx("c:\", FreeBytesAvailableToCaller, _ TotalNumberOfBytes, TotalNumberOfFreeBytes) MsgBox "Total Space " & Format(TotalNumberOfBytes * 10000, "#,##0") MsgBox "Free Space " & Format(TotalNumberOfFreeBytes * 10000, "#,##0") End Sub
This code sets up variables to hold the values returned by the API call. The actual value returned through the variable calling it (in this case, x ) is related to errors. It has a value of 1 for success.
The call is then made passing the root directory "C:\" and the variables already defined. It is then a simple case of multiplying the value by 10,000 and formatting to comma format for easy display. You can check the values by going into Windows Explorer or NT Explorer and selecting File Properties from the menu, as shown in Figure 17-1.
Figure 17-1: Getting the total space from a disk drive
Reading and Writing to INI Files
API calls can also be used for reading and writing to files. One of their useful functions is creating INI files for your program. INI files are a means of storing settings for your program, such as for control settings that you want to be sticky or details of user settings for individual applications. Variables and properties in VBA only hold their values while the program is present. Once you close down, all is lost, and everything returns to default.
An example might be a text box that holds a directory pathname that is set by the user. The user might put in a different pathname, but when the program is closed, it reverts to the default on reloading. This is irritating for the user, as they have to keep putting in their pathname.
The INI file holds this information locally so that it can be retrieved when the program is loaded next time. Of course, you could use a portion of the spreadsheet to store these values, but this could easily be overwritten by other data.
There are two declarations that can be used for your INI file. (There are other declarations for reading and writing to INI files, but you are going to use these in the example.)
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 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
These need to be added into the declarations section of a module. They set up references to the kernel32.dll file and describe how the parameters will be passed. You can then use code to pass the parameters and write to the file:
Sub Test_INI() x = WritePrivateProfileString("Parameters", "Path", "C:\temp\", "myini.ini") s$ = Space$(256) x = GetPrivateProfileString("Parameters", "Path", "", s$, 256, "myini.ini") MsgBox s$ MsgBox x End Sub
The first line writes the INI file. If the file is not already there, it is automatically created. The default location is in the Windows directory, although you can change this by placing a pathname onto the filename.
The first parameter (Parameters) is the section of the INI file to write the new string. The second parameter is the key name or the entry to set; if set to Null, it will delete all keys within this section. The third parameter is the value to write for the key. This is "C:\temp\"; if set to Null, it will delete the existing string for that key. The fourth parameter is the name of the INI file. You can use any suffix; you do not have to use an INI suffix. This is quite useful if you want to hide some settings and wish to disguise your INI file ‚ you can give it a DLL or EXE suffix so that it looks like a program file on casual inspection.
This now creates MYINI.INI. If you look in your Windows directory, you will see that the file has been created. If you double-click it, it will be loaded into Notepad because it is a text file, and should look like this:
[Parameters] Path=C:\temp\
The next API call reads the path back in. However, before this can happen, you must create a variable for it to be placed in.
The Space$ command has been used to create a string of spaces 256 long. Also, this variable must be specified as a string, otherwise errors will occur. The return value comes back as a string so it must be placed into a string variable.
GetPrivateProfileString works in a similar way to WritePrivateProfileString , but there are more parameters to pass. The first and second parameters are the same as before and give details of the section and key to be read. The third parameter has a default value to return if the entry is not found. The fourth parameter contains the variable name for the result to be passed to. The fifth parameter contains the maximum number of characters to load into the variable. The sixth parameter is the filename to search for.
The variable calling the API ( x ) will give the number of characters returned. S$ will contain the key, terminated by a Null character. If the keyname is left as Null, then all entries in that section will be returned, each terminated with a Null character.
Both these API calls are very forgiving in their operation. If the file is not there, it is created. If the key is not there, it is created. If the file does not exist when you use the read command, then a Null value will be returned.
This method is often used in programs to keep track of user settings so that when the user enters the program subsequently, it is set to that user's personal settings.
Microsoft no longer uses INI files; they use keys in the Registry to record this information.
Read Keyboard Activity
Another useful purpose of API calls is to read the keyboard and find out if a certain key has been pressed. There are events on user forms to manage keyboard events, but these only apply to a particular form or a particular control on the form that has the focus at that time.
For example, suppose you write a macro to do a time-consuming task that involves it looping around thousands of times. You may want to give the user a ‚“get out ‚½ command to bring it to a halt if it takes too long. You can only do this by checking the keyboard because the only way that you can see a keyboard event is on a UserForm, and this may not have the focus at the time when the user presses the ‚“get out ‚½ key. You use the API call GetKeyState to do this. You must put the following code in the declarations part of a module:
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) _ As Integer
This will allow you to examine any key while the program is running. You can even differentiate between left and right SHIFT keys or CTRL keys:
Sub Test_Key() x = 0 Do Until x = 1 If GetKeyState(&H9) < 0 Then x = 1 DoEvents Loop MsgBox "You pressed the TAB key" End Sub
This program sets up a simple Do Until..Loop that keeps running until x=1. This will never happen until the GetKeyState line turns up a value for the specified key that is less than 0 ‚ that is, it has been pressed. For the purposes of the example, the TAB key is used, which has a value of 09 in hexadecimal. When the TAB key is pressed, x changes its value to 1 and exits from the loop. The message box is then displayed.
The DoEvents command is very important here. It literally allows the operating system to catch its breath and finish processing all messages before moving onto the next instruction. If you do not use this, then the message from the keyboard will not be processed before the next loop is started and the keypress will be missed.
Run this code, and it goes into an infinite loop because x will never equal 1. Press any key on the keyboard and nothing happens. But press the TAB key, and the program will end with a message box. Of course, it helps if you know the values of the Virtual Key Codes if you want to use other key combinations. They are listed here:
Symbolic Constant Name | Value (Hexadecimal) | Mouse or Keyboard Equivalent |
VK_LBUTTON | 01 | Left mouse button |
VK_RBUTTON | 02 | Right mouse button |
VK_CANCEL | 03 | CTRL-BREAK processing |
VK_MBUTTON | 04 | Middle mouse button (three-button mouse) |
‚ | 05 ‚ 07 | Undefined |
VK_BACK | 08 | BACKSPACE |
VK_TAB | 09 | TAB |
‚ | 0A ‚ 0B | Undefined |
VK_CLEAR | 0C | CLEAR |
VK_RETURN | 0D | ENTER |
‚ | 0E ‚ 0F | Undefined |
VK_SHIFT | 10 | SHIFT |
VK_CONTROL | 11 | CTRL |
VK_MENU | 12 | ALT |
VK_PAUSE | 13 | PAUSE |
VK_CAPITAL | 14 | CAPS LOCK |
‚ | 15 ‚ 19 | Reserved for Kanji systems |
‚ | 1A | Undefined |
VK_ESCAPE | 1B | ESC |
‚ | 1C ‚ 1F | Reserved for Kanji systems |
VK_SPACE | 20 | SPACEBAR |
VK_PRIOR | 21 | PAGE UP |
VK_NEXT | 22 | PAGE DOWN |
VK_END | 23 | END |
VK_HOME | 24 | HOME |
VK_LEFT | 25 | LEFT ARROW |
VK_UP | 26 | UP ARROW |
VK_RIGHT | 27 | RIGHT ARROW |
VK_DOWN | 28 | DOWN ARROW |
VK_SELECT | 29 | SELECT |
‚ | 2A | Original equipment manufacturer (OEM) specific |
VK_EXECUTE | 2B | EXECUTE |
VK_SNAPSHOT | 2C | PRINT SCREEN |
VK_INSERT | 2D | INS |
VK_DELETE | 2E | DEL |
VK_HELP | 2F | HELP |
VK_0 | 30 |
|
VK_1 | 31 | 1 |
VK_2 | 32 | 2 |
VK_3 | 33 | 3 |
VK_4 | 34 | 4 |
VK_5 | 35 | 5 |
VK_6 | 36 | 6 |
VK_7 | 37 | 7 |
VK_8 | 38 | 8 |
VK_9 | 39 | 9 |
‚ | 3A ‚ 40 | Undefined |
VK_A | 41 | A |
VK_B | 42 | B |
VK_C | 43 | C |
VK_D | 44 | D |
VK_E | 45 | E |
VK_F | 46 | F |
VK_G | 47 | G |
VK_H | 48 | H |
VK_I | 49 | I |
VK_J | 4A | J |
VK_K | 4B | K |
VK_L | 4C | L |
VK_M | 4D | M |
VK_N | 4E | N |
VK_O | 4F | O |
VK_P | 50 | P |
VK_Q | 51 | Q |
VK_R | 52 | R |
VK_S | 53 | S |
VK_T | 54 | T |
VK_U | 55 | U |
VK_V | 56 | V |
VK_W | 57 | W |
VK_X | 58 | X |
VK_Y | 59 | Y |
VK_Z | 5A | Z |
VK_LWIN | 5B | LEFT WINDOWS (Microsoft Natural Keyboard) |
VK_RWIN | 5C | RIGHT WINDOWS (Microsoft Natural Keyboard) |
VK_APPS | 5D | APPLICATIONS (Microsoft Natural Keyboard) |
‚ | 5E ‚ 5F | Undefined |
VK_NUMPAD0 | 60 | Numeric keypad |
VK_NUMPAD1 | 61 | Numeric keypad 1 |
VK_NUMPAD2 | 62 | Numeric keypad 2 |
VK_NUMPAD3 | 63 | Numeric keypad 3 |
VK_NUMPAD4 | 64 | Numeric keypad 4 |
VK_NUMPAD5 | 65 | Numeric keypad 5 |
VK_NUMPAD6 | 66 | Numeric keypad 6 |
VK_NUMPAD7 | 67 | Numeric keypad 7 |
VK_NUMPAD8 | 68 | Numeric keypad 8 |
VK_NUMPAD9 | 69 | Numeric keypad 9 |
VK_MULTIPLY | 6A | MULTIPLY |
VK_ADD | 6B | ADD |
VK_SEPARATOR | 6C | SEPARATOR |
VK_SUBTRACT | 6D | SUBTRACT |
VK_DECIMAL | 6E | DECIMAL |
VK_DIVIDE | 6F | DIVIDE |
VK_F1 | 70 | F1 |
VK_F2 | 71 | F2 |
VK_F3 | 72 | F3 |
VK_F4 | 73 | F4 |
VK_F5 | 74 | F5 |
VK_F6 | 75 | F6 |
VK_F7 | 76 | F7 |
VK_F8 | 77 | F8 |
VK_F9 | 78 | F9 |
VK_F10 | 79 | F10 |
VK_F11 | 7A | F11 |
VK_F12 | 7B | F12 |
VK_F13 | 7C | F13 |
VK_F14 | 7D | F14 |
VK_F15 | 7E | F15 |
VK_F16 | 7F | F16 |
VK_F17 | 80H | F17 |
VK_F18 | 81H | F18 |
VK_F19 | 82H | F19 |
VK_F20 | 83H | F20 |
VK_F21 | 84H | F21 |
VK_F22 | 85H | F22 |
VK_F23 | 86H | F23 |
VK_F24 | 87H | F24 |
‚ | 88 ‚ 8F | Unassigned |
VK_NUMLOCK | 90 | NUM LOCK |
VK_SCROLL | 91 | SCROLL LOCK |
VK_LSHIFT | A0 | LEFT SHIFT |
VK_RSHIFT | A1 | RIGHT SHIFT |
VK_LCONTROL | A2 | LEFT CTRL |
VK_RCONTROL | A3 | RIGHT CTRL |
VK_LMENU | A4 | LEFT MENU |
VK_RMENU | A5 | RIGHT MENU |
‚ | E7 ‚ E8 | Unassigned |
‚ | E9 ‚ F5 | OEM specific |
VK_ATTN | F6 | ATTN |
VK_CRSEL | F7 | CRSEL |
VK_EXSEL | F8 | EXSEL |
VK_EREOF | F9 | ERASE EOF |
VK_PLAY | FA | PLAY |
VK_ZOOM | FB | ZOOM |
VK_NONAME | FC | Reserved for future use |
VK_PA1 | FD | PA1 |
VK_OEM_CLEAR | FE | CLEAR |
Play Multimedia Sounds
You can also use an API call to play a sound within your code from a WAV file. The old macro programming language had a command to do this, but it has not been included in VBA, or for that matter in Visual Basic itself, which makes it a bit restrictive if you want to play sound files. If you want to use Mulitmedia functions within your code to play sounds or voice files, then the means to play sound files is very crucial. The declaration goes in the declarations section of a module:
Public Declare Function PlaySound Lib "winmm.dll" Alias "PlaySoundA" _ (ByVal lpszName As String, ByVal hModule As Long, ByVal dwFlags As _ Long) As Long
You can then play the WAV files within your code:
Sub test_sound() x = PlaySound("c:\windows\media\chord.wav", 0, 2) x = PlaySound("c:\windows\media\ding.wav", 0, 2) End Sub
This example plays two standard Windows sounds. If you have a microphone on your computer, you can record your own sound effects or speech onto a WAV file and play them back in this way.
These examples give some idea of the power of API calls within a program. There are many books written on this subject if you wish to examine this topic further.