Jeff Webb, Mike McKelvy, Ronald Martinsen, Taylor Maxwell, Michael Regelski September 1995 Special Edition Using Visual Basic 4 - Chapter 33 1-56529-998-1 Computer Programming computer programming Visual Basic OLE database applications ODBC VB VBA API This book is an all-in-one reference that provides extensive coverage of every topic and technique for creating optimized and customized applications with Visual Basic.

Chapter 33

Accessing the Windows API


A programming language establishes boundaries of what can be done based on the different features of the language. If a feature does not exist, you must either create a workaround or do without that feature. If you want to display the extended memory available for a PC in a Visual Basic program, there is no way to do this with native Visual Basic. Fortunately, Visual Basic allows you access to the Windows API, which gives you a method for solving this problem.

API calls add capabilities to your Visual Basic applications. These new capabilities expand the depth of choices available to the Visual Basic programmer. This increase in capabilities comes at the price of forcing you to be more cautious. Visual Basic is inherently a fairly stable language. If you create applications with no API calls, you avoid problems caused by poor code that triggers random GPF errors. A problem with Visual Basic code will normally crash only the program. Problems with API calls will sometimes crash the Windows environment itself.

If API calls make Visual Basic applications potentially less stable, then why use them? You shouldn't use API calls just to use them, but there are several API calls that are very useful for improving the performance and look and feel of your applications. This chapter deals with showing you some of the useful API calls available to Visual Basic.

In this chapter, you learn how to do the following:

Visual Basic ships with a Help file named WIN31API.HLP. This file contains the declaration statements for most of the 3.1 API calls available within Visual Basic. All you have to do is look up the function or subroutine that you want and paste the appropriate text into one of the modules belonging to your application.

API Call Challenge

Using API calls in Visual Basic presents you with the challenge of how to reduce problems when you work with them. There are two techniques that you can use to reduce the possibility of error. First, jacket all of the API calls that can create problems. Second, create a common code module.

Visual Basic ships with a new feature called the API Text Viewer. This viewer provides a list of Windows API calls that you can search through. You simply have to find the API calls and add them to your project. The major advantage over the WIN31API.HLP file is that you can copy more than one call at the same time. The disadvantage is that there is no explanation text and you cannot see the actual declaration text until you paste it into your project.

Jacket Your Code

Code jacketing involves creating a subroutine or function that calls a specific API function or subroutine. Such a subroutine or function would include any required arguments for that API call. A jacketed API call allows you to shield yourself from any potential landmines that might cause a GPF error. One glaring example lies with the difference between the fixed-length strings used by the C-based Windows API calls and those used by Visual Basic. If you try to use a Visual Basic variable-length string in an API call, such as GetPrivateProfileString, you get a GPF.

The Common BAS File

Every time that you use a new Windows API call, you need to keep the code in a common code module. If you solve a problem once, why keep solving it over and over again? Every time that you start a new project, all you need to do is add this special module to your project and you already have access to all of the API calls and jacketed routines that you have used so far.

Configuration Strategies

Visual Basic 4.0 has the capability to create both 16-bit applications for Windows 3.x and 32-bit applications for Windows 95 and Windows NT. 16-bit Windows 3.x uses .INI files to store application configuration information. 32-bit Windows 95 and NT work with either the newer Registry or .INI files.

The choice of whether to use the Registry or .INI files lies with you. .INI files are more familiar to those of us who programmed for Windows 3.x with Visual Basic 3.0. There is the temptation to stay with the familiar. Use the method that makes the most sense.

Understanding Registry APIs

Windows 95 and NT place their system configuration information in the Registry database. This is a change from the older .INI system found in Windows 3.x systems. Any entries found in the old WIN.INI and SYSTEM.INI files are simply there for compatibility reasons. The Registry keeps information in binary files. An application uses the Registry API functions to access data in the Registry.

Registry Structure

Windows 95 and NT registries store their configuration data in a hierarchically structured tree. Both products ship with a Registry Editor which shows a picture of the Registry for the current computer or other computers on the network. Figure 33.1 shows what the Registry looks like. The Registry Editor does not appear as an icon in the default installation of either Windows 95 or NT. You can access the Registry Editor by running it from either the Run dialog box off of Start in Windows 95 or the Run dialog box from the File menu in Windows NT. The name of the executable is REGEDIT.EXE for Windows 95 and REGEDT32.EXE for Windows NT. You do not need to enter the path to make it work properly.

Each branch of the Registry tree has a text name, which is called a key. A key has a text name that consists of one or more printable ANSI characters. This means that only characters with a value between 32 and 127 are eligible. Key names cannot include a backslash, space, or wildcard character (* or ?). You must be sure not to begin a key name with a period.

Fig. 33.1

A view of the hierarchically structured Windows 95 Registry.

A key can have both subkeys and/or values. A subkey is a branch of the hierarchal tree that is connected to another key. For example, figure 33.1 shows Config as a subkey of HKEY_LOCAL_MACHINE. In some cases, the key itself is the data that an application uses. Values are any number of settings given to a key. Figure 33.2 shows the list of values for the Settings subkey.

Fig. 33.2

A view of values of the Settings subkey.

Registry Limits

There are practical limits to how much you should store in the Registry. You should only store configuration information for your applications in the Registry. Registry data consists of the bare outline of what your applications need to function. They answer questions like "Where is the data?" and "What is the valid UserName for this machine?". The list of all the states in the United States would not be a good candidate for the Registry. A better strategy would be to put in the Registry the name and path of the database that contains this information.

Security Mask Constants

