Visual Basic Expert Solutions

book coverQUE

Chapter 3

Using the Windows API

By 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:

Understanding the Windows API

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.

Dynamic Link Libraries

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.

Declaring a DLL Routine

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:

Declare Sub <name> Lib "<libname>" Alias <alias name> _
(<argument list>)
Declare Function <name> Lib "<libname>" Alias <alias name> _
(<argument list>) As type

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:

Declare Function GetDC Lib "User" (ByVal hWnd As Integer) _
As Integer

When placed in the declarations section of a form you need to add the keyword Private as follows:

Private Declare Function GetDC Lib "User" _
(ByVal hWnd As Integer) As Integer

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.

Passing Arguments

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.

Passing by Value or Reference

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:

Declare Sub GetClientRect Lib "User" (ByVal hWnd As Integer, _
lpRect As RECT)

Using Ordinal Numbers

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:

Declare Function FileRead% Lib "Kernel" Alias "_lread" _
(ByVal hFile As Integer, ByVal lpBuffer As String, ByVal _
wBytes As Integer)
Declare Function FileWrite% Lib "Kernel" Alias "_lwrite" _
(ByVal hFile As Integer, ByVal lpBuffer As String, _
ByVal wBytes As Integer)

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):

Declare Function SomeFunctionName Lib "LibName" Alias "#123" _
(arguments) as Integer

Passing Strings

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:

[Preferences]
VBBookEdition=2

Declare the function at the module level, as follows:

Declare Function GetPrivateProfileInt Lib "Kernel" _
(ByVal lpApplicationName As String, ByVal lpKeyName _
As String, ByVal nDefault As Integer, ByVal lpFileName _
As String) As Integer

And use the following in your code to retrieve the value:

' ----- Declare variables
Dim MyIniFile as string
Dim sAppName as string
Dim sKeyWord as string
Dim I as integer
' ----- Initialize variables
MyIniFile = App.Path & "\MYAPP.INI"
sAppName = "Preferences"
sKeyWord = "VBBookEdition"
' ----- Call the function, default value expected is '1'
I = GetPrivateProfileInt(sAppName,sKeyWord,1,MyIniFile)

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:

[Preferences]
VBBookName=Visual Basic 4 Expert Solutions

Add the following declaration in your module level code:

Declare Function GetPrivateProfileString Lib "Kernel" _
(ByVal lpApplicationName As String, lpKeyName As Any, ByVal _
lpDefault As String, ByVal lpReturnedString As String, _
ByVal nSize As Integer, ByVal lpFileName As _
String) As Integer

And use the following code to retrieve the string:

' ----- Declare variables
Dim MyIniFile as string
Dim sAppname as string
Dim sKeyWord as string
Dim sDefault as string
Dim sBuf as string
Dim L as integer
Dim sBookName as string
' ----- Initialize variables
MyIniFile = App.path & "\MYAPP.INI"
sAppName = "Preferences"
sKeyWord = "VBBookName"
sDefault = "None"
' ----- Stuff the buffer with nulls, forcing its size
sBuf = String$(255,0)
' ----- Call the function and get the size of the string
L = GetPrivateProfileString(sAppName, sKeyWord, sDefault, _
sBuf, Len(sBuf), MyIniFile)
' ----- Get the book name from the buffer
sBookName = Left$(sBuf,L)

Passing Structures

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:

Type RECT
left as integer
top as integer
right as integer
bottom as integer
End Type

You can then define a variable of type RECT as follows:

Dim rc as RECT

And pass it to the function as follows:

GetClientRect Form1.hWnd, rc

Win32 API Considerations

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.

Using Window Control Functions

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.

Handling 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:

Dim hWindow As Integer
On Error Resume Next
Err = 0
AppActivate "Notepad"
If Err <> 0 Then
MsgBox "Notepad not found"
Else
hWindow = GetActiveWindow()
MsgBox "Notepad window handle = " & Str$(hWindow)
End If

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.

Declare Function FindWindow Lib "User" (ByVal lpClassName As _
Any, ByVal lpWindowName As Any) As Integer

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:

hWindow = FindWindow (0&, "Notepad - (Untitled)")

To find the handle of another Visual Basic EXE application window, whose class name we know to be "ThunderForm", use the following:

hWindow = FindWindow("ThunderForm", 0&)

Obtaining Window Information

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.

Declare Sub GetWindowRect Lib "User" (ByVal hWnd As Integer, _
lpRect As RECT)
Declare Sub GetClientRect Lib "User" (ByVal hWnd As Integer, _
lpRect As RECT)

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.

