There is no single way to solve a technical programming problem. Many factors go into choosing a programming solution for a problem. Visual Basic properties, methods, and events define what you can and cannot do. You must find ways to work around bugs when they get in the way of providing a solution. The Windows environment itself presents you with challenges of capabilities and limitations.
When writing code to solve a client’s problem, you usually must do so as quickly as possible. Foreknowledge of efficient coding techniques and common technical pitfalls can make writing code a faster process. Being aware of potential resources that provide such knowledge can prepare you to develop such techniques and avoid such pitfalls before difficulties even arise.
In this chapter, you learn how to do the following:
The MSBASIC Forum on CompuServe Information Services (CIS)
The CompuServe online service provides a wonderful resource for finding solutions to programming problems. Join the MSBASIC forum and talk to other Visual Basic programmers who might have solutions to your current problems. You can obtain a membership copy from most computer software stores, or call 1-800-848-8990 to order a copy directly.
Minimizing CompuServe Charges
CompuServe charges can add up as you increasingly use this valuable resource. One way to reduce your online charges is to use an auto-navigator. An auto-navigator reduces your online time by first obtaining the headers of all the new messages; you can then choose which ones you want to read. Then the auto-navigator retrieves your selected messages to enable you to read them offline rather than online—and thus save money. An example of such an auto-navigator is Navcis, which is available in the DVORAK forum, but many other good ones are available. You should try some to discover one that best fits your needs.
Visual Basic supports the two most common methods for storing application information: .INI files and the Windows Registry. Support for .INI files exists for compatibility with earlier versions of Windows and Visual Basic. The Windows Registry is the current accepted location for placing application information.
Every application should have its own .INI file. This file provides a way to save settings between application sessions. Many Windows applications still store settings in the WIN.INI Windows configuration file. This creates problems when the WIN.INI file grows beyond the 64K limit. Under these circumstances, Windows no longer runs on that machine until someone reduces the .INI file’s size. Remember to use an application .INI file rather than WIN.INI.
Visual Basic provides no internal functions for creating and maintaining application .INI files. To work with .INI files, you must work directly with the Windows API. Three basic API functions allow access to application .INI files: GetPrivateProfileString, GetPrivateProfileInt, and WritePrivateProfileString. GetPrivateProfileString provides a method for obtaining text or values from an indicated .INI file. The API calls return these settings as a string. GetPrivateProfileInt returns values stored in the indicated .INI file. WritePrivateProfileString saves indicated strings to the indicated .INI file. Chapter 33, “Accessing the Windows API,” contains the declarations and syntax for using these routines.
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. OLE applications like Microsoft Office broke this trend by using the Registry database, but such applications are the exception rather than the rule. Any entries in the old WIN.INI and SYSTEM.INI files are there simply for compatibility reasons. The Registry keeps information in binary files. An application uses the Registry API functions to access data in the Registry.
Visual Basic provides no internal functions for creating and maintaining the Windows Registry. To work with the Windows Registry, you must work directly with the Windows 32 API. Seven API functions allow access to the Windows Registry: RegOpenKeyEx, RegQueryValueEx, RegCloseKey, RegCreateKeyEx, RegSetValueEx, RegDeleteKey, and RegDeleteValue. RegOpenKeyEx enables you to open a Registry key so that you can work with its contents. Use RegQueryValueEx to obtain the settings that belong to a key in the Registry. RegCreateKeyEx creates new Registry keys and RegSetValueEx assigns settings to a key. RegDeleteKey removes keys from the Registry, and RegDeleteValue removes a value from a key. Chapter 33 contains the declarations and syntax for using these routines.
An application’s startup process gives the user a first impression of the entire application. If you design an application that has a long startup, you give the user the impression that your entire application is slow. Unfortunately, at program startup, it is important for an application must make calculations and check settings. Therefore, you are faced with a challenge: to design applications that don’t seem slow yet still handle configuration issues at startup. You can use several techniques to avoid this dilemma.
The following two lines of code define the constants LINE_FEED and TAB_STOP:
At least these variables seem to be formatted as constants. Constants normally appear in the General Declarations sections of modules and rarely forms. Unfortunately, you cannot define constants by using functions such as Chr$. The code has to set these variables at run time. These variables are not constants, therefore, but simply behave as constants because you define them only once in the code. For this reason, such variables are called pseudo constants. By using such variables, you need only remember a pseudo constant’s name instead of having to remember, for example, the ASCII code for the linefeed or tab character.
These pseudo constants allow for carefully formatted message box text throughout your projects. You can now space message text within a text box by placing a space between logical phrases. For example, you can place “Warning, Warning!” at the top of a message and then one line further down “You are about to delete data permanently” by inserting two linefeed constant statements in the string assigned to the message box.
Visual Basic’s Project tab appears on the Options dialog box (which you access by choosing Tools, Options). This dialog box provides a method for identifying the startup form. Along with the names of each of the forms in the current project, you find the strangely out-of-place Sub Main. But you have no form named Sub Main, and when you try to add this subroutine to any of your forms, it does not work properly. The solution is simple: create a new module and place in it a subroutine named Sub Main. Figure 32.1 shows the Options dialog box in which the Sub Main choice appears in the Project tab.
The Options dialog box with Startup Form set to Sub Main
The project 32Proj01 demonstrates a possible use for Sub Main. To function properly, Visual Basic applications still must be aware of DOS paths. Users might not want to place their copy of your applications in the same directories that you intended. How do you discover what path that the user wants to use? The file 32Proj01 on the companion CD includes a rudimentary method for letting the user indicate the path. Figure 32.2 displays the form that you need to construct. Use this figure as a model as you proceed through the following steps:
The frmDataLocation form at design time.
Sub Main contains this project’s startup code (see listing 32.1). First this routine loads frmDataLocation and displays the path stored in the Registry or the current application path in the Path text box. Next the code displays frmDataLocation modally and waits for the user to exit from that form. The path placed in the text box on frmDataLocation provides the path for Sub Main to save into the Registry if the user presses the OK command button.
Listing 32.1 The Sub Main Routine
Listing 32.2 shows the GetDatabasePath function. Notice how you use App.Path to provide the database’s default path. The reference to App.Path appears to path to the current directory if the Registry doesn’t store a database path. Chapter 33, “Accessing the Windows API” features a complete discussion of the API calls for the Registry.
Listing 32.2 The GetDatabasePath Function
SaveDatabasePath (listing 32.3) provides a way to save the database path to the Registry. Chapter 34 includes a complete discussion of the Registry API database calls.
Listing 32.3 The SaveDatabasePath Function
Notice that each of the command-button click events (listing 32.4) hides frmDataLocation instead of unloading it. This is important because you need to access the contents of txtPath in the Sub Main routine. Because you display frmDataLocation modally, none of the code that follows frmDataLocation.Show vbModal processes until the form unloads or becomes invisible. Using the Hide method makes frmDataLocation invisible, thus enabling the processing of the code that follows the Show method in Sub Main.
Listing 32.4 The cmdOk_Click and cmdCancel_Click Events
The Pressed check box on frmDataLocation provides an excellent, easy way to determine whether the user pressed the Cancel or OK button. Another way to tell the Sub Main routine which button the user pressed is to use a global variable.
Several global variables are necessary to operate this project’s code. Listing 32.5 shows this project’s global variables.
Listing 32.5 The Global Variables
Before proceeding any further, you must declare the API calls. Create a new module and rename it WINAPI32.BAS. Copy the code in listing 32.5 into the General Declarations section of this new module. The API declarations and constants are globally available to the entire application and are necessary to make the code in the remainder of this chapter work. Listing 32.6 shows the API call declarations.
Listing 32.6 Declarations of the API Calls
Make sure that you add the gAPIDisplayError subroutine to WINAPI32. This function provides error checking for the Registry calls. Chapter 34 discusses the API calls and this error-checking routine.
Notice the use of the Visual Basic keyword Public rather than the more familiar Global. This is a major change that Visual Basic 4.0 introduces. Fortunately, Visual Basic 4.0 is backward-compatible, so you need not immediately change to using Public rather than Global. Both keywords behave exactly the same and work properly in a Visual Basic 4.0 project.
To provide some continuity among the different screens, applications need to present a consistent title. Sub Main is an excellent location for setting app.title. By using the Title property of the Visual Basic app object, you can specify that the same message appear for each screen. This property also makes changing the title easy, because you need only change the title in one place to affect each screen. If you had to insert the title text everywhere that you want it to appear, the process of changing the title would be harder and possibly less accurate.
The following code line is the definition of the app.Title object:
Visual Basic provides a place where you can easily set the name of an application’s Help file. Simply set your choice on the Project Options dialog box by choosing the Project tab of the Options dialog box as shown in figure 32.3.
The Project Options dialog box showing the Help File setting.
Unfortunately, if you hard-code the path in this dialog box, the user cannot install your application in another directory. If the user chooses another directory, the Help system does not work properly. Fortunately, an alternative to this unacceptable solution exists: Simply use the app object’s HelpFile property to set the path at run time in the Sub Main subroutine, as follows:
To create the illusion of a “fast” application, use a splash screen. A splash screen is just a form with a picture showing on screen something about the application—perhaps a company graphic, descriptive text, or even an entertaining picture—to keep the user’s attention while processing takes place in the background. Figure 32.4 shows an example of a splash screen.
The frmSplash displayed at run time.
To create the splash screen shown in figure 32.4, follow these steps:
You often must choose between using a Show or Load method. Show triggers the form’s load event and makes the form visible. Load triggers the same event but leaves the form invisible. To display the form immediately on the screen, simply use Load in Sub Main and change the visible property to True in the form’s Load event.
Chapter 31, “Advanced Form Techniques,” contains a full discussion of multiple-document interface (MDI) forms.
Toolbars and menu choices that trigger the exact same code present you with another problem: how to keep their shared code current. The obvious choice is to create a common subroutine that both the menu choice and the toolbar can call. For the sake of simplicity, you should place these common routines in a separate module. By using separate modules, you can easily port code to other applications simply by copying the module from one project to another.
Users perceive customizable applications as user-friendly and enjoy working with them. But consider carefully which parts of your application to make customizable. Features like saving column widths in grids and saving the last object opened are excellent choices for customization. The user sometimes does not want to save the settings from one session to another, so you must enable users turn this feature on and off. For example, once satisfied with the column widths in grids, a user does not want to overwrite the settings immediately. In these circumstances, the user need only turn on the feature that saves the settings, save the grid’s column width, and then simply turn off the feature that saves settings.
The shell’s Option menu does not enable the user to save customization choices in an application’s executable file. To save settings, you must use the Windows Registry. Place the GetSavedSettings function in MAIN.BAS. This routine checks for any settings in the Windows Registry and creates the defaults if none exist yet. Before any of this code can work, you must add to the project 32Proj01 the 32-bit API code, which appears in the WINAPI32.BAS module.
Unlike the previous projects in this chapter, saving the different settings associated with forms requires many calls to the Registry. You could make the calls exactly as shown in 32ProjJ01, but the code would be longer and less understandable. Instead, use the gSetValue subroutine to make the Registry calls for you.
gSetValue (see listing 32.7) places a setting in the Registry according to the settings of its arguments: sKey, sKeyValue, and sNewValue. sKey identifies the name of the key to set a value and sKeyValue identifies the name of the value. sNewValue contains the string or number to place under the identified value.
Listing 32.7 The gSetValue Subroutine
gGetValue (listing 32.8) obtains a setting in the Registry according to the settings of the function’s arguments: sKey and sKeyValue. sKey identifies the name of the key to set a value, and skeyValue identifies the value’s name. The gGetValue function returns the contents of the indicated key’s value.
Listing 32.8 The gGetValue Function
The GetSavedSettings subroutine accommodates all the settings over which you want to give the user control. This routine currently specifies whether the application should save any settings that change.
You want to create a way for the user to turn the Save Settings On Exit option on and off. To do so, simply create a new menu choice named mnuOptions and labeled “&Options,” with the submenu mnuOptionsSaveSettings with the label “Save Settings On Exit.”
Listing 32.9 contains the code to place in the mnuOptionsSaveSettings_Click event. The code ensures that the user can choose whether to enable or disable the Save Settings On Exit option. To discover the user’s choice, note whether a check mark appears next to the menu choice; if the check mark appears, the user enabled the Save Settings On Exit option.
Listing 32.9 The gGetSettings Function
Where a form appears is at least as important as how it appears. Positioning is a frequently overlooked part of Visual Basic programming; many programmers simply let their forms appear in their default design-time positions. The major problem with this approach is that different users have screens with different resolution sizes. A form that appears in the lower-right corner of an 800-by-600 screen disappears completely off a 640-by-480 screen.
This section introduces some routines that help you fine-tune the positioning of your forms. For the sake of simplicity, place all the position routines in a new module named POSITION.BAS.
Higher-resolution screens make programming in Visual Basic easier. They provide more room on which you can display different elements of Visual Basic without creating a confusing screen. Unfortunately, not every user has the luxury of an 800-by-600 resolution screen. Be careful to design screens that adjust their size based on the user’s screen resolution. Listing 32.10 shows the SetMainFrmPosition subroutine, which sizes and positions frmMain on the screen.
Listing 32.10 The SetMainFrmPosition Subroutine
The gSetMainFormPosition routine changes the appearance of frmMain based on the size of the screen. Therefore, frmMain appears maximized on 640-by-480 resolution screens and centered in 800-by-600 screens.
These choices might be good candidates for a user setting that can save the position of the form so that the next time that the user loads this program, the form appears in exactly the same place. gSaveMainFormPosition (listing 32.11) is a subroutine that gives the user this control.
Listing 32.11 The gSaveMainFormPosition Subroutine
After users start working with your application, they want to be able to customize its behavior. One user might prefer the main form to appear maximized on a 640-by-480 laptop screen. Another might want to display it in the lower-right corner of a 1,024-by-768 desktop machine. gSaveMainFormPosition puts the left and top coordinates and height and width of the main form into the Registry. This subroutine makes these settings available to gSetMainFormPosition when the main form loads.
Dialog boxes include any information screens that require a response from the user before the program can continue. Figure 32.5 shows an example of a type of dialog box.
A dialog box can be positioned in the center of the screen.
You can use the gSetDialogPosition subroutine to position all dialog boxes on the screen. The code for gSetDialogPosition includes all of that shown in listing 32.12, which shows the code for gCenterDialogPosition. gCenterDialogPosition centers the indicated dialog box over the MDI form. For this demonstration, assume that this form’s name is frmMain. However, frmMain could also be an argument of this routine. If the main form is smaller than the dialog box, gCenterDialogPosition centers the dialog box on the screen.
Listing 32.12 The gCenterDialogPosition Subroutine; the Code for gSetDialogPosition Is Similar
There is no right or wrong method for displaying MDI children within an MDI parent. Every application is different, so you must make any positioning choices based on the user’s needs and the type of application that you are developing. Figure 32.6 shows where SetChildWindowPosition subroutine places the MDI child. This location is an excellent choice for a default. One good enhancement to this routine is to place the MDI child in the same position as the last time that the user used the form. Listing 32.13 shows the SetChildWindowPosition subroutine.
An MDI child positioned on the screen.
Listing 32.13 The SetChildWindowPosition Subroutine
The gSetChildWindow routine is quite similar to the gSetMainFormPosition routine. The only difference lies in the fact that gSetChildWindow requires height and width properties only for sizeable MDI children. This routine checks whether those settings are necessary and, if so, obtains them. Every form differs, so you give this routine a tag name to differentiate it from other child windows. This tag name is normally the form’s tag property.
The gSaveChildWindowPosition routine (listing 32.14) performs the exact same function as gSaveMainFormPosition. This routine saves the height, width, left position, and top position of the indicated form. Notice that the giSaveSettings variable indicates whether to save these settings. This variable ensures that the settings do not change after the user sets them to his or her desired choices.
Listing 32.14 The gSaveChildWindowPosition Routine
You can fit all these routines into one project to demonstrate how these different routines work. To assemble this project, follow these steps:
In Chapter 30, “Advanced Control Techniques,” you added the code for controlling whether the toolbar and status bars appear on the main form. See Chapter 30’s discussion of how you do this.
In their simplest form, Copy and Paste operations and Cut and Paste operations take highlighted text from one screen, place the text in the Clipboard, and then paste it into a new, compatible location.
The difference between a Copy and Paste operation and a Cut and Paste operation is that the former removes text from the original control. Visual Basic controls support Cut, Copy, and Paste operations without the code, but users like to see the familiar menu and icon choices. To provide these choices, you must add them to frmMain as follows:
The following code shows the mnuEditCopy_Click event:
The mnuEditCopy_Click event calls the InitiateCopy subroutine and identifies the control by using Screen.ActiveControl. The Screen object with the related property ActiveControl provides a great way to create a generic routine that works for any form in this project.
The InitiateCopy subroutine checks which type of control is currently active on the screen:
Notice that some of the controls are commented out. This is because these controls ship with Sheridan Software’s DataWidgets and do not work unless you add those VBXs to the project. By examining these references, you can see that you can add any type of control that holds text by referencing the control type with a new ElseIf statement. Notice how the routine uses the SelText property rather than the Text property to obtain only the highlighted text. Also important is the use of the Clipboard object and the related SetText to place the text on the Clipboard.
The mnuEditCut_Click event references the InitiateCut subroutine in the same way that mnuEditCopy references InitiateCopy:
InitiateCut identifies the control and its type in exactly the same way that InitiateCopy does:
Unlike InitiateCopy, InitiateCut uses the CutText function to remove the highlighted text from that control:
The mnuEditPaste_Click event calls the InitiatePaste subroutine in a fashion similiar to that used by mnuEditCut_Click and mnuEditCopy_Click:
The InitiatePaste subroutine uses the control and control type to identify where to paste the Clipboard text:
Notice the use of the SelText property to identify the text to replace with the new text. If text is highlighted, this routine replaces it. If no text is highlighted, this routine simply pastes the text in the current location on the control. The routine identifies the actual text to paste with the Clipboard object and the related GetText property.
Look closely at the code lines that reference the Clipboard object. Each call includes the word vbCFTEXT. This ensures that the format is text. If your program needs to copy and paste or cut and paste something other than text, you need only choose the appropriate argument to identify it.
Chapter 30, “Advanced Control Techniques,” includes the code for generating the toolbar and status bar. This discussion includes all the information that you need to know to make toolbars that look like those of Microsoft Office applications.
A good icon can be the difference between an application making sense or confusing the user. Icons are pictures that represent concepts. If the concepts are unclear to the user, they are less than useless. The user must readily recognize an icon’s purpose. In a personal information manager, for example, an icon of a sailboat might have a meaning related to recreation. In another program, such as an inventory system for a boat dealership, the same icon might have another perfectly valid use.
In the shell, you use two types of icons. First are those that ship with Visual Basic. The Programmer’s Guide that ships with Visual Basic 4.0 includes a list of these icons. They use the .ICO format and appear when the user minimizes a form. Some programmers place such icons on the SSCommand button along with text. However, these icons tend to consume too much space on the screen. The second type of icons are bitmap icons such as those on the frmgraphics form. These icons are smaller than the first type, and look similar to the toolbars that you see in Microsoft products.
Where can you find icons to use in your applications? The primary source of .ICO format icons is the library of icons that ships with Visual Basic 4.0. Another source of these types of icons is on CompuServe, in the WINFUN forum in Library 7. This source offers hundreds of icons that might meet your needs.
The primary source of bitmap icons lies hidden in the Windows DLL files. These icons are those that other applications use. These applications use the DLL files to display various kinds of information. The best method for finding and accessing these bitmaps is to use the AppStudio utility that ships with Microsoft Visual C++ 1.5.
Microsoft Visual C++ 1.5 ships with a key to the DLL files already installed on your machine. Inside the DLL files that you find listed in AppStudio are a wealth of bitmaps and icons that you can use as the basis for your own icons in your own applications. For copyright reasons, you should not use someone else’s icons without permission, but these icons can give you ideas about how to make your own.
If you don’t own Visual C++ 1.5, the Paint application that ships with Microsoft Windows can serve as an excellent method for editing and creating useful bitmap icons. Try opening CUSTOM1.BMP, which you can find on this book’s companion CD. This file contains a series of commonly used graphics that you can use as the basis for your own icons. Figure 32.7 shows a picture of Microsoft Paint with the CUSTOM1.BMP open.
Microsoft Paint editing CUSTOM1.BMP.
Whenever you design an application in Visual Basic, you eventually must install the application on a user’s machine. Many of the features that you add to your applications require the addition of certain files besides the compiled executable. These files include .OCX files, OLE support files, and the run-time support files that enable your application to run properly on another machine.
Visual Basic ships with a newer version of Setup Wizard that offers several fine enhancements that make the wizard easier to use. Figure 32.8 shows Setup Wizard’s enhanced main screen. The wizard now offers an integrated compression utility that does not have to exit to a DOS window to work properly. Setup Wizard also indicates how much space your application will use when installed on a new system.
Microsoft Setup Wizard's main screen.
Setup Wizard does not enable you to minimize the compression part of the setup process so that you can do something else while the wizard is compressing.
The new Visual Basic Setup Wizard keeps track of all the files that are part of Visual Basic 4.0. Unfortunately, ensuring that your application will include any files that you access through API calls or other means (such as database files or any associated graphics files) is more difficult. As you look through the files, carefully ensure that such files appear as part of your setup routine.
At the heart of all your applications is the code that makes them work and do what you want. All the advances in technology have not relieved programmers of the need to write code. The more techniques that you have at your disposal, the easier the process becomes for increasing your arsenal of code. You can store information from application session to session in configuration files found in the Registry for Window 95 and NT and .INI files in Windows 3.x. You optimize the startup process by using splash screens and storing necessary information up front in global variables and the application object. Finally, you can provide users with more control over the behavior of an application by enabling them to set the positions of forms and then save those positions for later sessions. All these techniques make your applications more useful.
To learn more about related topics, see the following chapters:
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.