Many of the Registry API functions use a security mask that identifies what kind of access a particular call needs. The security mask constants appear in the list of constants shown in listing 33.1. KEY_ALL_ACCESS is the most common setting, and you need to stick it with unless there is a reason to restrict access to the Registry data that you are working with. For example, you might want to use the STANDARD_RIGHTS_READ constant for a user who can view the entries on the Registry but cannot change them. These constants need to be added to the General Declarations section of the WINAPI32.BAS module.

Listing 33.1 Registry Security Mask Constants

'Security Mask constants
Public Const READ_CONTROL = &H20000
Public Const SYNCHRONIZE = &H100000
Public Const STANDARD_RIGHTS_ALL = &H1F0000
Public Const STANDARD_RIGHTS_READ = READ_CONTROL
Public Const STANDARD_RIGHTS_WRITE = READ_CONTROL
Public Const KEY_QUERY_VALUE = &H1
Public Const KEY_SET_VALUE = &H2
Public Const KEY_CREATE_SUB_KEY = &H4
Public Const KEY_ENUMERATE_SUB_KEYS = &H8
Public Const KEY_NOTIFY = &H10
Public Const KEY_CREATE_LINK = &H20
Public Const 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))
Public Const KEY_READ = ((STANDARD_RIGHTS_READ Or KEY_QUERY__
VALUE Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY) And _
(Not SYNCHRONIZE))
Public Const KEY_EXECUTE = ((KEY_READ) And (Not SYNCHRONIZE))
Public Const KEY_WRITE = ((STANDARD_RIGHTS_WRITE Or KEY_SET_VALUE _
Or KEY_CREATE_SUB_KEY) And (Not SYNCHRONIZE))

Registry Error Constants

All Registry functions return a long value that identifies whether the function call was successful. The most common outcome of a function call is ERROR_SUCCESS. This indicates that the call worked properly without errors. Any other value returned tells the application that something went wrong. You need to add the following constants in listing 33.2 to the General Declarations section of the WINAPI.BAS module.

Listing 33.2 Registry Return Code Constants

' Return codes from Registration functions.
Public Const ERROR_SUCCESS = 0&
Public Const ERROR_BADDB = 1009&
Public Const ERROR_BADKEY = 1010&
Public Const ERROR_CANTOPEN = 1011&
Public Const ERROR_CANTREAD = 1012&
Public Const ERROR_CANTWRITE = 1013&
Public Const ERROR_OUTOFMEMORY = 14&
Public Const ERROR_INVALID_PARAMETER = 87&
Public Const ERROR_ACCESS_DENIED = 5&

When an error occurs, you need to know why. Just seeing a blank screen with no data doesn't tell you what went wrong. For this reason, you need some kind of simple error testing routine to tell you exactly what went wrong. If a Registry API call doesn't work properly and returns something other than ERROR_SUCCESS, then a routine should call this error routine, which in turn displays an error message to tell you exactly what went wrong. This gives you a clue as to where to look for the root of the error. You need to add the gAPIDisplayError to the WINAPI32.BAS module (see listing 33.3).

Listing 33.3 Error-Checking Routine

Sub gAPIDisplayError(Code&)
Select Case Code&
Case ERROR_BADDB
MsgBox "Corrupt Registry Database!"
Case ERROR_BADKEY
MsgBox "Key name is bad"
Case ERROR_CANTOPEN
MsgBox "Cannot Open Key"
Case ERROR_CANTREAD
MsgBox "Cannot Read Key"
Case ERROR_CANTWRITE
MsgBox "Cannot Write Key"
Case ERROR_ACCESS_DENIED
MsgBox "Access to Registry Denied"
Case ERROR_OUTOFMEMORY
MsgBox "Out of memory"
Case ERROR_INVALID_PARAMETER
MsgBox "Invalid Parameter"
Case Else
MsgBox "Undefined key error code!"
End Select
End Sub

Data Format

When you place values for a key into the Registry, you need to let the API function know the format of the value with which you are working. The most commonly used format is REG_SZ, meaning a simple string value. Listing 33.4 contains a list of the registry data format constants.

Listing 33.4 Registry Data Format Constants

'Data type Public Constants
Public Const REG_NONE = 0
Public Const REG_SZ = 1
Public Const REG_EXPAND_SZ = 2
Public Const REG_BINARY = 3
Public Const REG_DWORD = 4
Public Const REG_DWORD_LITTLE_ENDIAN = 4
Public Const REG_DWORD_BIG_ENDIAN = 5
Public Const REG_LINK = 6
Public Const REG_MULTI_SZ = 7
Public Const REG_RESOURCE_LIST = 8
Public Const REG_FULL_RESOURCE_DESCRIPTOR = 9
Public Const REG_RESOURCE_REQUIREMENTS_LIST = 10

The RegOpenKeyEx Function

There are two API calls for opening a key from the Windows 95 or NT Registry: RegOpenKeyEx and RegOpenKey. The logical question for you to ask is why there are two functions that do the same thing. RegOpenKeyEx and RegOpenKey do not do the same things. ReOpenKey is included in the current API for compatibility reasons only. Programmers are strongly encouraged to switch their applications to use the newer RegOpenKeyEx and stop using RegOpenKey. Listing 33.5 shows the predefined keys used in RegOpenKeyEx.

Listing 33.5 Predefined Keys Used in RegOpenKeyEx

'Predefined Registry Keys used in hKey Argument
Public Const HKEY_CLASSES_ROOT = &H80000000
Public Const HKEY_CURRENT_USER = &H80000001
Public Const HKEY_LOCAL_MACHINE = &H80000002
Public Const HKEY_USERS = &H80000003
Public Const HKEY_PERFORMANCE_DATA = &H80000004

