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.
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.
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.
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.
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.
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.
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.
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.
A view of values of the Settings subkey.
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.
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
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
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
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
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
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.
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&.
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.
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&.
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:
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
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.
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.
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
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.
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.
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.
A view of what frmLogin should look like at run time.
Listing 33.8 The cmdOk_Click() and cmdCancel_Click Procedures
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.
RegDeleteKey provides you with the capability to remove a key from the Registry:
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.
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.
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.
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.
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
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:
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:
You need not jacket this API call, because it returns a value compatible with Visual Basic.
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.
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.
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.
The following code contains an example of jacketing the GetPrivateProfileString:
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:
You must jacket the call with APIGetProfileString, which is shown in the following code:
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.
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.
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.
A Visual Basic MsgBox.
An enhanced Visual Basic InputBox.
Listing 33.9 shows the new code for project 33Proj04.
Listing 33.9 New Code fo Project 33Proj04
Follow these steps to construct project 33Proj04:
Listing 33.10 cmdButton Code for Project 33Proj04
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".
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.
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.
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
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.
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.
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:
A view of what Form1 should look like at run time.
Listing 33.12 The Command1_Click and Command2_Click Procedures
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.
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.
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.
For more information on related topics, see the following chapters:
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.