Declare Sub MoveWindow Lib "User" (ByVal hWnd As Integer, ByVal _
X As Integer, ByVal Y As Integer, ByVal nWidth As Integer, _
ByVal nHeight As Integer, ByVal bRepaint As Integer)
Declare Sub SetWindowPos Lib "User" (ByVal hWnd As Integer, _
ByVal hWndInsertAfter As Integer, ByVal X As Integer, ByVal _
Y As Integer, ByVal cx As Integer, ByVal cy As Integer, _
ByVal wFlags As Integer)

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:

Type RECT
left as integer
top as integer
right as integer
bottom as integer
End Type

And add the following code to a form:

Const SWP_SHOWWINDOW = &h40
Const HWND_TOPMOST = -1
Private Declare Sub GetWindowRect Lib "User" _
(ByVal hWnd As Integer, lpRect As RECT)
Private Declare Sub SetWindowPos Lib "User" (ByVal hWnd As _
Integer, ByVal WndInsertAfter As Integer, ByVal X As _
Integer, ByVal Y As Integer, ByVal cx As Integer, _
ByVal cy As Integer, ByVal wFlags As Integer)
Private Sub Form_Load()
Dim rc As RECT
GetWindowRect Me.hWnd, rc
Call SetWindowPos(Me.hWnd, HWND_TOPMOST, 0, 0, _
(rc.right - rc.left), (rc.bottom - rc.top), SWP_SHOWWINDOW)
End Sub

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.

Declare Function GetClassName Lib "User" (ByVal hWnd As Integer, _
ByVal lpClassName As String, ByVal nMaxCount As Integer) _
As Integer
hWnd : Handle of the Window
lpClassName : String Buffer to fill in with the Class Name
nMaxCount : Size of the string buffer
Declare Function GetWindowText Lib "User" (ByVal hWnd As Integer, _
ByVal lpString As String, ByVal aint As Integer) As Integer
hWnd : Handle of the Window
lpString : String buffer to fill in with the Window's title caption
aint : Size of the string buffer

Generating Window Parent-Child Hierarchies

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