Any application that works with the Registry must begin with an open key. There are several preopened keys including HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, and HKEY_PERFORMANCE_DATA. There is a public constant for each of these prefined open keys in the preceding list of constants. You need to place these constants (Shown in Listing 33.5) in the General Declarations section of the WINAPI32.BAS module. These preopened keys are your starting point for working with the Registry.

Declare Function RegOpenKeyEx Lib "advapi32" Alias _
"RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, _
ByVal ulOptions As Long, ByVal samDesired As Long, _
phkResult As Long) As Long

RegOpenKeyEx provides a means for opening additional keys using one of these existing opened keys as a reference point. The syntax for opening a key is relatively simple. It involves identifying any open key with hKey, the key to open in lpSubKey, the security mask in samDesired, and the variable to store the newly opened key's handle in phkResult. The remaining argument, ulOptions, is not used and is always entered as 0&.

The RegQueryValueEx Function

Once you open a key, you need to work with that key to get the information to make your application work properly. RegQueryValueEx provides a means to obtain the value of a specified key's stored values. Some programmers use the RegQueryValue API call instead of the RegQueryValueEx function. RegQueryValue is also a 32-bit call included in the current API set for compatibility with Windows 3.x. This older call will return only the default value belonging to a key. If there are any more values, RegQueryValue provides no way to obtain the different values assigned to a key.

Declare Function RegQueryValueEx Lib "advapi32" _
Alias "RegQueryValueExA" (ByVal hKey As Long, _
ByVal lpValueName As String, lpReserved As Long, _
lpType As Long, ByVal lpData As String, _
lpcbData As Long) As Long

RegQueryValueEx consists of a series of arguments which identify the key and provide a means to obtain one of its values. The key handle, hKey, must be opened with a RegOpenKeyEx call. You need to identify which of a key's values that you need with lpValueName. If there is only one value or you want the default value, leave it blank. You need to indicate the type of value that you are retrieving in lpType with one of the data format constants. REG_SZ represents a string. RegQueryValueEx returns the value in lpData and indentifies its expected size with lpcbData. The remaining argument, lpReserved, is not used and is always entered as 0&.

The RegCloseKey Function

When you are done working with a key in the Registry, you need to release that key. Make sure that you use this function whether or not a call to do something with a key works properly:

Declare Function RegCloseKey Lib "advapi32" _
(ByVal hKey As Long) As Long

Failure to remember to close a key can lead to memory loss and possibly make the system less stable.

Listing 33.6 shows Form Load event code for project 33Proj01.

Listing 33.6 Form Load Event Code for Project 33Proj01

Private Sub Form_Load()
Dim lReturn As Long
Dim hWndSoftware As Long
Dim lpName As String
Dim lpcbName As Long
Dim lpClass As String
Dim lpcbClass As Long
Dim lpData As String
Dim lpcbData As Long
lpcbName = 255
lpName = Space$(lpcbName)
lpcbClass = 255
lpClass = Space$(lpcbClass)
lReturn = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _
"Software\Microsoft\Windows\CurrentVersion\App Paths
\IEXPLORE.EXE", 0&, KEY_ALL_ACCESS, hWndSoftware)
lpcbData = 255
lpData = Space$(lpcbData)
lpcbName = 255
lpName = Space$(lpcbName)
lReturn = RegQueryValueEx(hWndSoftware, "", 0&, _
REG_SZ, lpData, lpcbData)
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
Else
Text1.Text = Left$(lpData, lpcbData - 1)
End If
lReturn = RegQueryValueEx(hWndSoftware, "Path", 0&, _
REG_SZ, lpData, lpcbData)
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
Else
Text2.Text = Left$(lpData, lpcbData - 1)
End If
lReturn = RegCloseKey(hWndSoftware)
End Sub

The Form_Load routine demonstrates the proper usage of the RegOpenValueEx, RegCloseKey, and RegQueryValueEx API functions. This routine is an example of all the concepts about the Registry explained so far. Follow these steps to construct a Visual Basic project that will make this routine work properly.

  1. Open a new project and rename this project as 33Proj01.
  2. Add a new code module and rename it as WINAPI32.
  3. Add the declaration text for RegOpenKeyEx, RegKeyClose, and RegQueryValueEx to the General Declarations section of WINAPI32.
  4. Add the Registry Error, Data Format, Predefined Registry Keys, and Security Mask constants to the General Declarations section of WINAPI32.
  5. Add the gAPIDisplayError routine to WINAPI32.
  6. Rename Form1 as frmAPI.
  7. Add the Form_Load code to the load event of frmAPI.
  8. Place two text boxes on the form as shown in figure 33.3.

Fig. 33.3

A view of what frmAPI should look like at design time.

There are a few things for you to notice about the code contained in the Form_Load event. The RegOpenKeyEx call includes a long string of key names separated by backslashes (\). You do not need to get the key name for each key in between the one that you are working with (EXPLORER.EXE) and the already opened key (HKEY_LOCAL_MACHINE). This is a great code saver which saves you from having to call the RegOpenKeyEx function five additional times to get the handle for the key that you need.

The string values passed to RegOpenkeyEx must be properly formatted for the call to work properly. Notice that lpName, lpClass, and lpData must be filled with spaces using the String function. If you tried this function call without this step, RegOpenKeyEx would not work properly.

The RegCreateKeyEx Function

Like most of the other Registry functions, there are two functions for creating new keys: RegCreateKeyEx and RegCreateKey. The recommended function is RegCreateKeyEx (listing 33.7) because RegCreateKey is in the API only for compatibility with earlier versions.

