Visual Basic Expert SolutionsChapter 3Using the Windows APIBy S. Rama Ramachandran |
Visual Basic 4 incorporates many new features over previous versions, chief among them being the use of OLE custom controls, the capability to create reusable objects, 32-bit support, the use of resource files, and Data Access Objects (DAO). However, as an experienced programmer, you will frequently need to go beyond the power that Visual Basic provides and tap into the power of Windows API functions. Even if you are a beginning programmer, you will find out how the Windows API can help you accomplish tasks faster and more efficiently. And Visual Basic provides the ease of using virtually all of the Windows API functions. As you will see in this chapter, the Window API can effectively manage Windows (or forms), handle menus creatively, and create dazzling special effects with the Windows API graphics routines.
Many programmers balk at using the Windows API. The main fear, and a well-justified one, stems from encountering the dreaded GPF (General Protection Fault) from Windows because of improper use of API calls. But once you know the basics of declaring a procedure or function in a DLL and the technique of initiating a call and interpreting its results, you will find that Windows API make the programmer's task much easier. Unlike the code you write with Visual Basic to accomplish a task, the code is much tighter and compact in the DLLs where the API function is written (usually in C or C++). This results in faster processing of the routine and quicker results. Not only that, as a programmer, you will need to supply just a few parameters to perform a routine within a DLL without having to bother how it's done. This will save you on development time and effort.
The Windows API contains more than a thousand useful functions and routines. A single chapter will not do justice to all of them. In fact, entire books are being written on it. This chapter is meant to whet your appetite and offer a sampling of what you can do with the Windows API. Consequently, you will find that this chapter touches on the most popular of the API routines.
To begin with, you need to understand the basics of the Windows API. We introduce the Windows API and the ways in which you can declare functions and statements within Visual Basic in order to use the API calls. Parameter passing is one of the most important and least understood areas of using the Windows API (this is covered next). The Windows API routines fall into broad categories. Once the ground work has been laid, the rest of the chapter is divided into sections, each of which concentrates on a single area of the Windows API. These sections explain a little of the Windows API in question, how it needs to be declared, what it does, and so forth, and then jumps into a small application that illustrates the live, hands-on usage of the specific Windows API being discussed. In this chapter, you learn the following:
What is the Windows API? The API stands for "Application Programming Interface." The Windows API is a set of procedures available to Windows (and Visual Basic) programmers that allow you to manipulate Windows' Graphical User Interface and other aspects of the Windows operating environment (such as virtual memory). All these procedures reside as functions in the various DLLs that Windows uses. These functions are powerful, fast and most important of all: free. They come with Windows, and every user that uses your VB application under Windows is guaranteed to have them residing in their Windows/System directory.
The Major Windows DLLs are KERNEL.EXE, GDI.EXE, and USER.EXE. These three DLLs alone provide hundreds of functions. KERNEL.EXE holds functions responsible for core Windows operations: managing memory, multitasking, and handling virtual memory. In addition, it contains functions to handle Initialization (.INI) files.
GDI.EXE contains functions that handle output to devices: the screen, the printer, memory blocks, and so on. Windows provides for Graphic DCs that shield the programmer from having to write code specific for a single printer or a single monitor. Windows handles all the conversions so that your code (in Windows or VB) will run irrespective of the type of monitor the user has or the type of printer connected to the computer. All drawing functions reside in this DLL, and we will be making use of it extensively in this chapter.
The USER.EXE DLL contains functions related to the Windows environment: managing windows, menus, cursors, timers, and so on.
All Windows API functions reside in Dynamic Link Libraries (DLLs). These are typically files with a .DLL or a .EXE extension. Conceptually, a DLL is a collection of procedures and functions placed inside a Windows recognizable file. Multiple procedures or functions can exist within a DLL. Each procedure or function is similar to the procedure or function that you create within your Visual Basic code. When you write a function, you declare the function and its arguments and ten write the code that is executed when the function is called. At another place in your code, you will make a call to the function, passing the correct parameters in the right order. If you pass the wrong parameters or they are in the wrong order, you receive a runtime error (or most probably a compile-time error). In the case of a DLL function, in addition to letting Visual Basic know of the function name and its arguments, you also have to indicate where the function is residing, usually the name of a Windows .DLL file or .EXE file. This is done by the immensely popular Declare statement in Visual Basic.
The Visual Basic keyword Declare is the way you indicate to Visual Basic that an external function or subroutine is present within a dynamic link library. The syntax for declaring a function or subroutine is provided in the Visual Basic manual with a full explanation. In a simplified form, the syntax is as follows:
In the previous syntax declaration, <name> is either the sub or function name, or if the Alias clause is used, it can be any valid routine name. This is a useful feature, as you will see later. If the Alias clause is used, the <alias name> is the real name of the sub or function as its exists within the library <libname>. This could either be the character string name of the function (like SetWindowPos) or could be the ordinal position of the function within the library (like #123).
In the case of declaring a function, As type indicates the return type of the function. The type that functions can return may be Boolean, Byte, Currency, Date, Double, Integer, Long, Object, Single, String (variable-length only), a user-defined type, or Variant type.
You can declare a sub or a function to be either Private or Public. A Private sub/function is visible to all routines within the same module only, whereas a Public sub/function is visible to all routines within the project. You can declare an API call in a module (.BAS file) or in the declarations section of a form (.FRM file). When you declare it at the module level, the API function/routine has a global scope and can be called from any routine within your application. When you declare it at the form level, however, its scope lies within the form only. With Visual Basic 4, when you declare an API call at the form level, you have to use the Private keyword to explicitly declare its scope.
For example, the GetDC function declared at the module level would be as follows:
When placed in the declarations section of a form you need to add the keyword Private as follows:
The Declare statement is usually placed at the very beginning of a module, usually following the global type and constant declarations. If you follow this style of programming, you will more easily locate and maintain your code than if all API calls were bunched together in a single module.
Note: Unlike the previous versions of Visual Basic that required you to type the entire line of the Declare statement in a single line of code within the module, with Visual Basic 4, you can use the line-continuation characters " _" (a space followed by an underscore) to break up the declare statement anywhere but within a quoted string.
Like in any other subroutine or function within Visual Basic, you need to pass arguments (if any are required) to the routines and functions within a DLL also. However, you have to be careful when you make your declarations and also while passing arguments. Remember, that the functions inside the DLL were written in a non-VB language, usually C or C++, and the arguments it expects can be of a different data type from what you think you are passing to it. In addition to this, Visual Basic can't verify that you are passing the correct number and type of arguments to a function residing in an external DLL.
Note: Because Visual Basic cannot verify whether the number and type of arguments you declare for a function residing in an external DLL is correct, it will ignore them, compile cleanly, and allow you to run the program. VB just takes it as you give it, and if the function fails because of incorrect arguments, you will encounter a GPF causing your program to hang up and lose data. The only remedy is to make sure you are passing the correct arguments and to save your work often. In fact, to prevent code loss, you should set your project parameters to always prompt you to save before running your application.
By default, Visual Basic passes all its arguments by reference. That is, when you pass an argument to a function, what is actually passed is a 32-bit pointer to the address where the data is stored. This is alright for most DLL functions, but some C functions require you to pass arguments by value: a copy of the argument is passed to the function. In such cases, you can use the ByVal keyword. You can use the ByRef keyword to indicate a value passed by reference if you wish; this will make your code easier to read.
For example, the function that provides the dimensions of the client area of a window on-screen requires its first argument to be passed by value and the second one (which is returned back stuffed with the dimension for you) to be passed by reference:
Sometimes, the functions inside a DLL have names that would be considered invalid as a procedure name in Visual Basic. For example, they may contain an invalid character (like a hyphen) as in the case of the functions "_lread" and "_lwrite" that perform file read and write procedures. Other DLLs may not have function names for their functions but may use numbers (called as ordinal numbers) to declare and use their functions. The creator of the DLL has done it in this way to consume less memory resources, but it poses a problem when declaring such a function from within Visual Basic. The answer to both cases is to use the Alias keyword. You can rename a DLL function to any valid function name and use your user-defined function name to refer to it throughout your code. For example, you can code the "_lread" and "_lwrite" functions as follows:
Now, when you refer to these functions in your Visual Basic code, you use FileRead and FileWrite as their names. In the previous example, we have called the functions FileRead and FileWrite because they are easier to understand. You could have called the functions LRead and LWrite (or anything else) for simplicity.
You would use the following similar process to handle a DLL using ordinal numbers (provided you know the ordinal numbers before hand):
Passing strings to functions inside a DLL is perhaps the most frustrating and confusing areas to the Windows API VB programmer. The reason for this is that Visual Basic handles strings in a manner different from C or C++. In C or C++, a string identified as an LPSTR structure is a long pointer to a string data type, the string that a DLL function expects to receive. In this case, the LPSTR structure is a pointer to an address in memory that contains a set of characters terminated by the Null (\0) character.
In Visual Basic, a String type has a structure called BSTR, which is a data type defined by OLE Automation. A BSTR is a pointer to an address in memory that contains a piece of information called the Header plus the actual set of characters that make up the string, terminated by the Null character. The BSTR ignores this header that contains data used by Visual Basic and points to the first data byte in the string. Therefore, a BSTR is actually a pointer to the string, just like an LPSTR structure. However, Visual Basic passes all strings by reference unless you specifically tell it not to. This means that Visual Basic passes a pointer to the BSTR structure (which itself is a pointer to the string).
If you pass a string to a DLL function expecting an LPSTR type (just a simple pointer to the data), it will receive a pointer to the BSTR type (a pointer to a pointer) and you will end up with disastrous results. To avoid this, when a DLL function is expecting a string via a LPSTR type, pass the string explicitly by value using the ByVal keyword.
For example, when you are using the GetPrivateProfileInt function to read in values from your own private .INI file, it is best to declare all strings with the ByVal keyword and then pass the strings avoid unexpected crashes.
Say you want to retrieve the value in your .INI file that looks like the following:
Declare the function at the module level, as follows:
And use the following in your code to retrieve the value:
When you pass strings to a DLL function, expect the function to modify your string, and have it passed back to you, you need to take special precautions. Remember that the actual string is not passed to the DLL function, what you are passing is a pointer to a location in memory that contains the string data. If the DLL modifies the string and the resultant string is bigger than the original one, the DLL does not know that and simply writes to the portion in memory where the string pointer points to, overwriting other portions in memory when it runs out of space. This could lead to unpredictable results. The remedy to this is to make sure that the string that you are passing to the DLL is sufficiently large to fit any value you expect the DLL to return. For example, to retrieve a string from an INI file using the GetPrivateProfileString function, make sure that the string you pass is at least 255 characters long by actually stuffing it with characters—Null or binary zero is a good choice.
To retrieve the string from the following .INI file:
Add the following declaration in your module level code:
And use the following code to retrieve the string:
In addition to passing strings, integers and other data types, you can also pass entire user-defined types to DLL functions. C functions recognize these user-defined types as structures: the ones you define in Visual Basic using the Type or End Type statements.
You can pass individual elements of a user-defined type as an argument to a DLL function with no problems. However, if you intend to pass the entire structure, it can only be passed by reference. User-defined types cannot be passed by value. For example, in our GetClientRect example described previously, the second argument is of the type RECT defined, as follows:
You can then define a variable of type RECT as follows:
And pass it to the function as follows:
With Visual Basic 4, you can now create and compile applications for a 32-bit operating system like Windows NT and Windows 95. You can use the same source code to create applications to run on 16-bit and 32-bit systems—provided you are aware of the slight differences to take in account when writing for the two platforms.
In most cases, your code will run without modifications as the general data types are the same in both versions of Visual Basic: an Integer is an Integer on both the systems and VB will take care of any translations for you. However, the biggest difference between the 16-bit and 32-bit versions of Visual Basic is the way in which they handle character data. This area is also transparent to the developer/user, and VB can handle most of the data conversions. However, if you are manipulating character data at the byte level, you should be aware of the differences and how to handle them.
The main difference arises because of the different character sets that can be used on 32-bit systems. The ANSI character set uses 1 byte per character, the standard that 16-bit applications use. In this case, each byte represents a single character. The Unicode character set uses two bytes for each character and is defined by the International Standards Organization (ISO). This is the set used by 32-bit OLE and is supported by Windows NT. The DBCS character set, or Double Byte Character Set, is used to represent far eastern languages that use non-Latin characters: for example, the Kanji characters of the Japanese language use DBCS format. In this case, each character is represented by two bytes. However, unlike Unicode character sets, the entire two bytes do not refer to a single character. DBCS uses the numbers zero through 128 to represent normal ASCII characters. Numbers greater than 128 are "lead byte characters" and represent special formatting characteristics for the character that follows.
When coding for DBCS character sets, a string manipulation function like Mid$ will return a character reading from the beginning of the string. Therefore, if you wanted the third character in a string initialized to "Hello," you can use the Mid$(stringname, 3,1) to return the character "l" under ANSI. However, under DBCS, the same Mid$ function would now return the "lead byte character" for the second character and it will represent gibberish. Instead, a byte manipulation equivalent of Mid$ called MidB$ needs to be used, which understands the byte position of each character and returns the proper one.
When it comes to using the Windows API, the 32-bit version of Visual Basic assumes that all external DLLs use ANSI characters and makes the necessary conversions when passing or retrieving string variables. All 32-bit Windows API files use ANSI characters so this is not a problem. Windows NT offers both ANSI and Unicode functions as 32-bit Windows API functions, but you can only use the ANSI version with Visual Basic. ANSI versions of common Windows API functions under the 32-bit system are differentiated by the character "A" at the end of the function name. You can still use the old function name if you Alias the function to represent the correct one.
Now that we have cleared up the issue of declaring routines within a DLL to Visual Basic, it's time we actually used the Windows API. First and foremost, Windows API routines concern controlling Windows.
All windows under the Microsoft Windows operating system are identified by their handles. Visual Basic provides a hWnd property to each form that represents the handle of the form when it is opened as a window. You cannot create a handle, and you should not manipulate the handle of a window. However, the hWnd property comes in very handy when we need to manipulate the windows themselves. Because every window has a handle, we can manipulate windows belonging to other applications using their handles.
The GetActiveWindow function can be used to retrieve the handle of the currently active window. The top-level window that has the input focus.
Declare Function GetActiveWindow Lib "User" () As Integer
For example, the following code activates the Notepad application and retrieves the handle of the Notepad window. This code checks to make sure Notepad is active also:
To obtain the handle of a specific window, we need to know either its title or its class name. The title is the string that the window displays on its caption, and the class name is the string name by which the window was registered with MS Windows when it was created. Even the desktop in Windows on which all other windows are placed has a handle and can be retrieved.
If you know the value of only one of the two arguments, use a Null string as a value for the other. Do not use the keyword Null, but instead pass zero as a Long data type (0&).
To find the handle of the notepad window, whose title we know to be "Notepad – (Untitled)", use the following:
To find the handle of another Visual Basic EXE application window, whose class name we know to be "ThunderForm", use the following:
MS Windows maintains a lot of information regarding all open windows, including their class names, title, sizes, locations, hierarchie, and so on. It is easy to obtain this information and manipulate them. Be careful in manipulating values however because some properties cannot be modified while modifying others could be disastrous. We shall see some of the more interesting functions that provide Window information in this section. The small application that will conclude this section will provide you with more hands-on experience.
The GetWindowRect function returns the dimensions of the window on screen including the title and border areas. The GetClientRect function returns the dimensions of the client area alone.
The MoveWindow function allows you to move and change the size of the window in a single stroke. SetWindowPos allows you to set the position and size of a window on screen as well as alter the window's order within the internal list of windows that MS Windows maintains.
Using the previously mentioned functions, we can write a small piece of code that will allow our application window to have an "Always on Top" property.
Declare the type RECT at the module level, as follows:
And add the following code to a form:
In addition, the functions GetClassName and GetWindowText return the class name and title caption of a window, as we shall see used in our application.
Using the Windows API, we can easily build a parent-child hierarchy between all the windows open on the desktop, and selecting any one at random, we can investigate its properties more clearly. All this is possible because Windows itself maintains a list of windows and their relations to each other. Remember, to Windows, a window is not only the "form" in Visual Basic but even objects such as combo boxes, list boxes, command button, and so on. A hierarchy list like this provides a lot of illumination as to how commercial applications were developed. For example, you may open up Microsoft Word, load a document, and then this application to see the various child windows that MS Word uses to manage itself (see fig. 3.1).
Fig. 3.1 PARCHILD.EXE displays Parent-Child Hierarchies within Windows.
We have used a new project PARCHILD.MAK (see Listing 3.1) to build a parent-child hierarchy list of our own. This system includes two files: PARCHILD.FRM is the main form and PARCHILD.BAS contains global declarations.
Listing 3.1 PARCHILD.MAK Building a Parent-Child Hierarchy List with PARCHILD.MAK
Create a form called PARCHILD.FRM and retain its name of Form1. Add a list box named WinList covering most of the window and insert two command buttons, one with a caption of Describe called cmdDescribe and one with a caption of Close called cmdClose. When you have done so, your form should look like one shown in figure 3.2.
Fig. 3.2 PARCHILD.FRM at designtime.
The accompanying CD-ROM contains the complete source code for the PARCHILD.MAK project. You can refer to it at any time; however, I point out the key features in this project.
This project puts the following API declarations to use in this project. All these declarations are included in the previous PARCHILD.FRM form (included on enclosed CD-ROM) in its declarations section.
There are three main routines in this application that perform the functions of populating the list box on-screen with all open windows, providing individual details of any window, and describing each window selected by the user in detail.
The LoadListBox routine accepts the handle of a top-level window--usually the desktop window, and then proceeds to walk through the list of all open windows. For each window it finds, the routine proceeds to walk through a list of all open child windows, calling itself recursively to perform the same task on the parent and the child window and any grand child windows, ad infinitum. Listing 3.2 shows you how this is done.
Listing 3.2 PARCHILD.FRM The LoadListBox Routine that Walks Through All Open Windows
As you can see, the LoadListBox routine makes a call to the routine GetWindowDetails that actually returns a string containing the properly formatted Window Handle, the Window's Module Name as well as its Class Name. Listing 3.3 shows how it is done.
Listing 3.3 PARCHILD.FRM The GetWindowDetails Routine
And finally, the DescribeWindow routine in Listing 3.4 displays a message box containing detailed information about the window that the user selects from the List Box. This routine interrogates the window for its characteristics and displays them to the user.
Listing 3.4 PARCHILD.FRM The DescribeWindow Routine
When you run PARCHILD.MAK, you get a screen listing all open windows with parent-child hierarchies built in (see fig. 3.3). Click on any window and choose Describe to obtain more information about the window selected.
Fig. 3.3 PARCHILD.EXE displays details of a single selected window in a message box.
Visual Basic provides a robust Menu Editor where you can create your own top-level menus containing sub menus. This feature in Visual Basic should be able to provide you with all the functionality you desire from menus. Because this chapter deals with using the Windows API to accomplish tasks that Visual Basic otherwise may not provide, we shall look at a few Windows API menu manipulation functions.
One of the ways in which you can liven up your menus is to provide graphic images in menus instead of text. For example, you may want to have a small graphic of a fax machine next to the menu caption "Fax this document," or a small caption of a modem next to the e-mail menu. The Windows API function ModifyMenu allows you to modify an existing menu by adding a bitmap of your own. The declaration for the function is as follows:
In the previous function, hMenu is the handle of the menu you wish to modify, and nPosition is the specific menu item number you wish to change. You can retrieve this hMenu handle by using the API function GetMenu and GetSubMenu repeatedly until you reach the menu item you need. The GetMenu function returns a handle to the top-level menu (if it exists)of a window. GetMenu is declared with the following:
Once you get the handle of the menu, you can obtain the handle of any popup menu or sub menu within it using the GetSubMenu function. Submenus begin with 0 so you should remember to pass the right parameter to the nPos argument as follows:
Each menu item is associated with a unique menu ID in Windows, and when you modify a menu, you can keep the existing ID or provide a new one. You can obtain the current menu ID for a menu item by using the GetMenuItemID function. Menu items begin with a 0, so remember to pass the correct position of the item you want.
Using the previous set of functions, we can modify a menu to incorporate a custom bitmap within a menu item. The handle to the bitmap is passed as the last argument to the ModifyMenu function. Let's see how we can use it for a real-world application. One of the menu types found mostly on the Macintosh version of word processors is the capability to display font names available to the user. Each menu item displaying the font name appears in the actual font of the menu item. Our application for this section duplicates this procedure by using ModifyMenu to load bitmaps into the menu items. But before we begin with our application, Displaying Bitmaps in Menus, there is one more API functionality that you may find interesting: pop-up menus.
Pop-up menus are menus that are not attached to the horizontal menu on the top of the window. Traditionally, pop-up windows appear at the click of a mouse button (usually the right mouse button), and the pop-up menu is displayed at the current cursor position. For example, in the design mode in VB 4, you can click your right mouse button on an OLE Control to bring up a context-sensitive pop-up menu.
With Visual Basic 3.0 onwards, VB has this feature built-in with the Pop-upMenu command. To use this effectively, create a menu as you normally would with the menu editor. For the last menu item in a top-level menu, change the caption to a Null string "", and create a submenu for this item. This will make the menu item invisible to the user but will still retain the submenu in memory that you can bring up anywhere on the form using the VB Pop-upMenu command. We shall use this functionality in our application for this section.
The Windows API function TrackPop-upMenu provides a similar functionality.
In the previous function, hMenu is the handle of the pop-up menu, and wFlags refer to position and mouse tracking flags similar to the VB Pop-upMenu flags. X and y refer to the screen coordinates where the pop-up menu will be displayed. nReserved is not used and should be passed as 0. hWnd is the handle of the window to receive the pop-up menu commands. lpRect refers to the rectangular area on screen beyond which, if the user clicks with the mouse, the pop-up menu will be closed. Pass this as zero long (0&) to make the pop-up menu close any time the user clicks the mouse anywhere on the screen.
Let's now build an application that puts into practice what we have discussed about menus. We shall create a single form project that contains a text box. This text box allows you to type in text and with a right mouse click displays a list of fonts available for the text box's text (see fig. 3.4). When the user selects a font, the text box text changes to display in the selected font. The available fonts are displayed in their original fonts using bitmaps, and VB's own Pop-upMenu command is used to bring up the pop-up menu.
Fig. 3.4 MENU4.EXE displays a pop-up menu of available fonts.
Create a new project called MENU4.MAK. Create a new form MENU4.FRM and retain its name as Form1. Add a text box to fill almost the entire form, add a label to indicate "Click the right mouse button to view menu," and add a Picture box. Give the Picture box an index of 0 to create a control array. Use the Menu Editor to create a Menu that has two menu items: "File" and "Fonts". Create a submenu containing "Exit" under "File", and a submenu containing "First Font" under "Fonts". When you are done, your screen should look like one shown in figure 3.5.
Fig. 3.5 MENU4.FRM in design mode.
Remember that you have access to the complete source code on the accompanying CD-ROM. In this project MENU4.MAK, you make use of all the Menu Control API's discussed earlier, including GetMenu, GetMenuItemID, ModifyMenu, and GetSubMenu.
The key routine in this application is LoadMenuFonts. This routine places the bitmap image of each font's name as a menu item within the Font menu. When the user clicks the menu, the name of the font is displayed in the actual font itself rather than the menu names dropping as standard menu strings. You can modify this routine to include any other bitmap of your choice. Listing 3.5 describes the LoadMenuFonts routine.
Listing 3.5 MENU4.FRM The LoadMenuFonts Routine in the MENU4.MAK Application
When this application runs, the user sees a screen containing a text box with some text. When the user clicks on the text with the right mouse button, the Font menu pops up displaying a choice of fonts for the text box. Once the user makes a choice, the text box font changes to the selected font. Listing 3.6 shows the code that handles this event.
Listing 3.6 MENU4.FRM The MouseDown Event Displays a Pop-up Menu
Note: The previous application uses the right mouse button to pop-up a menu displaying bitmaps. If you are running VB 4 under Windows 95, when you click the right mouse button, Windows 95 puts up its own context-sensitive pop-up menu. Just ignore it and click the right mouse button once again to display the application pop-up menu.
Probably the most appealing of all Windows API functions are the graphics routines available to the VB programmer. Windows is a graphical operating system and provides a rich set of graphics routines. However, in order to use these effectively, the VB programmer has to understand about graphic device interface objects or GDI objects. In this section, we shall see how to use GDI routines to create special graphical effects.
One of the advantages of programming under Windows is that Windows performs most of the hardware related processing under the hood. Different users could run your Visual Basic program by running Windows with different monitors and different screen resolutions. When you write VB code, you are not bothered about the actual hardware that the application is to run on—Windows will handle any hardware specific issues. For example, when printing out a report, your code will work no matter what printer the user is connected to: provided Windows has been configured to write to that printer.
In the same way, when we use graphic routines to draw to the screen, Windows provides us with an object called as a device context (DC). A DC can represent a window, a hardware device such as a printer, or even a portion of memory that, to us, acts as the device. When a DC refers to a block of memory, it is called as a memory DC. When we use graphic routines, we do all our drawing to these DCs. This way, all our code is written to the DC and Windows is left with the job of figuring out any translations and the actual process of displaying it on your user's monitor.
Windows provides us with other structures that control how graphic routines are handled and how graphics are displayed on-screen. These form the collection of GDI objects or objects belonging to the GDI of Windows. We have already seen the DC that is a key GDI object. When you display lines on-screen, you use the Pen object. When you fill an area on screen with color or with a pattern, you use the Brush object. Color palettes determine what colors are visible on a window. Bitmaps store information about an image including color within an object called as the Bitmap object.
Note: Before you start to use graphic routines in Windows, you must select the proper GDI object to draw with, and the correct DC to draw onto.
You can use DCs by either obtaining it from Windows or creating your own new one. You can obtain the DC of an existing window or device or even the screen by using the GetDC API function. You can pass the handle of a window to obtain the DC for the window, or you can pass a 0 to retrieve the DC of the screen itself.
Once you have a handle to the DC, you can then perform graphic routines directly onto the DC. Sometimes, it is beneficial to create a copy of an existing DC in memory. To do so, we use the CreateDC and CreateCompatibleDC functions.
To create a DC compatible with the screen, you can use the CreateDC function as follows:
To create a DC in memory that is compatible with the DC of an existing window, you can use the CreateCompatibleDC function as follows:
Once you have a handle to either the DC of a window, or the memory DC, you can perform graphic routines like drawing lines, circles, arcs, and rectangles to the Dcs. You can also draw entire bitmaps as we shall see. By manipulating both memory DCs and window DCs, we can create graphic special effects, as our application for this section will show.
In the same way in which you create a DC, Windows also supplies functions to create Pens, Brushes, Bitmaps, Color Palettes, Fonts, and so on. Once you create these objects and are ready to apply them to the DC, you need to select the object into the DC using the SelectObject API.
This ensures that the object you desire is currently the active object within the DC and subsequent graphic routines can access that object.
Bitmaps or BMP files are the format in which Windows stores information about an image. You can store bitmaps either as BMP files on disk, in a picture control, or in an image control within your application. For our application, we are going to provide the user with the capability to display any bitmap of choice. We can either use API routines to read individual bytes from a Bitmap structure on disk or take the easy way out and use the Visual Basic LoadPicture routine. We'll take the easy way out.
Once we have a bitmap stored in a picture control, we can then use the Windows API functions BitBlt and StretchBlt to copy portions of it onto a form or even the screen. BitBlt copies a bitmap from a source DC to a destination DC rectangle without any change in image size. StretchBlt goes one step further and is capable of stretching or shrinking the source image to fit in the destination rectangle you require.
BitBlt takes nine arguments. The first one is the destination DC to display the image. The next four are the destination rectangle dimensions. This is followed by the source DC and the origin of the source rectangle. BitBlt will start copying the image from the origin specified in the source rectangle and will fill an area pointed to by the destination rectangle. The last argument for BitBlt is a Raster Operation flag that determines how the function copies the bitmap from source to destination DCs. The simplest flag is SRCCOPY that copies the source to the destination. Other operations allow you to invert the image, do an XOR and AND copy, and so on.
StretchBlt is similar to BitBlt and in addition to the destination rectangle dimensions in that it requires the source rectangle dimensions to figure out how much stretching or shrinking needs to be done.
Have you wished to create presentations that display a series of images one on top of the other with professional dissolves? Do you like the way that Microsoft PowerPoint presents its slides? Well, with a little help from the Windows API, we can create our own BMP F/X application (see fig. 3.6).
Fig. 3.6 BMPFX4.MAK illustrates how you can display bitmaps with special effects.
We shall create a simple application for this chapter, and you can easily modify it to do more. Our application will use a form to display bitmaps of choice. For each bitmap to be displayed, we need to know the name of the bitmap file, the particular special effect to use to display it, the speed with which a bitmap will be displayed and the time to wait in between bitmap displays. We can also let the user determine whether we should use the original size of the bitmap or fill the entire window with the bitmap, stretching or shrinking the bitmap as desired. And lastly, we need to know if the bitmap has to replace an existing image on screen if or we should paint the screen white before displaying the bitmap. For each bitmap (or slide) to be displayed, we will store all this information in a user-defined type called SlideType.
For this application, we shall let the user decide all this by supplying information in an INI file which we can read, again by using Windows API calls. Our INI file will indicate the total number of slides to display, and for each slide, will present all information.
Before you proceed, you need to create an INI file as shown in Listing 3.7. Your application uses this INI file to display bitmaps. You can change the settings in this INI file to experiment with different special effects for displaying bitmaps.
Listing 3.7 BMPFX4.INI Creating an INI File
In this application, the function LoadPDetails first identifies how many slides are displayed by looking at MaxSlides under SlideInfo in the INI file. For each slide, it obtains information to fill in the SlideType structure: the name, the special effect to use, the speed, the wait time, and so forth. The RunPresentation routine displays each slide one by one. The DisplayPic routine displays a single slide with the desired special effect. The actual Bitmap Special Effects (or FX) are as follows:
As you see, for the first five effects, you use the window's DC and draw on it directly, and for the last two, you use a memory DC.
Create a new project called BMPFX4.MAK with one form BMPFX.FRM and one module BMPFX.BAS. Create the form BMPFX.FRM with one Picture box and one common dialog box control. The form BMPFX.FRM has a menu with three options. The first loads slide data from an INI file, the second displays the slides using special effects, and the third exits the system. Figure 3.7 shows what your form looks like in design mode.
Fig. 3.7 This is what your form should look like in design mode.
Note that the common dialog box control has its properties set as follows:
As usual, you can refer to the actual code on the accompanying CD-ROM. In this application, you make use of the following API calls declared within the BAS file BMPFX.BAS.
The actual grunt work of displaying a bitmap using a special effect is done by the DisplayPic routine. This routine loads the correct bitmap into a hidden VB Picture box and then proceeds to paint it on to the form using the desired effect. Listing 3.8 shows the code for this routine.
Listing 3.8 BMPFX4.BAS The DisplayPic Routine Displays a Bitmap with a Desired Special Effect
For the first five special effects, we create a DC compatible with the form's DC and select the bitmap into the compatible DC. We then use the StretchBlt function to blast bits from the bitmap onto the form using different widths and heights as the destination. For the last two special effects, it is not sufficient that we copy the image from the Picture box to the form. Because we are creating a horizontal or vertical blinds effect, after drawing each blind, or a portion of it, we need the image now on the form to work with. That is, after drawing each portion of a blind, we need a copy of what is visible on the form. Therefore, we need to copy the image from the Picture box to the form, and copy the image from the form to a temporary storage space with which we can then work. This is done by creating a memory DC compatible to the form's DC as follows:
We then proceed to draw portions of our horizontal or vertical blinds onto the hShadowDC. And when we are satisfied, we copy the entire hShadowDC back to the form DC.
This way, we can use the memory DC to perform fast BitBlt or StretchBlt operations and then copy the entire memory DC image onto the form DC with one stroke.
You can see the potential to modify the application. You can add a variety of different special effects in a similar manner. You can have menu options that allow the user to modify the presentation data and view the results immediately. You can have a flag in the .INI file to cycle through the presentation so that all the slides are shown over again.
Windows provides us with a set of rich Multimedia functions through the Windows Multimedia System built into Windows. With these multimedia functions, you can manipulate sound, graphics, and video. The sound recorder that comes with Windows 3.1 and Windows for Workgroups 3.1x versions lets you add digitized sounds to your documents and applications. The Media Player utility plays WAVE files, MIDI sequencer files or AVI video files.
The functions that provide multimedia Windows API are present in the MMSYSTEM.DLL file. And like any other API call, you can declare and use these functions also. The entire gamut of multimedia functions available and their usage is too large for the scope of this chapter. We will however, discuss a small sampling of how to use sound, video, and multimedia text in your applications.
The simplest way to incorporate sound in your applications is, of course, to use the Visual Basic Beep statement. However, if you wish to provide professional quality sound, you need to be able to play sound files (with a .WAV extension). To do so, you can use the mciExecute API function.
Then in your application, you can have the following code to play the WAV file TADA.WAV:
You can test out the code only if you have a multimedia PC (or at least have a sound card installed and configured under Windows).
If you wish to have a wave audio file (WAV file) playing in the background as your application proceeds with its tasks, you can use the sndPlaySound API function. To do so, you must have the waveform audio device driver installed.
Declare Function sndPlaySound Lib "MMSYSTEM" (ByVal _
lpszSoundName As String, ByValuFlags As Integer) As Integer
In the previous function, lpszSoundName is the filename of the wave audio file to play. uFlags refers to the windows flag that determines how you want the function to behave. The valid values for uFlags are as follows:
You can find numerous other functions included in MMSYSTEM.DLL to help you play .WAV files and .MID files and to control sound.
You can use the Media Control Interface VBX (MCI.VBX) that comes with the pro edition of Visual Basic to display video files (.AVI files) in you application. However, if you wish, you can directly manipulate the multimedia functions within MMSYSTEM.DLL to accomplish the same thing.
In order to run .AVI files, you need to have software such as Video for Windows (from Microsoft) or QuickTime for Windows (from Apple) loaded on your machine. This software comes in runtime versions and are available from numerous bulletin board services and on CompuServe. They provide an extension for Windows that enables applications to play video clips.
You can play an .AVI file in your VB application by using the same mciExecute command we saw earlier.
And, in your application, you can call the previous function to play a AVI file:
This code causes Windows to open a new window, title it with the name of the AVI file and play it. When the file is done playing, the window is automatically closed.
If you are using your own Help File with your application, you can activate your it and have it perform different actions by using the WinHelp API function.
You can call the function as the response to the user pressing the F1 key or for any other action. In the previous function, hWnd is the handle of the window requesting help. Windows keeps track of which application requested what help and needs this parameter. lpHelpFile is the fully qualified name of your help file. wCommand is the action you desire from Windows Help engine, and dwData is the data supporting the action you request. The valid values for wCommand its corresponding dwData type is as follows:
Create a new project called TESTHELP.MAK (see fig. 3.8) and create a new form called TESTHELP.FRM with two text boxes, a label, and three command buttons.
Fig. 3.8 TESTHELP.MAK demonstrates how the WinHelp API can display Help Files.
Figure 3.9 shows your form at designtime.
Fig. 3.9 TESTHELP.FRM in design mode.
The source code for this small application is included on the accompanying CD-ROM. Compile and run this application. When you use the default text box strings, you can see the Program Manager Help File displayed with different starting options when you click on each of the three command buttons.
Visual Basic 4 now provides you with the option of writing applications that will work in a 32-bit environment such as Windows NT or Windows 95. The advantage is that you can now write code that can utilize the power and speed of a 32-bit operating system. Your copy of VB 4 will be optimized to work under 32-bit systems. However, if you are delving into the Windows API you are on your own and need to keep a few things in mind.
For the 32-bit operating system, Windows provides a 32-bit API with its own new functions. Most of the changes in the Windows 32-bit API fall into one of four categories: functions that have been dropped, 16-bit values that have been widened to 32 bits, modified functions, and new APIs for file I/O and new features.
Microsoft has taken care to change very little from the 16-bit API to the 32-bit API, and this change has been well managed, with Microsoft employing strict rules to ensure minimum differences. For example, names of symbolic constants you use in your 16-bit API have not changed. Similarly, Data structure names and members remain the same as do function names and argument orders.
To make a smooth transition from 16-bit API usage to 32-bit API usage, keep the following in mind:
The 16-bit API calls MoveTo, ScaleViewPortExt, ScaleWindowExt, SetBitmapDimension, SetViewPortExt, SetWindowExt, and a few others handle graphic routines and include packed x and y coordinates for graphics. In Windows 3.x, the x and y coordinates are 16 bits each and are packed into a 32-bit return value. In Win32, they are 32 bits each and the return value is 64 bits long that cannot be handled by the previous function calls. You need to use their equivalent Win32 API calls, each of which has the same name as the previously mentioned functions with the characters "Ex" appended to the end. So MoveTo becomes MoveToEx. The Ex provides an additional parameter that points to the location from where you can retrieve data. If you do not wish to use this data, you can pass Null. For example, in a call to MoveTo, we would not be concerned with knowing where we actually moved to, so the call under Windows 3.x as MoveTo (hDC, x, y) can be changed to MoveToEx(hDC,x,y,Null) under Win32 without any problem.
Other graphic routines have now included the POINT structure to obtain the values of x and y coordinates instead of passing x and y as return values. For these functions, the characters "Point" is appended to the end of the function name to denote the use of a POINT structure. In addition, these new "Point" functions now accept an argument of type POINT in which the return values are placed.
If you are using DOS system calls (using the DOS3Call API function) to perform file I/O, they need to be replaced with Win32 file I/O calls. For example, the functions that retrieve the system date and time, obtain disk free space, and create and remove directories have to be changed to their corresponding Win32 API equivalent: GetDateAndTime, SetDateAndTime, GetDiskFreeSpace, CreateDirectory, and RemoveDirectory.
Because Windows NT supports filenames of up to 256 characters, fixed-length buffers for filenames, and environment strings need to be increased in size.
As you can see, we have carried out a whirlwind tour of the Windows API. We managed to glimpse briefly at the power and ease of using the Windows API. The only way you can master it fully is by practice. Learn to pass parameters the right way, use the API to perform activities that Visual Basic does not provide, and you will be developing applications that your users will marvel at, and your peers will be wondering how that was done in VB.
Now that you have mastered the basics of using the Windows API with Visual Basic, you can round out your knowledge and use it in your applications. To do so, you should also review the material in the following chapters of this book:
| Previous Chapter | Next Chapter | Search | Table of Contents | Book Home Page |
| Buy This Book | Que Home Page | Digital Bookshelf | Disclaimer |
To order books from QUE, call us at 800-716-0044 or 317-361-5400.
For comments or technical support for our books and software, select Talk to Us.
© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.