PARCHILD.FRM
PARCHILD.BAS
Object={F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.0#0; COMDLG16.OCX
Object={FAEEE763-117E-101B-8933-08002B2F4F5A}#1.0#0; DBLIST16.OCX
ProjWinSize=160,352,248,215
ProjWinShow=2
IconForm="Form1"
HelpFile=""
Title="PARCHILD"
ExeName="PARCHILD.exe"
Command=""
Name="ParChild"
HelpContextID="0"
StartMode=0
VersionCompatible="0"
MajorVer=1
MinorVer=0
RevisionVer=0
AutoIncrementVer=0
VersionComments="Parent Child Hierarchy demo"
VersionCompanyName="Que Publishing"
VersionLegalCopyright="Que Publishing"
Reference=*\G{00025E01-0000-0000-C000-000000000046}#0.0#0# _
C:\VB4\PRODUCT\DAO2516.DLL#Microsoft DAO 2.5 Object Library
Reference=*\G{BEF6E001-A874-101A-8BBA-00AA00300CAB}#1.0#0# _
C:\WINDOWS\SYSTEM\OC25.DLL#Standard OLE Types

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.

Private Declare Function IsWindowEnabled% Lib "User" (ByVal hWnd%)
Private Declare Function IsWindowVisible% Lib "User" (ByVal hWnd%)
Private Declare Function GetClassName Lib "User" (ByVal hWnd As _
Integer, ByVal lpClassName As String, ByVal nMaxCount _
As Integer) As Integer
Private Declare Function GetDesktopWindow Lib "User" () As Integer
Private Declare Function GetModuleFileName Lib "Kernel" _
(ByVal hModule As Integer, ByVal lpFilename As String, _
ByVal nSize As Integer) As Integer
Private Declare Function GetWindow Lib "User" (ByVal hWnd As _
Integer, ByVal wCmd As Integer) As Integer
Private Declare Function GetWindowLong& Lib "User" (ByVal hWnd%, _
ByVal nIndex%)
Private Declare Sub GetWindowRect Lib "User" (ByVal hWnd%, _
lpRect As RECT)
Private Declare Function GetWindowWord Lib "User" (ByVal hWnd As _
Integer, ByVal nIndex As Integer) As Integer

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

Private Sub LoadListBox(iWin As Integer)
'
' This is a recursive sub LoadListBox. For every window, a
' description is pasted into the List box. If the window has
' children, this sub is recursively called. If the window has
' siblings, this sub is again recursively called. The recursive
' call ends when there are no more siblings at any level - to be
' precise, we have a list of all windows with all their children
' nested deep.
'
Static Level As Integer
Dim hChild As Integer
Dim hSibling As Integer
' ----- Load this window's description into the list box
WinList.AddItem GetWindowDetails(iWin, Level)
WinList.ItemData(WinList.NewIndex) = CLng(iWin)
' ----- Does it have a child
hChild = 0
hChild = GetWindow(iWin, GW_CHILD)
If hChild Then
' -- Child found, increment level and call sub recursively
Level = Level + 1
LoadListBox hChild
Level = Level - 1
End If
' ----- Does it have a sibling
hSibling = 0
hSibling = GetWindow(iWin, GW_HWNDNEXT)
If hSibling Then
' ----- Sibling found, call sub recursively
LoadListBox hSibling
End If
End Sub

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

Private Function GetWindowDetails(hWindow As Integer, _
iLevel As Integer)
'
' Provides a string to paste into the ListBox with details
' of a single window
'
Dim ResultStr As String ' String returned
Dim sBuf As String ' Buffer string
Dim L As Integer ' Misc integer
' ----- Construct our string piece by piece with information
' ----- Initial simple graphic to display hierarchy
ResultStr = Space$(iLevel * 5) + "|=> "
' ----- The Window handle
ResultStr = ResultStr & "#" & Str$(hWindow) & ": "
' ----- The Window Application Name
sBuf = String$(255, 0)
L = GetModuleFileName(GetWindowWord(hWindow, GWW_HINSTANCE), _
sBuf, Len(sBuf))
sBuf = left$(sBuf, L)
ResultStr = ResultStr & sBuf & ": "
' ----- The Window Class Name
sBuf = String$(255, 0)
L = GetClassName(hWindow, sBuf, Len(sBuf))
sBuf = left$(sBuf, L)
ResultStr = ResultStr & sBuf & ": "
GetWindowDetails = ResultStr
End Function

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

Private Sub DescribeWindow(hWind As Integer)
'
' Provide the description of a single window in a message
' box to the user
'
Dim ResultStr As String ' The string to be displayed
Dim rc As RECT ' Window dimensions rectangle
Dim CrLf As String ' Carriage return and _
line feed string
Dim LStyle As Long ' Style flag
CrLf = Chr$(13) + Chr$(10)
' ----- Build up our string, piece by piece
ResultStr = "Window description " & CrLf
' ----- Window handle
ResultStr = ResultStr & "Handle # = " & Str$(hWind) & CrLf
' ----- Window Dimensions
GetWindowRect hWind, rc
ResultStr = ResultStr & "Position (" & Str$(rc.left) & ", " _
& Str$(rc.top) & ") - (" & Str$(rc.right) & ", " _
& Str$(rc.bottom) & ") " & CrLf
ResultStr = ResultStr & "Dimensions " & Str$(rc.right - _
rc.left) & "x" & Str$(rc.bottom - rc.top) & " pixels" & CrLf
' ----- Window Condition
If IsWindowEnabled(hWind) Then
ResultStr = ResultStr & "Is Enabled" & CrLf
Else
ResultStr = ResultStr & "Is Disabled" & CrLf
End If
If IsWindowVisible(hWind) Then
ResultStr = ResultStr & "Is Visible" & CrLf
Else
ResultStr = ResultStr & "Is Invisible" & CrLf
End If
' ----- Window Style
LStyle = GetWindowLong(hWind, GWL_STYLE)
If LStyle And WS_BORDER Then ResultStr = ResultStr & _
"WS_BORDER" & CrLf
If LStyle And WS_CAPTION Then ResultStr = ResultStr & _
"WS_CAPTION" & CrLf
If LStyle And WS_CHILD Then ResultStr = ResultStr & _
"WS_CHILD" & CrLf
If LStyle And WS_CLIPCHILDREN Then ResultStr = ResultStr & _
"WS_CLIPCHILDREN " & CrLf
If LStyle And WS_CLIPSIBLINGS Then ResultStr = ResultStr & _
"WS_CLIPSIBLINGS " & CrLf
If LStyle And WS_DISABLED Then ResultStr = ResultStr & _
"WS_DISABLED " & CrLf
If LStyle And WS_DLGFRAME Then ResultStr = ResultStr & _
"WS_DLGFRAME " & CrLf
If LStyle And WS_HSCROLL Then ResultStr = ResultStr & _
"WS_HSCROLL " & CrLf
If LStyle And WS_MAXIMIZE Then ResultStr = ResultStr & _
"WS_MAXIMIZE " & CrLf
If LStyle And WS_MINIMIZE Then ResultStr = ResultStr & _
"WS_MINIMIZE " & CrLf
If LStyle And WS_OVERLAPPED Then ResultStr = ResultStr & _
"WS_OVERLAPPED " & CrLf
If LStyle And WS_POPUP Then ResultStr = ResultStr & _
"WS_POPUP " & CrLf
If LStyle And WS_SYSMENU Then ResultStr = ResultStr & _
"WS_SYSMENU " & CrLf
If LStyle And WS_THICKFRAME Then ResultStr = ResultStr & _
"WS_THICKFRAME " & CrLf
If LStyle And WS_VISIBLE Then ResultStr = ResultStr & _
"WS_VISIBLE " & CrLf
If LStyle And WS_VSCROLL Then ResultStr = ResultStr & _
"WS_VSCROLL " & CrLf
' ----- Display the string in a message box
MsgBox ResultStr, 48, gsAPPNAME
End Sub

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.

Incorporating Menu Functions

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.

Customizing Menus with Bitmaps

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:

Declare Function ModifyMenu% Lib "User" (ByVal hMenu%, ByVal _
nPosition%, ByVal wFlags%, ByVal wIDNewItem%, ByVal _
lpString As Any)
hMenu : Handle to the Menu being manipulated obtained from _
GetMenu/GetSubMenu
nPosition : Position of menu item being manipulated - _
numbered from zero
wFlags : Windows Flags to indicate how modification can be done
lpString : String used as caption for menu item, or Long _
identifying a bitmap

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:

Declare Function GetMenu% Lib "User" (ByVal hWnd%)
hWnd : Handle to the Window

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:

Declare Function GetSubMenu% Lib "User" (ByVal hMenu%, ByVal nPos%)
hMenu : Handle to the Menu
nPos : Position of the sub Menu within hMenu, numbered from zero

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.

Declare Function GetMenuItemID% Lib "User" (ByVal hMenu%, _
ByVal nPos%)
hMenu : Handle to the Menu
nPos : Position of the menu item within hMenu, numbered from zero

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.

Using 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.

Declare Function TrackPop-upMenu% Lib "User" (ByVal hMenu%, ByVal _
wFlags%, ByVal x%, ByVal y%, ByVal nReserved%, ByVal hWnd%, _
lpRect As Any)

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.

Displaying Bitmaps in Menus

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

Sub LoadMenuFonts()
'
' For each font in the array, create a bitmap and load it into
' the menu using API calls
'
Dim hMenu As Integer ' Handle to the menu
Dim hSubMenu As Integer ' Handle to the sub menu
Dim i As Integer, J As Integer ' Misc counters
Dim menuID As Integer ' Menu item ID
' ----- Load Font Names into Array
FontList(0) = "Arial"
FontList(1) = "MS Sans Serif"
FontList(2) = "Courier New"
FontList(3) = "System"
FontList(4) = "Times New Roman"
' ----- Change "Font" menuitem to null string: This will cause
' ----- it to be invisible to the user, yet loaded for our use
mnuFont.Caption = ""
' ----- Get a handle to the Form Menu
hMenu = GetMenu(hWnd)
' ----- Get a handle to Form Menu's Sub Menu
' - at position 1 = "Font"
hSubMenu = GetSubMenu(hMenu, 1)
' ----- Load items in control array - this ensures that they
' ----- are loaded in memory and are made available to us.
For i = 1 To 4
' ----- Menu item control array that holds a font name
Load mnuFontList(i)
' ----- Picture box control array that holds a bitmap
Load Picture1(i)
Next
For i = 0 To 4
' ----- Place new font in picture box
Picture1(i).FontName = FontList(i)
Picture1(i).FontSize = 14
Picture1(i).Width = Picture1(i).TextWidth(FontList(i))
' ----- Provide enough space for the name to be visible
Picture1(i).Height = Picture1(i).TextHeight(FontList(i)) _
* 1.5
' ----- Print the name in the Picture box
Picture1(i).Print FontList(i)
' ----- Create a Persistent Bitmap in the Picture Box
Picture1(i).Picture = Picture1(i).Image
' ----- Get the Menu ID for this sub menu
menuID = GetMenuItemID(hSubMenu, i)
' ----- Place the Persistent Bitmap on the menu
' at the same location
J = ModifyMenu(hMenu, menuID, MF_BYCOMMAND Or MF_BITMAP, _
menuID, CLng(Picture1(i).Picture))
Next
End Sub

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

Private Sub Text1_MouseDown(button As Integer, Shift As Integer, _
x As Single, Y As Single)
'
' If the button pressed is the right mouse button,
' then display pop-up menu
'
If button And RIGHT_BUTTON Then Pop-upMenu mnuFont
End Sub

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.

Graphics Functions

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.

GDI Objects—The Device Contexts

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.

Creating and Selecting GDI objects

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.

Declare Function GetDC Lib "User" (ByVal hWnd As Integer) _
As Integer

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.

Declare Function CreateDC Lib "GDI" (ByVal lpDriverName _
As String, ByVal lpDeviceName As String, ByVal lpOutput _
As String, ByVal lpInitData As String) As Integer
Declare Function CreateCompatibleDC Lib "GDI" (ByVal hDC _
As Integer) As Integer

To create a DC compatible with the screen, you can use the CreateDC function as follows:

Dim hScrDC as integer
hScrDC = CreateDC("DISPLAY", Null, Null, Null)

To create a DC in memory that is compatible with the DC of an existing window, you can use the CreateCompatibleDC function as follows:

Dim hCompatDC as integer
hCompatDC = CreateCompatibleDC(MyWinHwnd)

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.

Declare Function SelectObject Lib "GDI" (ByVal hDC As Integer, _
ByVal hObject As Integer) As Integer

This ensures that the object you desire is currently the active object within the DC and subsequent graphic routines can access that object.

Displaying Bitmaps with Special Effects

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.

Declare Function BitBlt Lib "GDI" (ByVal hDestDC As Integer, _
ByVal X As Integer, ByVal Y As Integer, ByVal nWidth _
As Integer, ByVal nHeight As Integer, ByVal hSrcDC _
As Integer, ByVal XSrc As Integer, ByVal YSrc _
As Integer, ByVal dwRop As Long) As Integer

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.

Declare Function StretchBlt% Lib "GDI" (ByVal hDC%, ByVal X%, _
ByVal Y%, ByVal nWidth%, ByVal nHeight%, ByVal hSrcDC%, _
ByVal XSrc%, ByVal Ysrc%, ByVal nSrcWidth%, ByVal _
nSrcHeight%, ByVal dwRop&)

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.

Type SlideType ' Structure holding a single slide display _
information
szFileName As String ' Graphic file to be displayed
iFX As Integer ' Special effect to use
iSpeed As Integer ' Speed of transition effect
iWait As Integer ' Milliseconds to wait before _
yielding
fClearBG As Integer ' Flag to clear background _
before display
fFillWindow As Integer ' Flag to indicate whether _
entire window is filled
End Type

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

; BMPFX4.INI
; INI file containing Slide information
[SlideInfo]
MaxSlides=5
[Slide1]
FileName=bmpfx1.bmp
SpecialFX=1
Speed=2
Wait=1
ClearBG=
FillWindow=0
[Slide2]
FileName=bmpfx2.bmp
SpecialFX=2
Speed=2
Wait=1
ClearBG=
FillWindow=0
[Slide3]
FileName=bmpfx3.bmp
SpecialFX=3
Speed=1
Wait=1
ClearBG=
FillWindow=0
[Slide4]
FileName=bmpfx4.bmp
SpecialFX=5
Speed=4
Wait=1
ClearBG=
FillWindow=0
[Slide5]
FileName=bmpfx5.bmp
SpecialFX=6
Speed=4
Wait=1
ClearBG=
FillWindow=0

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:

0 = Normal display, no effect.
1 = Push Bitmap from Left to Right
2 = Push Bitmap from Right to Left
3 = Push Bitmap from Top to Bottom
4 = Push Bitmap from Bottom to Top
5 = Display as Horizontal blinds
6 = Display as Vertical blinds

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:

defaultext = "*.INI"
dialogtitle = "Choose Presentation Data file to Open"
filename = "*.INI"
filter = "Presentation Data (*.INI)|*.INI|All _
Files (*.*)|*.*|"

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.

Declare Function BitBlt Lib "gdi" (ByVal hDC1 As Integer, _
ByVal x1 As Integer, ByVal y1 As Integer, ByVal wd As _
Integer, ByVal ht As Integer, ByVal hDC2 As Integer, _
ByVal x2 As Integer, ByVal y2 As Integer, ByVal _
pattern As Long) As Integer
Declare Function GetDC Lib "user" (ByVal hWnd As Integer) _
As Integer
Declare Function GetDesktopWindow Lib "user" () As Integer
Declare Function ReleaseDC Lib "user" (ByVal hWnd As Integer, _
ByVal hDC As Integer) As Integer
Declare Function CreateCompatibleDC% Lib "gdi" (ByVal hDC%)
Declare Function SelectObject% Lib "gdi" (ByVal hDC%, _
ByVal hObject%)
Declare Function StretchBlt% Lib "gdi" (ByVal hDC%, ByVal X%, _
ByVal Y%, ByVal nWidth%, ByVal nHeight%, ByVal hSrcDC%, _
ByVal XSrc%, ByVal YSrc%, ByVal nSrcWidth%, ByVal _
nSrcHeight%, ByVal dwRop&)
Declare Sub GetClientRect Lib "user" (ByVal hWnd%, lpRect As RECT)
Declare Function DeleteDC% Lib "gdi" (ByVal hDC%)
Declare Function DeleteObject% Lib "gdi" (ByVal hObject%)
Declare Function CreateCompatibleBitmap Lib "gdi" (ByVal hDC _
As Integer, ByVal nWidth As Integer, ByVal nHeight As _
Integer) As Integer
Declare Function GetPrivateProfileInt Lib "Kernel" (ByVal _
lpApplicationName As String, ByVal lpKeyName As String, _
ByVal nDefault As Integer, ByVal lpFileName As String) _
As Integer
Declare Function GetPrivateProfileString% Lib "Kernel" (ByVal _
lpApplicationName$, ByVal lpKeyName As Any, ByVal _
lpDefault$, ByVal lpReturnedString$, ByVal nSize%, _
ByVal lpFileName$)
Declare Function PatBlt Lib "gdi" (ByVal hDC As Integer, ByVal X _
As Integer, ByVal Y As Integer, ByVal nWidth As Integer, _
ByVal nHeight As Integer, ByVal dwRop As Long) As Integer
This BAS file also contains all the code to create the special effects. The special effects themselves are enumerated using global constants.
' ----- Special Effects defined
Global Const BMPFX_NORMAL = 0
Global Const BMPFX_LEFT_TO_RIGHT = 1
Global Const BMPFX_RIGHT_TO_LEFT = 2
Global Const BMPFX_TOP_TO_BOTTOM = 3
Global Const BMPFX_BOTTOM_TO_TOP = 4
Global Const BMPFX_HORIZONTAL_BLINDS = 5
Global Const BMPFX_VERTICAL_BLINDS = 6

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

Sub DisplayPic(iCurSlide As Integer)
Dim i As Integer
Dim retval As Integer ' Value returned by functions
Dim ScreenhWnd As Integer, ScreenDC As Integer
Dim hShadowDC%
Dim OldBM%
Dim hShadowBMP%
Dim hTempBitmap%
Dim rc As RECT
Dim dyFactor%, j%, dx%, dy%, result%
Dim fOriginalSize As Integer
Dim nWidth%, nHeight%, X%, Y%
Dim nSrcWidth%, nSrcHeight%, XSrc%, YSrc%
Dim hCompatDC%
Dim iPropFactor%
Dim Start As Long
' ----- Load the file into picture1 -
' use VB LoadPicture for simplicity
BmpFX.Picture1.Picture = LoadPicture(ArySlides(iCurSlide). _
szFileName)
' ----- Create a compatible DC for our form
hCompatDC% = CreateCompatibleDC(BmpFX.hDC)
' ----- Select the bitmap into the compatible DC
OldBM% = SelectObject(hCompatDC%, BmpFX.Picture1.Picture)
' ----- Dimensions of source bitmap
GetClientRect BmpFX.Picture1.hWnd, rc
nSrcWidth% = rc.right - rc.Left
nSrcHeight% = rc.bottom - rc.Top
XSrc% = rc.Left
YSrc% = rc.Top
' ----- Dimensions of destination
GetClientRect BmpFX.hWnd, rc
If ArySlides(iCurSlide).fFillWindow Then
' ----- Destination is entire window client area
nWidth% = rc.right - rc.Left
nHeight% = rc.bottom - rc.Top
X% = rc.Left
Y% = rc.Top
Else
' ----- Destination is bitmap centered in client area
nWidth% = nSrcWidth%
nHeight% = nSrcHeight%
X% = rc.Left + Abs((rc.right - rc.Left) - nWidth%) / 2
Y% = rc.Top + Abs((rc.bottom - rc.Top) - nHeight%) / 2
End If
' ----- Do we need to clear the background first ?
If ArySlides(iCurSlide).fClearBG Then
retval = PatBlt(BmpFX.hDC, X%, Y%, nWidth%, nHeight%, _
WHITENESS)
End If
' ----- Carry out the Special Effects
Select Case ArySlides(iCurSlide).iFX
Case BMPFX_NORMAL
' ----- display without any effect
result = StretchBlt(BmpFX.hDC, X%, Y%, nWidth%, _
nHeight%, hCompatDC, 0, 0, nSrcWidth%, nSrcHeight%, SRCCOPY)
Case BMPFX_LEFT_TO_RIGHT
' ----- Push Left to Right
iPropFactor% = nWidth% / nSrcWidth%
For i% = 0 To nSrcWidth% Step (ArySlides(iCurSlide). _
iSpeed * iPropFactor)
retval% = StretchBlt%(BmpFX.hDC, X%, Y%, (i% * _
iPropFactor), nHeight%, hCompatDC%, XSrc%, YSrc%, i%, _
nSrcHeight%, SRCCOPY)
Next i%
Case BMPFX_RIGHT_TO_LEFT
' ----- Push Right to Left
iPropFactor% = nWidth% / nSrcWidth%
For i% = 0 To nSrcWidth% Step (ArySlides(iCurSlide). _
iSpeed * iPropFactor)
retval% = StretchBlt%(BmpFX.hDC, X% + _
(nSrcWidth% - i%) * iPropFactor%, Y%, i% * iPropFactor%, _
nHeight%, hCompatDC%, nSrcWidth% - i%, YSrc%, i%, _
nSrcHeight%, SRCCOPY)
Next i%
Case BMPFX_TOP_TO_BOTTOM
' ----- Push Top to Bottom
iPropFactor% = nHeight% / nSrcHeight%
For i% = 0 To nSrcHeight% Step (ArySlides(iCurSlide). _
iSpeed * iPropFactor)
retval% = StretchBlt%(BmpFX.hDC, X%, Y%, nWidth%, _
i% * iPropFactor%, hCompatDC%, XSrc%, YSrc%, nSrcWidth%, _
i%, SRCCOPY)
Next i%
Case BMPFX_BOTTOM_TO_TOP
' ----- Push Bottom to Top
iPropFactor% = nHeight% / nSrcHeight%
For i% = 0 To nSrcHeight% Step (ArySlides(iCurSlide). _
iSpeed * iPropFactor)
retval% = StretchBlt%(BmpFX.hDC, X%, Y% + _
(nSrcHeight% - i%) * iPropFactor%, nWidth%, i% * _
iPropFactor, hCompatDC%, XSrc%, nSrcHeight% - i%, _
nSrcWidth%, i%, SRCCOPY)
Next i%
Case BMPFX_HORIZONTAL_BLINDS
' ----- Display as Horizontal Blinds
' ----- We need a third DC as our workspace -
' create it now
hShadowDC = CreateCompatibleDC(BmpFX.hDC)
' ----- Create a compatible bitmap as working area
hShadowBMP = CreateCompatibleBitmap(BmpFX.hDC, _
nWidth%, nHeight%)
' ----- Select the bitmap into the Shadow DC
hTempBitmap = SelectObject(hShadowDC, hShadowBMP)
' ----- How many slats will we be having:
dyFactor = nSrcHeight% / 10
' ----- For each row of pixels in each slat
For j = 1 To dyFactor Step ArySlides(iCurSlide).iSpeed
' ----- Copy the screen image into shadow dc.
result = BitBlt(hShadowDC, 0, 0, nWidth%, _
nHeight, BmpFX.hDC, X%, Y%, SRCCOPY)
' ----- For each slat
For i = 1 To dyFactor
' ----- Now manipulate the ShadowDC image
result = StretchBlt(hShadowDC, 0, (i * _
dyFactor), nSrcWidth%, j, hCompatDC, 0, (i * dyFactor), _
nSrcWidth%, j, SRCCOPY)
Next i
' ----- And copy the reconstructed image
' back to screen
result = StretchBlt(BmpFX.hDC, X%, Y%, _
nSrcWidth%, nSrcHeight, hShadowDC, 0, 0, nSrcWidth%, _
nSrcHeight%, SRCCOPY)
Next j
' ----- Make sure entire bitmap has been copied
result = StretchBlt(BmpFX.hDC, X%, Y%, nWidth%, _
nHeight%, hCompatDC, 0, 0, nSrcWidth%, nSrcHeight%, SRCCOPY)
' ----- Delete objects and restore resources
i% = SelectObject(hShadowDC, hTempBitmap)
i% = DeleteObject(hShadowBMP)
i% = DeleteDC(hShadowDC)
Case BMPFX_VERTICAL_BLINDS
' ----- Display as Vertical Blinds
' ----- We need a third DC as our workspace -
' create it now
hShadowDC = CreateCompatibleDC(BmpFX.hDC)
' ----- Create a compatible bitmap as working area
hShadowBMP = CreateCompatibleBitmap(BmpFX.hDC, _
nWidth%, nHeight%)
' ----- Select the bitmap into the Shadow DC
hTempBitmap = SelectObject(hShadowDC, hShadowBMP)
' ----- How many slats will we be having:
dyFactor = nSrcWidth% / 10
' ----- For each row of pixels in each slat
For j = 1 To dyFactor Step ArySlides(iCurSlide).iSpeed
' ----- Copy the screen image into shadow dc.
result = BitBlt(hShadowDC, 0, 0, nWidth%, _
nHeight, BmpFX.hDC, X%, Y%, SRCCOPY)
' ----- For each slat
For i = 1 To dyFactor
' ----- Now manipulate the ShadowDC image
result = StretchBlt(hShadowDC, (i * _
dyFactor), 0, j, nSrcHeight%, hCompatDC, (i * dyFactor), _
0, j, nSrcHeight%, SRCCOPY)
Next i
' ----- And copy the reconstructed image
' back to screen
result = StretchBlt(BmpFX.hDC, X%, Y%, _
nSrcWidth%, nSrcHeight, hShadowDC, 0, 0, nSrcWidth%, _
nSrcHeight%, SRCCOPY)
Next j
' ----- Make sure entire bitmap has been copied
result = StretchBlt(BmpFX.hDC, X%, Y%, nWidth%, _
nHeight%, hCompatDC, 0, 0, nSrcWidth%, nSrcHeight%, SRCCOPY)
' ----- Delete objects and restore resources
i% = SelectObject(hShadowDC, hTempBitmap)
i% = DeleteObject(hShadowBMP)
i% = DeleteDC(hShadowDC)
End Select
' ----- Reselect old object into shadow DC
i% = SelectObject(hCompatDC%, OldBM%)
' ----- Delete DC, free up resources
i% = DeleteDC(hCompatDC%)
If OldBM% <> 0 Then
i% = DeleteObject(OldBM%)
End If
' ----- Wait for the time required
Start = Timer ' Set start time.
Do While Timer < Start + ArySlides(iCurSlide).iWait
DoEvents ' Yield to other processes.
Loop
End Sub

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 need a third DC as our workspace -
' create it now
hShadowDC = CreateCompatibleDC(BmpFX.hDC)
' ----- Create a compatible bitmap as working area
hShadowBMP = CreateCompatibleBitmap(BmpFX.hDC, _
nWidth%, nHeight%)
' ----- Select the bitmap into the Shadow DC
hTempBitmap = SelectObject(hShadowDC, hShadowBMP)
Once we have a memory DC and have selected a bitmap object into it, we can now copy the image from the form DC into the memory DC:
' ----- Copy the screen image into shadow dc.
result = BitBlt(hShadowDC, 0, 0, nWidth%, nHeight, _
BmpFX.hDC, X%, Y%, SRCCOPY)

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.

' ----- And copy the reconstructed image back to screen
result = StretchBlt(BmpFX.hDC, X%, Y%, nSrcWidth%, _
nSrcHeight, hShadowDC, 0, _
0, nSrcWidth%, nSrcHeight%, SRCCOPY)

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.

Multimedia Functions

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.

Using Sound

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.

Declare Function mciExecute Lib "MMSystem" (ByVal lpString _
as String) as Integer

Then in your application, you can have the following code to play the WAV file TADA.WAV:

Dim x as integer
x = mciExecute("play c:\windows\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.

Using Video

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.

Declare Function mciExecute Lib "MMSystem" (ByVal _
lpString as String) as Integer

And, in your application, you can call the previous function to play a AVI file:

Dim x as integer
x = mciExecute("play MyAVI.AVI")

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.

Using WinHelp Functions

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.

Declare Function WinHelp Lib "User" (ByVal hWnd As Integer, _
ByVal lpHelpFile As String, ByVal wCommand As Integer, _
dwData As Any) As Integer

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:

Type MULTIKEYHELP
mkSize As Integer
mkKeylist As String * 1
szKeyphrase As String * 253 ' Array length is _
arbitrary; may be changed
End Type

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.

Using the Windows API under Win32 Systems

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:

Graphic Routines

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.

DOS System Calls

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.

From Here...

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.