Listing 33.7 RegCreateKeyEx Declaration and Constants

Declare Function RegCreateKeyEx Lib "advapi32" _
Alias "RegCreateKeyExA" (ByVal hKey As Long, _
ByVal lpSubKey As String, ByVal Reserved As Long, _
ByVal lpClass As String, ByVal dwOptions As Long, _
ByVal samDesired As Long, _
lpSecurityAttributes As SECURITY_ATTRIBUTES, _
phkResult As Long, lpdwDisposition As Long) As Long
'Options
Public Const REG_OPTION_VOLATILE = 0
Public Const REG_OPTION_NON_VOLATILE = 1
Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Variant
bInheritHandle As Long
End Type

RegCreateKeyEx creates a new key in the Registry database. The hKey argument identifies the key that you want to make a new subkey for. lpSubKey contains the name to give to the new key. lpClass provides the class to which this new entry belongs. If the specified class does not exist, this function adds it as a valid entry to the Registry. In the dwOptions argument, then, you decide whether to make this new key permanent or temporary. This entry has two valid constant values, REG_OPTION_VOLATILE or REG_OPTION_NON_VOLATILE. The security mask contains one of the same security mask constants that you used in RegOpenKeyEx. phkResult returns the window handle of the newly created key. lpdwDisposition takes a SECURITY_ATTRIBUTES structure. Simply dim a variable as being part of that type and leave its properties blank. The remaining argument, lpReserved, is not used and is always entered as 0&. The required new constants and new type structure definitions appear along with the declare statement in listing 33.7.

The RegSetValueEx Function

RegSetValue and RegSetValueEx enter a value for a key. RegSetValue is the older version, which is kept in the current API for compatibility reasons. RegSetValueEx has the additional capability of allowing you to create multiple named values under one key, which the older version cannot do.

Declare Function RegSetValueEx Lib "advapi32" _
Alias "RegSetValueExA" (ByVal hKey As Long, _
ByVal lpValueName As String, ByVal Reserved As Long, _
ByVal dwType As Long, ByVal lpData As String, _
ByVal cbData As Long) As Long

RegSetValueEx creates a new value for the key identified by the hKey argument. The name to give this new key's value appears in lpValueName. dwType identifies the data format and takes one of the data format constants such as REG_SZ. This function places the string in the lpData argument and sets its length at the value in cbData. The remaining argument, lpReserved, is not used and is always entered as 0&.

The following steps include the instructions for assembling project 33Proj02. This project demonstrates the proper use and syntax of the registry functions RegCreateKeyEx and RegSetValueEx.

  1. Open a new project and rename this project as 33Proj02.
  2. Add the WINAPI32.BAS file in 33Proj01 to this project.
  3. Add the declaration text for RegSetValueEx and RegCreateKeyEx to the General Declarations section of WINAPI32.
  4. Add the options constants and SECURITY_ATTRIBUTES type to the General Declarations section of WINAPI32.
  5. Rename Form1 as frmLogin
  6. Add two command buttons to frmLogin and rename them cmdOK and cmdCancel.
  7. Add the cmdOK_Click (listing 33.8) and cmdCancel code to the Click event of the corresponding command buttons on frmLogin.
  8. Place two text boxes and two labels on the form as shown in figure 33.4.

Fig. 33.4

A view of what frmLogin should look like at run time.

  1. Change the captions of the labels to Username: and Password:. Make the names of the labels lblUserName and lblPassword.
  2. Change the text of the text boxes to blank. Make the names of the text boxes "txtUserName" and "txtPassword".

Listing 33.8 The cmdOk_Click() and cmdCancel_Click Procedures

Private Sub cmdOk_Click()
Dim lReturn As Long
Dim hlstrUsername As String
Dim hlstrPassword As String
Dim hWndQue As Long
Dim hWndUsername As Long
Dim hWndPassword As Long
Dim lpcbName As Long
Dim lpClass As String
Dim Security As SECURITY_ATTRIBUTES
Dim lpdwDisposition As Long
'Ensure that username and password are not blank
hlstrUsername = Trim$(txtUserName.Text)
hlstrPassword = Trim$(txtPassword.Text)
If hlstrUsername = "" Or hlstrPassword = "" Then
MsgBox "Username and Password are required!"
Unload Me
End
End If
'Create or Open Que Registry entry for local machine
lpClass = Space$(255)
lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, "QUE", 0&, _
lpClass, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, Security, _
hWndQue, lpdwDisposition)
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
End If
'Create or Open Username entry for local machine
lpClass = Space$(255)
lReturn = RegCreateKeyEx(hWndQue, "UserName", 0&, _
lpClass, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, _
Security, hWndUsername, lpdwDisposition)
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
End If
'Save Username in the Registry
lReturn = RegSetValueEx(hWndUsername, "", 0&, REG_SZ, _
hlstrUsername, Len(hlstrUsername))
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
End If
'Create or Open Password entry for local machine
lpClass = Space$(255)
lReturn = RegCreateKeyEx(hWndQue, "Password", 0&, lpClass, _
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, Security, _
hWndPassword, lpdwDisposition)
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
End If
'Save Username in the Registry
lReturn = RegSetValueEx(hWndPassword, "", 0&, _
REG_SZ, hlstrPassword, Len(hlstrPassword))
If lReturn <> ERROR_SUCCESS Then
gAPIDisplayError lReturn
End If
'Close open keys
lReturn = RegCloseKey(hWndQue)
lReturn = RegCloseKey(hWndUsername)
lReturn = RegCloseKey(hWndPassword)
End
End Sub
Private Sub cmdCancel_Click()
End
End Sub

There are a couple of things to notice about the second project. Notice that the code explicitly closes each of the created handles before the application itself exits. This is very important to remember, or you will receive an error when you exit Windows. Notice that the RegSetValueEx statements have blank entries for lpValueName. This ensures that the setting will appear as the default value. If you need to have multiple values for a key, you simply need to specify a name in this argument.

The RegDeleteKey Function

RegDeleteKey provides you with the capability to remove a key from the Registry:

Declare Function RegDeleteKey Lib "advapi32" _
Alias "RegDeleteKeyA" (ByVal hKey As Long, _
ByVal lpSubKey As String) As Long

The hKey argument identifies an open key and the lpSubKey is the exact path of the subkey to remove. This function will not remove a key with subkeys under it. All subkeys must be removed before you can remove the key itself.

The RegDeleteValue Function

RegDeleteValue provides a means for removing value names from a key. The hKey argument identifies the key to remove the value name for, and lpValueName is the name of the value.

Declare Function RegDeleteValue Lib "advapi32" _
Alias "RegDeleteValueA" (ByVal hKey As Long, _
ByVal lpValueName As String) As Long

Exploring .INI File APIs

Everyone has preferences that dictate how they like to work. Some users like toolbars and status bars and some do not. Your applications need to give users the ability to modify the ways in which they use and view what they see on the screen. Windows provides configuration files that allow an application to store a user's preferences between sessions. These files are known as .INI files, and most Windows applications use at least one .INI file to keep track of user preferences and other application-oriented information.

Don't use the Windows WIN.INI file to store application parameters and user preferences. There are limits on how big an .INI file can be. When the WIN.INI file grows too large, Windows stops working properly until this problem is fixed manually. Using the WIN.INI file for your application is a sign of bad programming practice that should be avoided.

.INI File Structure

To understand how to work with .INI files, you first must understand their structure. All .INI files consist of three elements of information: ApplicationName, KeyName, and Value. The ApplicationName (lpApplicationName or lpAppName) identifies the header text that appears between the two brackets "[ ]" in the .INI file. For example, the Application Name "Database" would appear as "[Database]". Each ApplicationName has a number of KeyNames (lpKeyName) with associated Values for each. To retrieve or save an .INI file's settings, you need the ApplicationName and KeyName of the information that you want. Further, you need the name of the .INI file that contains the information.

The WritePrivateProfileString Function

WritePrivateProfileString stores a string of information in the indicated .INI file name under the ApplicationName and KeyName indicated. This routine works for both values and strings. The declaration text for WritePrivateProfileString appears in the code above and needs to appear in the WINAPI32.BAS module

Declare Function WritePrivateProfileString Lib "kernel32" _
Alias "WritePrivateProfileStringA" (ByVal lpApplicationName _
As String, lpKeyName As Any, lpString As Any, ByVal lplFileName _
As String) As Long

The WriteProfileString Function

WriteProfileString works in the same way as WritePrivateProfileString without the naming of the .INI file. In this case, this routine is solely for changing or adding settings to the WIN.INI file. This routine works for both values and strings. The declaration text for WriteProfileString appears in the following code and must appear in the WINAPI32.BAS module:

Declare Function WriteProfileString Lib "kernel32" _
Alias "WriteProfileStringA" (ByVal lpApplicationName As String, _
lpKeyName As Any, lpString As Any) As Long

The GetPrivateProfileInt Function

GetPrivateProfileInt obtains the value stored in the indicated .INI for the specified ApplicationName and KeyName. If there is a value, the GetPrivateProfileInt function returns that value as an integer. The declaration text to place in the General Declarations section of WINAPI32.BAS appears in the following code:

Declare Function GetPrivateProfileInt Lib "kernel32" _
Alias "GetPrivateProfileIntA" (ByVal lpApplicationName As _
String, ByVal lpKeyName As String, ByVal nDefault As Long, _
ByVal lpFileName As String) As Integer

You need not jacket this API call, because it returns a value compatible with Visual Basic.

The GetProfileInt Function

GetProfileInt works in the same way as GetPrivateProfileInt without the naming of the .INI file. In this function, the .INI file is always the WIN.INI file. This is an excellent function for obtaining stored Windows settings in 16-bit applications.

Declare Function GetProfileInt Lib "kernel32" _
Alias "GetProfileIntA" (ByVal lpAppName As String, _
ByVal lpKeyName As String, ByVal nDefault As Long) As Long

Many programmers set the the nDefault parameter to the value of 0. But if 0 is a valid return value, this practice is a mistake. How do you know if the value is 0 or if the setting does not exist yet? The best practice is to use a value that could not exist in that setting such as a negative number if there are no negative numbers used.

The GetPrivateProfileString Function

GetPrivateProfileString obtains the value of the indicated ApplicationName and KeyName for the specified .INI file. This .INI file can be any .INI file including the Windows WIN.INI and SYSTEM.INI files. Since this function returns a fixed-length string, you need to use a jacketed function to avoid problems.

Declare Function GetPrivateProfileString Lib "kernel32" _
Alias "GetPrivateProfileStringA" (ByVal lpApplicationName _
As String, lpKeyName As Any, ByVal lpDefault As String, ByVal _
lpReturnedString As String, ByVal nSize As Long, _
ByVal lpFileName As String) As Long

The following code contains an example of jacketing the GetPrivateProfileString:

Function APIGetPrivateProfileString(lpAppName As String, _
lpKeyName As String, lpDefault As String, nSize As Long, _
lpINIFileName) As String

The GetProfileString Function

GetProfileString works in the same way as GetPrivateProfileString without the naming of the .INI file. Like GetPrivateProfileString, GetProfileString returns the text value in fixed-length string format:

Declare Function GetProfileString Lib "kernel32" _
Alias "GetProfileStringA" (ByVal lpAppName As String, _
lpKeyName As Any, ByVal lpDefault As String, ByVal _
lpReturnedString As String, ByVal nSize As Long) As Long

You must jacket the call with APIGetProfileString, which is shown in the following code:

Function APIGetProfileString(lpAppName As String, _
lpKeyName As String, lpDefault As String, nSize As Long) As String

Going beyond Visual Basic through API Calls

You must always be looking for ways to use the Windows API to help you to go beyond the capabilities of Visual Basic. Visual Basic 4.0 allows you to work with many of the attributes of an application using their properties. There are cases when you need to find out information about a Visual Basic application that is not available with the use of properties. In cases like this, you need to look for API calls to give you the information that you need.

Using DrawIcon and LoadIcon API calls

The InputBox method that ships with Visual Basic allows you to display a generic dialog box that prompts the user for information. Figure 33.5 shows what a normal Visual Basic InputBox looks like. Unfortunately, this dialog box is neither very professional looking nor completely useful. You need a more enhanced version of this dialog box that functions much more like the MsgBox dialog box provided by the MsgBox method.

Fig. 33.5

A normal Visual Basic InputBox.

The DrawIcon and LoadIcon API calls provide a way to produce a form that contains the familiar graphics found on Visual Basic message boxes. This provides a continuity between the Visual Basic message box and the new enhanced inputbox. Notice the similarity between the MsgBox in figure 33.6 and the enhanced InputBox in figure 33.7.

Fig. 33.6

A Visual Basic MsgBox.

Fig. 33.7

An enhanced Visual Basic InputBox.

Listing 33.9 shows the new code for project 33Proj04.

Listing 33.9 New Code fo Project 33Proj04

Declare Function LoadIcon Lib "user32" Alias "LoadIconA" _
(ByVal hInstance As Long, ByVal lpIconName As Any) As Long
Declare Function DrawIcon Lib "user32" (ByVal hDC As Long, _
ByVal X As Long, ByVal Y As Long, ByVal hIcon As Long) As Long
Option Explicit
Global Const MODAL = 1
'Input Box constants
Global Const OK_BUTTON = 0
Global Const OK_CANCEL = 1
Global Const YES_NO = 2
'Returned Text
Global gInputBoxReturn$
' Standard Icon IDs
Public Const IDI_HAND = 32513&
Public Const IDI_QUESTION = 32514&
Public Const IDI_EXCLAMATION = 32515&
Public Const IDI_ASTERISK = 32516&
Sub Main()
Dim ReturnText$
MsgBox "This is a demo of Enhanced InputBox", _
vbCritical, App.Title
ReturnText$ = InputBox("This is a standard Message Box", _
App.Title)
ReturnText$ = EnhancedInputBox(IDI_HAND, OK_CANCEL, _
"Please enter text here", ReturnText$)
ReturnText$ = EnhancedInputBox(IDI_QUESTION, OK_CANCEL, _
"Please enter text here", ReturnText$)
ReturnText$ = EnhancedInputBox(IDI_EXCLAMATION, OK_CANCEL, _
"Please enter text here", ReturnText$)
ReturnText$ = EnhancedInputBox(IDI_ASTERISK, OK_CANCEL, _
"Please enter text here", ReturnText$)
End
End Sub
Sub CenterDialogPosition(WNdName As Form)
WNdName.Top = (Screen.Height - WNdName.Height) \ 2
WNdName.Left = (Screen.Width - WNdName.Width) \ 2
End Sub
Function EnhancedInputBox(Icon&, Button%, Label$, Default$) _
As String
Dim IconhWnd%, ReturnCode%, LeftPosition%, CommandWidth%, _
ReturnText$
Dim TextLength%, LineLength%, LabelLines%, CommandTop%
Load frmInputBox
frmInputBox.Caption = App.Title
'Display the InputBox icon in picScreenIcon.
IconhWnd% = LoadIcon(0, Icon&)
ReturnCode% = DrawIcon(frmInputBox!picScreenIcon.hDC, 0, 0, _
IconhWnd%)
frmInputBox!picScreenIcon.Picture = _
frmInputBox!picScreenIcon.Image
'Display Proper buttons
Load frmInputBox!cmdButton(1)
Select Case Button%
Case OK_BUTTON
EnhancedInputBox$ = ""
Exit Function
Case OK_CANCEL
frmInputBox!cmdButton(0).Caption = "OK"
frmInputBox!cmdButton(0).Default = True
frmInputBox!cmdButton(1).Caption = "Cancel"
frmInputBox!cmdButton(1).Cancel = True
Case YES_NO
frmInputBox!cmdButton(0).Caption = "YES"
frmInputBox!cmdButton(0).Default = True
frmInputBox!cmdButton(1).Caption = "NO"
frmInputBox!cmdButton(1).Cancel = True
End Select
'Position the command button in center of the form at bottom
CommandWidth% = frmInputBox!cmdButton(0).Width
LeftPosition% = ((frmInputBox.ScaleWidth \ 2) - _
(CommandWidth% + 4))
frmInputBox!cmdButton(0).Left = LeftPosition%
frmInputBox!cmdButton(1).Left = LeftPosition% + _
CommandWidth% + 8
frmInputBox!cmdButton(1).Visible = True
'Check length of message and resize form and command buttons
' accordingly
LineLength% = frmInputBox.TextWidth("mmmmmmmmmmmmmmmmmmmm")
TextLength% = frmInputBox.TextWidth(Label$)
If TextLength% > LineLength% Then
LabelLines% = Int(TextLength% / LineLength%) + 1
frmInputBox!lblInstructions.Height = _
(frmInputBox.TextHeight("X")
* LabelLines%)
frmInputBox!lblInstructions.Top = _
frmInputBox!picScreenIcon.Top +
((frmInputBox!picScreenIcon.Height - _
frmInputBox!lblInstructions.Height) / 2)
frmInputBox!txtInputText.Top = _
frmInputBox!lblInstructions.Top +
frmInputBox!lblInstructions.Height + 8
CommandTop% = frmInputBox!txtInputText.Top + _
frmInputBox!txtInputText.Height + 16
frmInputBox!cmdButton(0).Top = CommandTop%
frmInputBox!cmdButton(1).Top = CommandTop%
frmInputBox.Height = Screen.TwipsPerPixelY * (CommandTop% + _
frmInputBox!cmdButton(0).Height + 40)
End If
'Show Label
frmInputBox!lblInstructions.Caption = Label$
'Show Text box if indicated
frmInputBox!txtInputText.Text = Default$
frmInputBox.Show MODAL
Unload frmInputBox
'Sets the return text
ReturnText$ = gInputBoxReturn$
Select Case ReturnText$
Case "CANCEL"
ReturnText$ = ""
Case "NO"
ReturnText$ = ""
Case Else
EnhancedInputBox$ = ReturnText$
End Select
End Function

Follow these steps to construct project 33Proj04:

  1. Create a new project and rename it 33Proj03.
  2. Add a new module and insert the code for this module from the code in Listing 33.7.
  3. Rename Form1 as InputBox at the DOS level and frmInputBox at the Visual Basic level.
  4. Change the Appearance property of frmInputBox to 1 - 3D.
  5. Add a picture box to frmInputBox and rename it picScreenIcon.
  6. Make picScreenIcon's AutoRedraw property True.
  7. Create a command button and rename it cmdButton.
  8. Change cmdButton's Index property to 0.
  9. Draw a text box and rename it txtInputText.
  10. Draw a label and rename it lblInstructions.
  11. Change lblInstructions' BackStyle property to Transparent.
  12. Add the code in listing 33.10 to frmInputBox.
  13. Change the Startup form to Sub Main.

Listing 33.10 cmdButton Code for Project 33Proj04

Private Sub cmdButton_Click(Index As Integer)
Select Case cmdButton(Index%).Caption
Case "OK", "Yes"
gInputBoxReturn$ = Trim$(txtInputText.Text)
Case "Cancel"
gInputBoxReturn$ = "CANCEL"
Case "No"
gInputBoxReturn$ = "NO"
End Select
frmInputBox.Hide
End Sub
Private Sub Form_Load()
CenterDialogPosition Me
End Sub

Module 1's General Declarations section supports the constants and variables used in this example. These constants and variables must be included or Project 33Proj04 will not work properly. MODAL is from the CONSTANT.TXT file. The InputBox constants identify which buttons to display on the InputBox. gInputBoxReturn$ contains the text returned by the user. Each of the standard icon IDs gives a text identification key to show which icon to display.

This project begins with the code found in Sub Main. This subroutine demonstrates all of the different message boxes that are used in Visual Basic. Notice the use of the new type of Visual Basic constant, vbCritical, in the MsgBox statement. The most import thing to see is that there is no corresponding constant declaration for vbCritical. This constant is already a part of Visual Basic.

You need to have your enhanced dialog box centered on the screen. The preceding code centers the enhanced InputBox on the screen. Most projects have main forms and need code to center any dialog boxes over that main form. However, this project has no main form, so you don't have to do this.

EnhancedInputBox displays the enhanced input box with the appropriate icon. The LoadIcon API call loads the Window handle for the appropriate icon. DrawIcon uses this Window handle to draw that icon in the picture box picScreenIcon. The process is very simple and will work for almost any icon graphic.

The remainder of the code formats and displays frmInputBox. This routine checks the size of the text to place in the label and then formats and positions the text box and label appropriately. The argument Default$ identifies whether to place default text in the text box. Button% tells this routine which labels to place on the command button: Yes No, Ok Cancel, or only OK. Based on the text returned in gInputBoxReturn$, this routine returns the text or nothing if the user entered nothing.

EnhancedInputBox also includes a very simple method for returning the user-entered text. You use a MODAL show method to display frmInputBox. This means that you need to return the results of user actions with text. If the user chooses "Yes" or "OK", this is no problem. You simply need to return the entered text in the global variable gInputBoxReturn$. When the user presses "NO" or "Cancel", you need to return text that indicates this choice. This normally means that the user does not want to enter this choice of text. You solve this problem by returning "NO" or "CANCEL".

Using Window APIs

A whole family of API calls work with the different elements of Windows applications, which are all called windows.

Windows thinks of everything on the screen as a window. An icon on the screen is not surprisingly called one window. Possibly more surprisingly, the label below the icon is also known as a window. So when you look at an icon and a label, you are really looking at two windows.

The GetParent Function

This API call provides a means to obtain the window handle of the parent of the indicated window. This function is especially useful for obtaining the handle of the MDI parent of an MDI child form. You use this function when you need to learn something about the parent of window in another application.

Declare Function GetParent Lib "user32" _
Alias "GetParent" (ByVal hWnd As Long) As Long

The GetWindow Function

This function provides a means to cycle through the different running applications and any of their children. This is a very useful function for finding the window handle of a window which is not readily apparent to the current application. For example, you might need to discover whether Mail is currently running in an application that works with Mail. The GetWindow API call (listing 33.11) is very useful for discovering whether that application is running.

Listing 33.11 GetWindow Declaration and Constants

Declare Function GetWindow Lib "user32" (ByVal hWnd As Long _
, ByVal wCmd As Long) As Long
' GetWindow() Constants
Public Const GWW_HINSTANCE = (-6)
Public Const GW_HWNDFIRST = 0
Public Const GW_HWNDLAST = 1
Public Const GW_HWNDNEXT = 2
Public Const GW_HWNDPREV = 3
Public Const GW_OWNER = 4
Public Const GW_CHILD = 5
Public Const SW_SHOWNOACTIVATE = 4

The code in listing 33.11 shows what is necessary for making the GetWindow API call work properly in your applications. All of this code belongs in the WINAPI32.BAS file. This API call functions differently based on the referenced constant. There is a list of possible constants within this code.

The GetWindowText and GetWindowTextLength Functions

The GetWindowText and GetWindowTextLength API calls provide a means to obtain the length and text of the indicated window. This function is especially useful for converting the text provided by GetWindowText into a Visual Basic string.

Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, _
ByVal aint As Long) As Long
Declare Function GetWindowTextLength Lib "user32" _
Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long

The SetParent Function

The SetParent function offers you the opportunity to move controls from one container object to another at run time. SetParent works in many situations, making it a very valuable function. Using this function, you can move any control from different picture boxes, frames, or forms. You can create highly customizable applications that change appearance based on user input.

Declare Function SetParent Lib "user32" Alias "SetParent" _
(ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long

There are some circumstances in which you need to change the appearance of a form "on the fly" at run time. One way to quickly change a form's appearance is to display the controls on different frames. There will always be controls that are common to both frames. The SetParent function permits you to move buttons such as the OK and Cancel buttons from one frame to another.

There are a couple of things to notice about Project 33.03. First, notice how you use the hWnd property of the command buttons and frames to make this project work properly. Most Visual Basic controls have window handle properties. You use this property when you need to move them or utilize any function that needs its window handle to function properly. Second, note the use of the Unload method before the use of End. This is a cleaner way of closing down an application. You need to always remember to close any open forms with the Unload method prior to using the End method.

Project 33Proj03 demonstrates the use of the GetParent API function. To create this project, use the following steps:

  1. Open new project and rename this project as 33Proj03.
  2. Add a new module.
  3. Add the declaration text for SetParent to module Module1.
  4. Draw two frames on Form1 with Frame1 directly over Frame2 as shown in figure 33.8.

Fig. 33.8

A view of what Form1 should look like at run time.

  1. Place two command buttons on Frame1 and change the caption of Command1 to "Exit" and Command2 to "SetParent".
  2. Add the code shown in listing 33.12 to Form1.

Listing 33.12 The Command1_Click and Command2_Click Procedures

Private Sub Command1_Click()
Unload Me
End
End Sub
Private Sub Command2_Click()
Dim rc&
If Frame1.Visible Then
Frame2.Visible = True
Frame1.Visible = False
rc& = SetParent(Command1.hWnd, Frame2.hWnd)
rc& = SetParent(Command2.hWnd, Frame2.hWnd)
Else
Frame1.Visible = True
Frame2.Visible = False
rc& = SetParent(Command1.hWnd, Frame1.hWnd)
rc& = SetParent(Command2.hWnd, Frame1.hWnd)
End If
End Sub

Using File Access APIs

Visual Basic applications are still tied to Windows' underlying operating system. Applications still need to be aware of the locations of required files. You need to be aware that users will not always want to place your applications in the same directory or drive. This means that you need to provide methods for adjusting to these changes. Further, you must be able to perform underlying operating system commands. Because you are now using Windows 95, your applications must work with that underlying operating system. Several Windows APIs make working with files easier.

The GetSystemDirectory Function

The GetSystemDirectory function obtains the path of the system directory on the current Windows computer. The system directory contains the specialized Windows files that allow Windows to run properly including the DLL, VBX, and DRV files. You can use this function to discover the path of any of the files that are in this directory.

Declare Function GetSystemDirectory Lib "kernel32" _
Alias "GetSystemDirectoryA" (ByVal lpBuffer As String, _
ByVal nSize As Long) As Long

Using GetSystemDirectory is very simple. Add the declaration text found to the General Declarations section of WINAPI32.BAS. You need to provide a variable in which to place the return string. This return string must be formatted as a fixed-length string. Additionally, you need an integer variable to store the size of the returned string. Because GetSystemDirectory returns a fixed-length string, you need a means to convert this string to a Visual Basic string.

The GetWindowsDirectory Routine

The GetWindowsDirectory routine provides the full path of the current computer's Windows directory. The Windows directory contains the main Windows files that involve the basic inner working of Windows. Each of the executable and Help files of all the basic applications that ship with Windows are here. These files include those for WordPad, Solitaire, Paint, and so on. In addition, there are the .INI configuration files for all of the applications on the current machine including the SYSTEM.INI and WIN.INI files.

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

There are a couple of secrets to making the GetWindowsDirectory function work properly. The declaration text belongs in the WINAPI.BAS file. You need to use the jacketed form of this function known as APIGetWindowsDirectory. Together, these two things make working with this function an easy process.

From Here...

For more information on related topics, see the following chapters:


© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.