Killer Win32 API

Originally Presented at the

9th Annual Borland Conference

Denver, Colorado

August 9, 1998

 

Keith G. Chuvala, First Intermark Corp.

mailto:kgc@pdq.net  -  http://www.sckans.edu/~kgc

 

Download Source Codes

 

Introduction to the Win32 API for Visual dBASE Developers

The Win32 API (Application Programming Interface) is the developer’s connection to the inner and outer workings of Windows 95, 98, and NT.  The API is your gateway to a huge collection of functions that make up the core of Windows itself and the bulk of many of its applications.  Learning and understanding key components of the API and getting comfortable with the techniques involved in exploiting it allows us to not only produce applications with greater functionality, but also to better understand how Windows itself  “sees the world” and why it works the way it does.

 

Visual dBASE 7 provides a powerful development environment and a terrific object-oriented language, but like any other development product, it cannot be all things to all developers.  I’ve yet to encounter a language product that natively supports every capability we want or need to supply in our own applications.  But “under the hood” Microsoft has written myriad useful functions into Windows itself, and in many cases these functions supply part or all of the added functionality we desire.   Of course, Windows is not written in the dBASE language, so accessing those functions often requires some extra effort on our part.

 

Our goal is to make a Windows API function “look like” a regular Visual dBASE function that can be called by our VdB applications as needed.  As you’ll see there are also myriad constant values used by API functions, and these constants will need to be added to our applications as well.  In many cases, then, two or more steps are required to successfully access the functions available through the Windows API.  Typically we’ll create or use constants with #define, then describe (or “prototype”) the function using dBASE’s EXTERN command.

 

In this tutorial we will cover the fundamentals of accessing the API using these dBASE tools, then we’ll work forward, moving into specific important functions available to use through the API.  Along the way there is a lot of jargon and techno-speak, so be sure to take notes and ask questions as we go!  Finally we’ll look at a number of useful API-laden tools developed by dBASE developers around the world.

 

Deciphering Win32API.H and Friends

The Visual dBASE installation program creates a number of files that will be referenced throughout this tutorial.  These files are filled with #define and EXTERN statements that are required to access the various functions in the Windows API.  In most cases we will not have to write those statements ourselves, but rather will need to include these dBASE-supplied files in our programs and projects.

 

Win32API.H is actually little more than a “front end” to other API-related files.  The files we’ll be examining are found in the INCLUDE and SAMPLES directories under your Visual dBASE “home” directory.  If you don’t find these files in your installation, you might need to reinstall VdB.

 

Here are the files we’ll be working with from the Visual dBASE INCLUDE directory:

 

Win32API.h           Loads other .H files

WinBASE.h              Oodles of API constants

WinDEF.h                Type definitions that help us “fake” C data types

WinREG.h                Constants used with registry-related API

WinERROR.h           Windows error codes

WinGDI.h                Constants and such for drawing (Graphics Device Interface)

WinMISC.h              Constants for Networking, Shell, and other functions

WinUSER.h              User interface constants

StructAPI.h         Constants used with STRUCMEM.DLL

Win32API.prg      Monster-sized prototype/EXTERN program

 

In the Visual dBASE SAMPLES directory we’ll use:

 

StructAPI.prg    Prototypes/EXTERNs for STRUCMEM.DLL

Structure.prg    Custom class front end to STRUCMEM.DLL functions

Registry.prg      Custom class front end to Windows registry API

Win32API.wfm      VdB sample form illustrating various API functions

 

This is not a complete list of the files in the INCLUDE and SAMPLES directories; we simply won’t have time to cover all of them in any detail.  Instead we’ll concentrate on fundamental concepts and techniques that you can easily extend to other functions.

 

A quick word about the filenames above.  The ‘.h’ file extension denotes a “header file,” and originally came into wide use with the C language.  It’s such a common convention that Visual dBASE adheres to it.  Header files can include valid code of any kind, but the traditional (and highly recommended) standard convention is that only data type definitions, constant values, and such be stored in them.  So-called “executable” code (actual program logic) should be written in .PRG, .WFM, and other such files.  Hiding UDFs or other code in header files is permissible, but it goes against convention and makes that code harder to locate when you go looking for it 6 months down the road!

 

Using EXTERN

The EXTERN command is used to construct a prototype of a non-VdB function.  Such functions are typically API functions, or contained in vendor-supplied .DLL files.  In order for VdB to call a foreign function, it must be told via the EXTERN statement what argument(s) the function requires, the types of the argument(s), and what data type the function returns to dBASE, if any.

 

For our fist simple example, here’s the prototype for the WinAPI Beep() function, which simply beeps the speaker at a given frequency for a given duration, or in dBASE, will play the default sound if it has been setup via the Control Panel Sounds applet. 

 

Extern CINT Beep(CLONG, CLONG) kernel32

 

We’ll talk about CINT and CLONG in a bit.  For now, just type the command into the Command Window, or add it to a program file.  NOTE: The case of “Beep” must be matched exactly!  While dBASE ordinarily doesn’t care if commands are capitalized, all lowercase, or otherwise, other languages do, and the API functions are case-sensitive.  So you can’t use BEEP, beep, bEEp, or BeeP; it’s gotta be Beep.

 

The parameters (or arguments) in the prototype specify what data types dBASE will have to pass to the API function.  These must match up with the data types of the actual function as closely as possible.  dBASE does a decent job of translating things for us, but clearly there’s some non-dBASE lingo to learn here.  We’ll cover this in more detail in the “Data Types in EXTERN Statements” section that follows.

 

The final item in the statement is the name of the dynamic link library (“DLL”) or module in which the function is actually located.  If this is a built-in Windows module (Kernel32, User32, GDI32, ADVAPI32, SHELL32, etc.) or one which dBASE itself always loads (IDAPI32, for example), no filename extension need be specified.  For other functions, you’ll need to specify the full name (e.g. MPR.DLL, COMDLG32.DLL, etc.)

 

Now, if you goofed typing in our Beep line above, you’ll be greeted with an error, most likely something like this:

 

 

So try it again and get it right this time J.

 

To execute the function, simply type the function call in the Command Window as if it were any other dBASE function:

 

beep()

 

Your speaker will sing if you have a sound setup for the default beep. Note that I didn’t match the case here, yet it still works!  The above restriction on case sensitivity applies to prototyping the function only; in standard dBASE practice you can mix case at will in your dBASE code.  I recommend, however, that you stick as closely to convention as possible; it’s best to match the case of all API functions and constants exactly.  This makes for consistent and clear code, and besides, if the next guy or gal to come along and work on your code happens to have a background in C, they’ll try to strangle you if you mixed case arbitrarily!

 

Data Types in EXTERN Statements

In the previous example we used non-dBASE data types to prototype the Beep() function.  This is necessary because dBASE’s data types don’t always match up well with those of other languages. Since Windows and most all of its components are written in C or C++, we have to match up data types with those expected by functions written in those languages.  Here’s a list of built-in data types supported in Visual dBASE specifically for use with EXTERN, as reported in the Visual dBASE documentation:

 

dBASE

Keyword

 

As Pointer

dBASE

Data Type

 

Size

API (WindEF.H)

Data Type(s)*

CINT

CPTR CINT

Numeric

32 bits

INT

CLONG

CPTR CLONG

Numeric

32 bits

LONG

CSHORT

CPTR CSHORT

Numeric

16 bits

SHORT

WCHAR

CCHAR

 

Numeric

8 bits

CHAR

 

CSTRING

String

Null-terminated

LPSTR

LPCSTR

Most STRINGs

CHANDLE

 

Numeric

32 bits

HANDLE

CHWND

Most Hxxx types

CUINT

CPTR CUINT

Numeric

32 bits

UINT

WPARAM

CLONG

CPTR CULONG

Numeric

32 bits

LONG

DWORD

LPARAM

LRESULT

CUSHORT

CPTR CUSHORT

Numeric

16 bits

WORD

ATOM

CUCHAR

 

Numeric

8 bits

BYTE

CFLOAT

CPTR CFLOAT

Numeric

32 bits

FLOAT

CDOUBLE

CPTR CDOUBLE

Numeric

64 bits

LONGLONG

DWORDLONG

CLDOUBLE

CPTR CLDOUBLE

Numeric

80 bits

 

CLOGICAL

CPTR CLOGICAL

Logical

8 bits

BOOL

CVOID

 

none

N/A

VOID

 

*   In most cases, a single dBASE C-type will correspond to multiple API types.  When in doubt, check the WinDEF.h header file (see below).  This table lists only a few of the API types!

 

Some of these might seem hard to understand, but rather than go into the significance and nuances of each, suffice it to say at this point that your task is simply to match up the dBASE data type you’re using with the closest C-style data type.  When constructing EXTERN statements, you’ll just need to use the type specified in the API function’s documentation.  Which poses another challenge!

 

To illustrate the problem, load up Win32API.PRG in the source editor and find the EXTERN statement for the Beep() API function.  It reads like this:

 

extern BOOL Beep(DWORD, DWORD) kernel32

 

Uh oh.  BOOL and DWORD are not in our list of built-in types as listed above.  And in fact, most API documentation you use, be it from Inprise, Microsoft, or another vendor, will likely use a set of data type descriptors that aren’t native to any language in particular, but rather were derived specifically for work within the Windows framework.  Rather than defining a specific language’s data type, they instead describe a fundamental data type that any language should have a match for. 

 

The “fix” to this problem is supplied in the VdB-supplied WinDEF.H header file, which is a collection of #define statements mapping dBASE’s build-in C data types to these new windows data types.  Hence the following advice is important:

 

When working with Windows API functions, ALWAYS #include <WinDEF.h> in your program!  Otherwise you’ll have to convert documented data types by hand.

 

The WinAPI.PRG program #includes this file already, and as you’ll see, you’ll probably also want or need to include other .H files, depending on the API functions you are calling. During this tutorial and in this paper my goal is to make as many examples as possible run-able from the command window.  So most of the examples here will use the built-in types. 

What about Structures?

Another complication arises with some API functions.  In C, there is a mechanism called a struct, which allows the programmer to package two or more variables into one variable.  The variable itself is said to be “of type struct <struct_name>” where struct_name is whatever the programmer decided to call his new data type.  Even if you’re not a C programmer, you can see the effect in the following:

 

// simple.c: a simple structure

 

struct pixel

   {

   int xcoord;

   int yccord;

   int color;

   };

 

void main(void)

{

   struct pixel one_point;

}

 

In this short example we’ve created a new data type called “struct pixel,” which contains 3 variables (called “members”).  In the main() function we’ve declared one variable, called one_point.  This variable contains 3 members, per the definition of the data type, which are called one_point.xccord, one_point.ycoord, and one_point.color.  Look familiar?  Sure, it’s just like a dBASE object.  When we create a new form object with code like f = new form(), we know that f has members like Text, ColorNormal, Open, OnClose, etc.  Such is a struct, though structs as used by the Windows API most often contain only memory variables (properties, if you will), and not events or methods (though those elements can be contained in C++ structs, so beware!)

 

Here’s a fairly simple API function that returns the coordinates of a window’s client area.  Again, to facilitate typing this into the command window, I’ll use the built-in data types rather than the ones you’d find in the Windows API reference:

 

extern CLOGICAL GetClientRect( CHANDLE, CPTR ) USER32

 

The two arguments GetClientRect() expects are a CHANDLE, the window’s handle (the HWND property of a dBASE form, usually), and a CPTR, the address of a 16-byte structure in memory that will be used to store the coordinates.  So we need to create a 16-byte “buffer” that the function can use to store the individual coordinate values (one coordinate each 4 bytes – top, left, bottom, right).  And it’s this buffer that poses the problem

 

Visual dBASE has two problems with this kind of data type.  First, although dBASE sports the generic Object type into which we can package variables seemingly just like a C struct, in memory it’s built very differently, and the API functions that expect structs will also expect them to have C-style structure.  So what do we dBASE folks have at our disposal for packing data together in a very precise way?  Strings, that’s what.  We can pack a string with the data required and pass it to the API function like it were a C style struct.

 

Well, except for the second problem. J 

 

Visual dBASE 7.x is architected to be easily ported to different languages (the human kind, that is!)  To facilitate multiple character sets, some of which require more than one byte per character, VdB strings are Unicode, which means that (among other things) they are 2 bytes per character.  So building a C style struct with simple string concatenation (which is what we did in previous releases of Visual dBASE) no longer works; the resulting struct is 2 times too large, and data offsets are all incorrect.

 

So while we might be tempted to try this to get the bottom coordinate from bytes 8 and 9, it would not produce the correct results:

 

f = new form()

f.open()

cBuffer = space(16)         // This will produce 32 bytes!

GetClientRect(f.hwnd,cBuffer)

? val(cBuffer[8])+(val(cBuffer[9])*256)  // nothing there!

 

Fortunately the Visual dBASE gurus at Borland added some new capabilities aimed specifically at overcoming this obstacle.  The new String class includes methods SetByte() and GetByte() that allow us to set or get the value of a specific byte within a string.  So the corrected code for the above example might look like this:

 

f = new form()

f.open()

cBuffer = space(8)          // This will produce 16 bytes!

GetClientRect(f.hwnd,cBuffer)

? cBuffer.getbyte(8) + cBuffer.getbyte(9)*256  // Correct!

 

 

Included in the VdB SAMPLES directory you’ll find Structure.PRG and the attendant files StructAPI.H, StructAPI.PRG, and STRUCMEM.DLL.  These files offer a higher-level interface for exchanging structures with API functions.  The files contain internal documentation (particularly useful  are the comments in STRUCTURE.PRG, and with Win32API.WFM form in the SAMPLES directory demonstrates its use as well. 

 

Without getting too deep into the code, this code is intended to be used to create a custom class that takes on the form of the structure needed for an API call.  You create an instance of that class, and pass its Value property to the API function.  For now we’ll show an example using the GetClientRect() function to get some useful information, namely the size of the dBASE shell’s client area.

 

// STRUCTDEMO.PRG: Demonstrate essentials of STRUCTURE.PRG

// Uses GetClientRect() to retrieve _App.FrameWin client size

 

#include <structapi.h>  // type definitions

 

set procedure to _dbwinhome+"samples\structure" additive

 

if type("GetClientRect") # "FP"

   extern CLOGICAL GetClientRect( CHANDLE, CPTR ) USER32

endif

 

clear

sBuff = new ClientRect_Struct()  // Create and instance of the stucture

 

GetClientRect(_app.framewin.hwnd,sBuff.value) // call the API function

 

? "Size of Visual dBASE Client Rectangle"

? "-------------------------------------"

? "Height:", sBuff.getmember("Bottom")

? "Width: ", sBuff.getmember("Right")

 

 

CLASS ClientRect_Struct of structure   // structure definition per 

                                       // Win32API.h

   super::addMember( TYPE_DWORD, "Top")

   super::addMember( TYPE_DWORD, "Left")

   super::addMember( TYPE_DWORD, "Bottom")

   super::addMember( TYPE_DWORD, "Right")

endclass

 

Of course you don’t have to create a custom class; you could build the structure on the fly.  Encapsulating the structure in a class makes it far easier to reuse, of course.

 

Character Buffer Considerations

Related to the above concern about Unicode strings, some API functions return strings all neatly filled in for us.  But they’re regular non-Unicode strings, and dBASE’s can’t work with them.  Again, we can use GetByte to do the conversion for us.  Among the code listings at the end of this paper you’ll find Jim Sare’s fix for GetModuleFileName(), which returns the executable program name associated with a running application.  The function fills in a string you supply when it is called, but it fills the string with 1-byte characters, so we have the reverse problem we had with structures.  Jim’s code is a fine example of how you can convert these character buffers into useful strings.

 

Name Changes between Win3x and Win32

Windows 95 (and Windows 98) are as backward compatible as possible, which is good.  And which is bad.  Numerous compromises had to be made to facilitate both the old 16-bit Windows API and the new 32-bit Windows API.  There are numerous API functions that are available to both, so of course one or the other name had to change, and it is in almost all cases the 32-bit version.  Often the name change is simply the addition of  “A” to the end of the function name (e.g. “FindWindow” for 16 bit and “FindWindowA” for 32-bit.  There are also numerous “extended” API functions that have been added, and frequently an “Ex” suffix is added to the original function name (e.g. “FindWindowEx”).

 

The most noticeable change for developers who did some WinAPI work in Visual dBASE 5.x is that the fundamental Windows modules have changed.  In the 16-bit world (including compatibility mode in Win9x and WinNT), the modules that contain most of our WinAPI functions were called USER and KERNEL.  In Win32 they are USER32 and KERNEL32, respectively. 

The Windows Registry

Those who use Visual dBASE to create applications for distribution should pay attention to a few issues that might fall under the guise of “well-behaved” Windows applications.  In the days of Windows 3.1, it was common practice (and in fact encouraged by Microsoft) to use Windows API functions to create and use localized .INI files to hold persistent configuration and other state information.  In the Win32 world this practice is changing, as we are supposed to save all such information now in the Windows registry.  I’ll not go into the debate over the propriety or wisdom of relying on the registry, but it’s worthwhile to understand something about the registry and how to access it with Win32 API functions.

 

Ideally, all configuration information for the system should reside in the registry.  In fact, if you want your program to qualify for the “Compatible with Windows 9x” logo, it must be registry-oriented.

 

Microsoft itself has moved almost everything to the Registry.  Even the venerable configuration files CONFIG.SYS and AUTOEXEC.BAT are utterly unnecessary in Win9x, though their use is supported for backward compatibility and for those applications and devices that still rely on 16-bit drivers and such.

 

So what kind of information would a VdB developer want to store in the Registry, anyway?  Any settings that must remain persistent across instances of the application, maintained even when the machine is powered down.  User preferences, serial number information, process statuses, etc. are all possibilities.  I like my MDI applications to “remember” the sizes and locations of MDI child windows between invocations, so I sometimes store this information.  You can think of the Registry as similar to a database table in that respect; a long-term storage area for whatever kind of information you wish to track.  But it’s not a database table, so it’s important to keep the volume of information in mind.  Large registries cause large problems when they get corrupted.  So if you need hundreds of settings or other large amounts of information stored, it might be best to save those things in a .DBF or .MEM file and then store in the registry the path and filename of that file.

 

A well-behaved Win9X application is supposed to save a bit of information about itself in the Registry.  The InstallShield Express installer that comes with Visual dBASE handles this for us, and even allows the option of building the registry keys necessary for uninstalling the application.  So much of this burden is off our shoulders from the start.  Even so, it’s good for any developer to have at least a passing familiarity with the registry’s structure and use.

 

The tool most often used to access the registry is RegEdit, normally located in your Windows home directory.  If you’ve never used RegEdit, and you’re sitting at a Win9X PC as you read this, start the program now and navigate around a bit with the mouse to get a feel for the layout of the data contained in the registry.

 

The above illustration is a screenshot of  RegEdit on the machine I’m on now, which is running Win98. RegEdit looks the same in Win95 and Win98, and everything I say here applies to both as far as I can tell!

 

Each key (displayed on the left side of the display) is a container for other registry information, be it in the form of other keys or actual data.  The value entries for a given key are displayed in the right window pane when the key is selected on the left.  If you’ve poked around much you probably have noticed that the hierarchy of keys can get quite deep.  There’s no way we could go into exhaustive detail on any one of them, let alone all of them, so let’s discuss the 6 major keys and their use.

 

HKEY_CLASSES_ROOT is full of Object Linking and Embedding (OLE)-related information.  You’ll find scores of file extensions, shell extension information used by Explorer, etc.  Leave it alone if you don’t know what you’re doing!

 

HKEY_CURRENT_USER contains the user profile of the currently active user.  As you probably know, Win9x supports multiple user profiles on a single machine, so remember that information under this key does not necessarily affect everyone who uses the machine.  Program groups, printers, network connections, desktop settings, and other such things are stored here.  Well-behaved applications will often store their user preferences here, so that different users can have different preferences, even on the same machine.

 

HKEY_LOCAL_MACHINE contains information about the PC you’re running on.  Device driver, startup, and other system-wide data are stored here.  Don’t mess with this!

 

HKEY_USERS contains all the currently loaded user profiles (including the one detailed in HKEY_CURRENT_USER).  In fact, HKEY_CURRENT_USER is technically a subkey of this key. HKEY_USERS is the key under which the machine’s default user preferences are stored.  Note that if you’re looking at the registry on a server (Win9x or WinNT), this key does not normally affect logged-in users; their profiles are in the registries on their local PCs.

 

HKEY_CURRENT_CONFIG is  key designed specifically for Win9x, though you might find it on a WinNT machine as well.  It is predefined under Win9x, though, so they’ll always have it. HKEY_CURRENT_CONFIG is actually mapped to a branch under HKEY_LOCAL_MACHINE, but is also available at the higher level for ease of access.  This is they key where you want to store your application’s non-user-specific configuration settings and information, e.g. server or backend database name(s).

 

There are a few specific subkeys you and/or your applications should know about and possible even create or modify.  The “application information key” is

 

[HKEY_LOCAL_MACHINE]\Software\Company\Product\Version

 

InstallShield Express builds this key for us if we use it for the application’s installer, but it doesn’t fill in too much data by default.  Here’s a screen shot of our company’s TimeClock application’s entries in my registry:

 

 

As you can see, I didn’t add of change anything under InstallShield’s “Make Registry Changes” section for this project.  Fortunately ISE is smart enough to insert the keys for the company name, program name, and version number for me.  These are taken from the primary setup screen when you start the process to deploy the application, as you can see:

 

 

The values returned for the “Company” and “Name” items are supplied by the machine and/or user when the application is installed.

 

Now, how can my applications get at this data?  There are a number of API functions involved in accessing the registry, and they can be a bit intimidating.  Once again there is sample code we can use in our own apps to get the functionality quickly without having to learn all the ins and outs of the registry functions.

 

In your SAMPLES directory is a file called REGISTRY.PRG.  This program contains a custom class we can use to get at registry data easily.  Here’s a simple program that uses the class to read the information in the keys we just looked at from my Time Clock application:

 

// REGDEMO.PRG: Demonstrate REGISTRY.PRG usage

 

#include <windef.h>

#include <winreg.h>

 

set procedure to _dbwinhome+"\samples\registry" additive

 

Reg = new Registry(HKEY_LOCAL_MACHINE,;

                   "SOFTWARE\First Intermark Corp.\FU$$ Time Clock\1.0")

? Reg.QueryValue("Company") // Prints "First Intermark Corp."

? Reg.QueryValue("Name")    // Prints "Keith Chuvala"

 

Reg.close()

 

Basically, you create a Registry object initialized with the upper level key (in this case HKEY_LOCAL_MACHINE, which is #define’d in WinREG.h) as the first argument, and the subkey as the second arguments.  Then you use the various methods of the class to add, set, delete, or query values under the specified key.

 

Other methods in this class include DeleteKey, DeleteValue, EnumValue (this one returns an array with the names of each value in the current key!), QueryKeyName, and SetValue.  Take some time to play with this useful class!

 

Some Useful API Functions

Every window has a so-called “handle” which is a unique integer number identifying that particular window.  We can’t set this value; Windows does this for us when we create a new form.  In Visual dBASE we see this value as the form’s HWND property.  Many API functions require that this handle be passed as an argument to the function.  If the window in question was created with Visual dBASE, simply passing the HWND property is all that is required.  If your program needs to interact with other active windows active at the time, you can use API functions to help you get their handle.  Here we’ll look at a few useful functions that demonstrate how to ascertain a “foreign” HWND, and how to use it.

 

/* FindWin.PRG: Find a top-level window by its title text

   Useful for detecting when an app is already running.

   Does NOT search child windows

*/

 

Procedure FindWin

   parameter cTitleText

   if type("cTitleText") # "C"

      return 0

   endif

   if type("FindWindow") # "FP"

      extern cHandle FindWindow(CSTRING, CSTRING) User32 ;

             from "FindWindowA"

   endif

return FindWindow(0, cTitleText)

 

 

// ShowWin.PRG: Set the display state of a running Window

Procedure ShowWin

   parameter nHWND, nHow

   /* Values for nHow:

      0 - Hide completely from view

      1 – Normal window

      2 - Minimize

      3 - Maximize

   */

   if type("nHWND") # "N"

      return

   else

      if nHWND == 0

         return

      endif

   endif

   if type("nHow") # "N"

      nHow = 1 // default to normal

   endif

   if type("ShowWindow") # "FP"

      extern cHandle ShowWindow(cHandle, cInt) User32

   endif

   ShowWindow(nHWND, nHow)

return

 

 

You can use FindWin() and ShowWin() together to prevent multiple instances of your application from running simultaneously.  Just search for the title text of your app’s primary window, and if found, bow out gracefully.  Try something like this in your startup code:

 

/* RunOnce.PRG: Demonstrate use of FindWin and ShowWin to ensure that

   only one instance of an app runs at any given time

*/

 

set procedure to findwin additive

set procedure to showwin additive

 

nHWND = findwin("My App")

if nHWND # 0

   showwin(nHWND,1)  // it's already out there, so show it!

   return  // For a compiled app this would be QUIT

endif

 

define form f property text "My App", mdi .f., left 0, top 0

f.open()

 

The next function, LockWin, was originally posted to the Borland newsgroups by either Romain Streiff or Jim Sare; I need to find out which and credit the right gentleman!  It’s a great little function that tells Windows to NOT update a particular window’s display; it blocks repainting of the form.  This can be used to prevent form “flicker” that so often happens when constructing a busy form, or when form elements are updated in an annoying fashion during looping operations and such.  In my own incarnation of the function, I pass it two arguments, the first a logical value to indicate whether we’re locking or “unlocking”, and the HWND of the form to lock.  If no HWND is passed, it is assumed the whole desktop window should be locked. 

/* LockWin.PRG: Prevents the updating of a window. 

   Useful for avoiding “flicker” during some operations.

*/

Procedure LockWin

   parameter lLock, nHandle

   local nHWND

   if type("nHandle") # "N"   // default to desktop if omitted

      nHWND = 0

      if type("GetDesktopWindow") # "FP"

         extern CHANDLE GetDesktopWindow (CVOID) USER32

      endif

      nHWND = GetDeskTopWindow()

   endif

 

   if type("lLock") # "L"   // default to locking it if omitted

      lLock = .t.

   endif

 

   nHWND = iif(lLock, nHandle, 0) // set to 0 to unlock

 

   if type("LockWindowUpdate") # "FP"

      extern CLOGICAL LockWindowUpdate(CHANDLE) User32

   endif

 

   LockWindowUpdate(nHWND)

return

 

 

One of the features of Win9x that I’ve come to appreciate is the ubiquitous filename extension recognition the registry provides, allowing us to operate in a more document-centric, rather than application-centric, fashion.  As you undoubtedly know, if you double click on, say, a .BMP file in the Windows Explorer, the application associated with that file type is automatically launched, and the image you selected is opened up for display, editing, etc. 

 

At the DOS level you can get the same behavior using the START command.  START WINLOGO.BMP will accomplish the same effect as double-clicking on WINLOGO.BMP.  This document-centric view of the world means that I don’t worry so much about what applications are out there, so much as what document(s) I want to work with.

 

Calling external editors or other programs from within Visual dBASE 5.x apps under Windows 3.x required ascertaining either the actual path to the application desired, or figuring out OLE hooks to accomplish the same.  In Win9x a simple START will do the trick.  But running the DOS START command results in an ugly (if temporary) DOS window on the screen.  A better option is to do what Windows Explorer does; run the ShellExecute API function.

 

ShellExecute can open executable files or document files, can print document files, and can even be used to invoke explorer windows on a specified folder. As you might imagine, ShellExecute takes several arguments to handle those varied functions.  At this point it’s useful to rely on the docs, which in this case is the Microsoft Win32 help file.  Here’s the prototype for ShellExecute and a few of the notes that go along with it:

 

HINSTANCE ShellExecute(

 

 HWND hwnd,             // handle to parent window

 LPCTSTR lpOperation,   // pointer to string that specifies operation to perform

 LPCTSTR lpFile,        // pointer to filename or folder name string

 LPCTSTR lpParameters,  // pointer to string that specifies executable-file parameters

 LPCTSTR lpDirectory,   // pointer to string that specifies default directory

 INT nShowCmd           // whether file is shown when opened

 );                    

Parameters

 

Hwnd          Specifies a parent window. This window receives any message boxes that an application produces. For example, an application may report an error by producing a message box.

 

LpOperation   Pointer to a null-terminated string that specifies the operation to perform. The following operation strings are valid:

 

String        Meaning

"open"        The function opens the file specified by lpFile. The file can be an executable file or a document file. The file can be a folder to open.

"print"       The function prints the file specified by lpFile. The file should be a document file. If the file is an executable file, the function opens the file, as if "open" had been specified.

"explore"     The function explores the folder specified by lpFile.

 

 

The lpOperation parameter can be NULL. In that case, the function opens the file specified by lpFile.

 

LpFile        Pointer to a null-terminated string that specifies the file to open or print or the folder to open or explore. The function can open an executable file or a document file. The function can print a document file.

 

LpParameters  If lpFile specifies an executable file, lpParameters is a pointer to a null-terminated string that specifies parameters to be passed to the application.

If lpFile specifies a document file, lpParameters should be NULL.

 

LpDirectory   Pointer to a null-terminated string that specifies the default directory.

 

NShowCmd      If lpFile specifies an executable file, nShowCmd specifies how the application is to be shown when it is opened.

 

You can see why having a good API reference is essential for success in working with API functions.  The Win32.HLP file has loads of information, but no examples.  There are numerous books available that detail the workings of many of the API functions and offer examples to follow. 

 

/* WinSTART.PRG: uses the ShellExecute API call

   to execute a Windows "start" without dropping to DOS

*/

function WinStart

   parameter cCommand

   if type("cCommand") # "C"

      return

   endif

   if type("ShellExecute") # "FP"

      extern cHandle ShellExecute(cHandle,cstring,cstring,cstring,cstring,CINT) ;

             shell32 from "ShellExecuteA"

   endif

 

return ShellExecute(0,"open",cCommand,null,null,1)

 

WinSTART assumes you’re going to “open” the document, as you can see.  This afford you tremendous flexibility.  Here are some examples of code I use this function for:

 

WinSTART(_dbwinhome + ”readme.txt”) // Read the VdB README (launches Notepad)

WinSTART(“http://www.fusslocal.com”)// Launch default browser, go to specified URL

WinSTART(mailto:kgc@hit.net)        // Launch default mail program, start message to me!

WinSTART(“c:\program files”)        // Launch Explorer, opened to “C:\Program Files”

 

Working with Messages

Many Windows objects can be manipulated or otherwise communicated with by sending (or “posting”) messages to them.  We’ll not dive into all the details here, but essentially Windows is a huge message processing engine.  Most everything we do in Windows involves the passing of messages from one element to another.  If you have a utility like Borland’s WinSight, you can see these messages.  We’ll do this in the tutorial.

 

As you will see, this message passing happens all the time, and largely without our knowledge or awareness of it.  There are situations where we need to manipulate and/or send messages manually to get the behavior we want from our applications.

 

Visual dBASE’s support for the “Windows” menu in the menu designer is a good example.  The Windows menu automatically maintains a list of currently open MDI child windows.  Most MDI applications also add the ability to cascade or tile all open windows to clean up the display.  Bu the native dBASE Window menu lacks these commands.  We can add them on our own by adding code to the menu to send the appropriate message to the MDI parent shell. 

 

The API function we need is SendMessage.  Here is its prototype from the SDK Help file:

 

LRESULT SendMessage(

    HWND hWnd,       // handle of destination window

    UINT Msg,        // message to send

    WPARAM wParam,   // first message parameter

    LPARAM lParam    // second message parameter

   );     

 

 

Parameters

 

HWnd       Identifies the window whose window procedure will receive the message. If this parameter is HWND_BROADCAST, the message is sent to all top-level windows in the system, including disabled or invisible unowned windows, overlapped windows, and pop-up windows; but the message is not sent to child windows.

 

Msg        Specifies the message to be sent.

 

WParam     Specifies additional message-specific information.

 

LParam     Specifies additional message-specific information.

 

For the most part we concern ourselves with the first two arguments, the HWND of the window we’re passing a message to, and the Msg parameter itself.  There are hundreds of different messages; this parameter is just an integer value that represents a given message.  You’ll find many of these messages with somewhat mnemonic #defines in Visual dBASE’s header files, particularly WinUSER.h. 

 

The two messages we’re interested in for this example are WM_MDICASCADE  and WM_MDITILE, which tell an MDI parent Window to arrange its children accordingly.  Here, then, is a simple form with a menu that contains this functionality:

 

// WinMenu.WFM: demonstrate Cascade and Tile via SendMessage() API call

shell(.f.) 

 

** END HEADER -- do not remove this line

//

// Generated on 05/04/98

//

parameter bModal

local f

f = new testForm()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class testForm of FORM

   with (this)

      height = 10

      left   = 20

      top    = 5

      width  = 40

      text   = "First Form"

      menuFile = "winmenu.mnu"

   endwith

 

endclass

 

Okay, nothing exciting there, of course.  The message passing is done my the menu itself.  Examine the code that follows.  It’s not required that the menu contain the code; you certainly could hook the same logic into the form, and in fact, it might make good sense to pack things like this inside a custom form class for ease of later reuse.

 

Notice how we use the OnInitMenu event handler to EXTERN the API functions we need.  Then the menu selections for Cascade and Tile execute the SendMessage API function, in each case sending the message to the MDI parent, but with different message identifiers according to the behavior each requires.


//WinMENU.MNU: With cascase and Tile

** END HEADER -- do not remove this line

//

// Generated on 05/04/98

//

parameter formObj

new WINMENU(formObj, "root")

 

class WINMENU(formObj, name) of MENUBAR(formObj, name)

   with (this)

      onInitMenu = class::ROOT_ONINITMENU

   endwith

 

   this.MENU2      = new MENU(this)

   this.MENU2.text = "&File"

 

   this.MENU2.MENU6 = new MENU(this.MENU2)

   with (this.MENU2.MENU6)

      onClick = class::MENU6_ONCLICK

      text = "&New Window"

   endwith

 

   this.MENU2.MENU3 = new MENU(this.MENU2)

   with (this.MENU2.MENU3)

      onClick = class::MENU3_ONCLICK

      text = "E&xit"

   endwith

 

   this.MENU5 = new MENU(this)

   with (this.MENU5)

      text = "&Window"

   endwith

 

   this.MENU5.MENU13 = new MENU(this.MENU5)

   with (this.MENU5.MENU13)

      onClick = class::MENU13_ONCLICK

      text = "&Cascade"

   endwith

 

   this.MENU5.MENU14 = new MENU(this.MENU5)

   with (this.MENU5.MENU14)

      onClick = class::MENU14_ONCLICK

      text = "&Tile"

   endwith

 

   this.MENU4 = new MENU(this)

   with (this.MENU4)

      text = ""

   endwith

 

   this.windowMenu = this.menu5

 

   function MENU13_onClick

      #define WM_MDICASCADE 0x0227

      SendMessage(GetParent(form.hWnd), ;

           WM_MDICASCADE,0,0)  //cascade     

      return

 

   function MENU14_onClick

      #define WM_MDITILE 0x0226

      SendMessage(GetParent(form.hWnd), ;

           WM_MDITILE,0,0)    //tile     

      return

 

   function MENU3_onClick

      form.close()

      return

 

   function MENU6_onClick

      local f  // Create new forms at will!

      f = new form()

      f.menufile = "winmenu.mnu"

      f.open()

      return

 

   function ROOT_onInitMenu

      if type( "GetParent" ) # "FP"

        extern CHANDLE GetParent(CHANDLE);  

               User32

      endif

      if type( "SendMessage" ) # "FP"

         extern CLONG SendMessage(CHANDLE,;

                CUINT,CWORD,CLONG ) ;

                User32 from "SendMessageA"

      endif

      return

endclass


 

 

The results follow.  The first illustration is the app after clicking “File | New Window” a few times.  Next is the same app after clicking “Window | Cascade”, which issues the command…

 

SendMessage(GetParent(form.hWnd),WM_MDICASCADE,0,0)

 

…and then finally the results of clocking “Window | Tile,” which does this…

 

SendMessage(GetParent(form.hWnd),WM_MDITILE,0,0)

 

 Just what we wanted!

 

 

 

 

  

 

GDI Functions and the VdB PaintBox Control

To get a good grasp of how you can use GDI functions to draw on a form and create boffo text effects, you need to Just Do It.  And that's what we'll do, with the following several forms.  We'll look at graphics primitives first, then text-related functions.  We'll rely heavily on the Win32.HLP file, and will examine and running the following forms during the tutorial. 

 


// GDI0.WFM: Simple line drawing on a PaintBox control

 

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI0Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI0Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

   procedure FORM_ONOPEN  // Initialize stuff

      local hdc

 

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

      MoveToEx(hdc, 0, 0, NULL)            // Draw a diagonal line

      LineTo(hdc, form.width,form.height )

 

      msgbox("Look, it's there!","")

 

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

 

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

 

/* GDI0B.WFM: Drawing with the OnPaint event handler

*/

 

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI0bForm()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI0bForm of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

   return

 

   procedure PX1_PAINT

      local hdc

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      MoveToEx(hdc, 0, 0, NULL) 

 

      LineTo(hdc, form.width, form.height )

 

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

 

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

 

/* GDI1.WFM: Drawing a box with LineTo

*/

shell(.f.)

local f

f = new GDI1Form()

f.open()

 

return

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI1Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI1Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

   return

 

   procedure PX1_PAINT

      local hdc, nH, nW

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      nW = form.width - 10         // Form might have changed size, so calc these

      nH = form.height - 10

 

      MoveToEx(hdc, 10, 10, NULL)  // First we'll draw a rectangle "manually"

      LineTo(hdc, nW,10 )

      LineTo(hdc, nW,nH )

      LineTo(hdc, 10,nH )

      LineTo(hdc, 10,10)

 

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

 

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

 

/* GDI2.WFM: Draw a box using the Rectangle function

*/

shell(.f.)

local f

f = new GDI2Form()

f.open()

 

return

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI2Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI2Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

   return

 

   procedure PX1_PAINT

      local hdc, nH, nW

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      nW = form.width - 10         // Form might have changed size, so calc these

      nH = form.height - 10

 

      MoveToEx(hdc, 10, 10, NULL)  // First we'll draw a rectangle "manually"

      LineTo(hdc, nW,10 )

      LineTo(hdc, nW,nH )

      LineTo(hdc, 10,nH )

      LineTo(hdc, 10,10)

 

      nW = form.width - 20         // Calc coords for inner rectangle

      nH = form.height - 20

 

      Rectangle(hdc,20,20,nW,nH)   // Now cheat and use the easy function.

 

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

 

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

 

/* GDI3.WFM: Add a Pen to change line thickness and color

   Introduce the RECT struct, FillRect, etc.

   Use DrawEdge to do "3d" rectangles

*/

 

shell(.f.)

local f

f = new GDI3Form()

f.open()

 

return

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI3Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI3Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

 

      form.rectStructO = new RectStruct()  // Outer rectangle

      form.rectStructO.SetMember("top",10) // Assign static values

      form.rectStructO.SetMember("left",10)

 

      form.rectStructI = new RectStruct()  // Inner rectangle

      form.rectStructI.SetMember("top",20) // Assign static values

      form.rectStructI.SetMember("left",20)

 

   return

 

   procedure PX1_PAINT

      local hdc, InRect, OutRect, nH, nW, nPen, nHatch

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      nW = form.width - 10         // Form might have changed size, so calc these

      nH = form.height - 10

 

      // NOTE: SelectObject() returns the object being *replaced*!

      nPen = SelectObject(hdc,CreatePen(0,5,0x00ff00)) 

     

      Rectangle(hdc, 10, 10, nW, nH)

 

      nW = form.width - 20           // Calc coords for inner rectangle

      nH = form.height - 20

 

      InRect = form.rectStructI      // We'll do this one with one function

      InRect.setmember("right",nW)

      InRect.setmember("bottom",nH)

 

      nHatch = CreateHatchBrush(5,0xa010c0)

      FillRect(hdc, InRect.value, nHatch)

 

      DrawEdge(hdc,InRect.value,EDGE_BUMP,BF_BOTTOMLEFT + BF_TOPRIGHT)

 

      // NOTE!!! It's very important to delete GDI objects you've created to

      //         prevent resource "leaks!"

      DeleteObject(SelectObject(hdc,nPen)) // Delete our custom pen

      DeleteObject(nHatch)

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

 

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

class RECTSTRUCT of Structure

   super::addMember(TYPE_LONG, "left")

   super::addMember(TYPE_LONG, "top")

   super::addMember(TYPE_LONG, "right")

   super::addMember(TYPE_LONG, "bottom")

endclass

 

// GDI4.WFM: Introduce some randomness (which will lead to trouble!)

shell(.f.)

local f

f = new GDI4Form()

f.open()

 

return

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI4Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI4Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

 

      form.rectStructO = new RectStruct()  // Outer rectangle

      form.rectStructO.SetMember("top",10) // Assign static values

      form.rectStructO.SetMember("left",10)

 

      form.rectStructI = new RectStruct()  // Inner rectangle

      form.rectStructI.SetMember("top",20) // Assign static values

      form.rectStructI.SetMember("left",20)

   return

 

   procedure PX1_PAINT

      local hdc, InRect, OutRect, nH, nW, nHatch, nBrush

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      nW = form.width - 10         // Form might have changed size, so calc these

      nH = form.height - 10

 

      OutRect = form.rectStructO  // Fill area with a pattern & color

      OutRect.setmember("right",nW)

      OutRect.setmember("bottom",nH)

 

      nBrush = CreateSolidBrush(0x45d067)

      FillRect(hdc,OutRect.value, nBrush)

 

      Rectangle(hdc, 10, 10, nW, nH)

 

      nW = form.width - 20        // Calc coords for inner rectangle

      nH = form.height - 20

 

      InRect = form.rectStructI       // We'll do this one with one function

      InRect.setmember("right",nW)

      InRect.setmember("bottom",nH)

 

      nHatch = CreateHatchBrush(int(random() * 6),int(random() * 0xd0d0d0))

      FillRect(hdc, InRect.value, nHatch)

 

      DrawEdge(hdc,InRect.value,EDGE_BUMP,BF_BOTTOMLEFT + BF_TOPRIGHT)

 

      // NOTE!!! It's very important to delete GDI objects you've created to

      //         prevent resource "leaks!"

      DeleteObject(nBrush)

      DeleteObject(nHatch)

 

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

 

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

class RECTSTRUCT of Structure

   super::addMember(TYPE_LONG, "left")

   super::addMember(TYPE_LONG, "top")

   super::addMember(TYPE_LONG, "right")

   super::addMember(TYPE_LONG, "bottom")

endclass

 

/* GDI5.WFM: Create 5 similar forms, show OnPaint clipping problem for

             dynamic forms

*/

shell(.f.)

local f

for cnt = 1 to 5

   f = new GDI5Form()

   f.top = cnt * 50

   f.left = cnt * 50

   f.text = f.text + " #"+str(cnt,1)

   f.open()

next

 

return

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI5Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI5Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "GDI Function Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

 

      form.rectStructO = new RectStruct()  // Outer rectangle

      form.rectStructO.SetMember("top",10) // Assign static values

      form.rectStructO.SetMember("left",10)

 

      form.rectStructI = new RectStruct()  // Inner rectangle

      form.rectStructI.SetMember("top",20) // Assign static values

      form.rectStructI.SetMember("left",20)

   return

 

   procedure PX1_PAINT

      local hdc, InRect, OutRect, nH, nW, nBrush, nHatch

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      nW = form.width - 10         // Form might have changed size, so calc these

      nH = form.height - 10

 

      OutRect = form.rectStructO  // Fill area with a pattern & color

      OutRect.setmember("right",nW)

      OutRect.setmember("bottom",nH)

 

      nBrush = CreateSolidBrush(0x45d067)

      FillRect(hdc, OutRect.value, nBrush)

      Rectangle(hdc, 10, 10, nW, nH)

 

      nW = form.width - 20        // Calc coords for inner rectangle

      nH = form.height - 20

 

      InRect = form.rectStructI       // We'll do this one with one function

      InRect.setmember("right",nW)

      InRect.setmember("bottom",nH)

 

      nHatch = CreateHatchBrush(int(random() * 6),int(random() * 0xd0d0d0))

      FillRect(hdc, InRect.value, nHatch)

 

      DrawEdge(hdc, InRect.value, EDGE_BUMP, BF_BOTTOMLEFT + BF_TOPRIGHT)

 

      // NOTE!!! It's very important to delete GDI objects you've created to

      //         prevent resource "leaks!"

      DeleteObject(nBrush)

      DeleteObject(nHatch)

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

class RECTSTRUCT of Structure

   super::addMember(TYPE_LONG, "left")

   super::addMember(TYPE_LONG, "top")

   super::addMember(TYPE_LONG, "right")

   super::addMember(TYPE_LONG, "bottom")

endclass

 

/* GDI6.WFM: Invalidate entire client area to ensure proper updates

*/

 

shell(.f.)

local f

for cnt = 1 to 5

   f = new GDI6Form()

   f.top = cnt * 50

   f.left = cnt * 50

   f.text = f.text + " #"+str(cnt,1)

   f.open()

next

 

return

** END HEADER -- do not remove this line

//

// Generated on 05/06/98

//

parameter bModal

local f

f = new GDI6Form()

if (bModal)

   f.mdi = false // ensure not MDI

   f.readModal()

else

   f.open()

endif

 

class GDI6Form of FORM

   with (this)

      onOpen = class::FORM_ONOPEN

      scaleFontBold = false

      text = "InvalidateRect Demo"

      metric = 6  // Pixels

      height = 352

      left = 133

      top = 52

      width = 280

   endwith

 

   this.PX1 = new PAINTBOX(this)

   with (this.PX1)

      onPaint = class::PX1_PAINT

      height = 352

      left = 0

      top = 0

      width = 280

      anchor = 6  // Container

   endwith

 

   procedure FORM_ONOPEN  // Initialize stuff

      #define WIN32API_USER_H   // for metrics

      #include <win32api.h>

      #include "structapi.h"

      SET PROCEDURE TO "structure.prg" ADDITIVE

      form.paintStruct = new PaintStruct() // Create structure for drawing

 

      form.rectStructO = new RectStruct()  // Outer rectangle

      form.rectStructO.SetMember("top",10) // Assign static values

      form.rectStructO.SetMember("left",10)

 

      form.rectStructI = new RectStruct()  // Inner rectangle

      form.rectStructI.SetMember("top",20) // Assign static values

      form.rectStructI.SetMember("left",20)

   return

 

   procedure PX1_PAINT

      local hdc, InRect, OutRect, nH, nW, nBrush, nHatch

 

      InvalidateRect(form.px1.hwnd,0,false) // Tells Windows it's all in need of

                                            //painting

 

      hdc = BeginPaint(form.px1.hWnd, form.paintStruct.value)

 

      nW = form.width - 10         // Form might have changed size, so calc these

      nH = form.height - 10

 

      OutRect = form.rectStructO  // Fill area with a pattern & color

      OutRect.setmember("right",nW)

      OutRect.setmember("bottom",nH)

 

      nBrush = CreateSolidBrush(0x45d067)

      FillRect(hdc, OutRect.value, nBrush)

      Rectangle(hdc, 10, 10, nW, nH)

 

      nW = form.width - 20        // Calc coords for inner rectangle

      nH = form.height - 20

 

      InRect = form.rectStructI       // We'll do this one with one function

      InRect.setmember("right",nW)

      InRect.setmember("bottom",nH)

 

      nHatch = CreateHatchBrush(int(random() * 6),int(random() * 0xd0d0d0))

      FillRect(hdc, InRect.value, nHatch)

 

      DrawEdge(hdc, InRect.value, EDGE_BUMP, BF_BOTTOMLEFT + BF_TOPRIGHT)

 

      // NOTE!!! It's very important to delete GDI objects you've created to

      //         prevent resource "leaks!"

      DeleteObject(nBrush)

      DeleteObject(nHatch)

   return EndPaint(form.px1.hwnd, form.paintStruct.value)

endclass

 

class PAINTSTRUCT of Structure      // Structure we'll pass to BeginPaint

   super::addMember(TYPE_HDC,   "hdc")

   super::addMember(TYPE_BOOL,  "fErase")

   super::addMember(TYPE_RECT,  "rcPaint")

   super::addMember(TYPE_BOOL,  "fRestore")

   super::addMember(TYPE_BOOL,  "fIncUpdate")

   super::addMember(TYPE_STRING,"rgbReserved",32)

endclass

 

class RECTSTRUCT of Structure

   super::addMember(TYPE_LONG, "left")

   super::addMember(TYPE_LONG, "top")

   super::addMember(TYPE_LONG, "right")

   super::addMember(TYPE_LONG, "bottom")

endclass

 


Miscellaneous Code Listings

This is a collection of various API-based routines developed by dBASE developers who frequent the borland.public.vdbase.* newsgroups.  The newsgroups are perhaps the single best resource a Visual dBASE developer can rely on for peer support, nifty code tricks, and generally nice people.

 

Most of these are standalone functions and have been modified so that no other #includes and such are required.  The first listing is an exception in that it’s a single file listing with three functions.

 

 

 

//BDE.PRG: BDE Alias manipulation routines from Romain Strieff

//         Posted to the Borland newsgroups

 

//  Just the answer to those “But what if the user deleted the

//  database alias in his BDE settings” questions.  I’ve taken

//  to including this one in almost every app I write!

 

function DeleteAlias(cAliasName)  //Deletes an existing Alias

   if type("DbiDeleteAlias") # "FP"

      extern cint DbiDeleteAlias(cHandle,CPTR) idapi32

   endif

 

return DbiDeleteAlias(null,DBCSToSBCSZ(cAliasName))=0

 

function CreateAlias(cAliasName,cPath) //Creates a DBF Alias

   if type("DbiAddAlias") # "FP"

      extern cint DbiAddAlias(cHandle,CPTR,CPTR,CPTR,clogical) idapi32

   endif

return DbiAddAlias(null,DBCSToSBCSZ(cAliasName),;

                        DBCSToSBCSZ("DBASE"),;

                        DBCSToSBCSZ("PATH:" + cPath),;

                        TRUE)=0

 

Function DBCSToSBCSZ(c)

   LOCAL cTemp, x

 

   cTemp = Replicate(Chr(0), ((Len(c) + 1) / 2) + ((Len(c) + 1) % 2))

   For x = 1 To Len(c)

      cTemp.SetByte(x - 1, Asc(SubStr(c, x)))

   EndFor

RETURN cTemp


 

 

// HourGlass.PRG: TeamB member Bowen Moursund wrote this one to force

//  the standard “wait” mouse cursor to appear as needed.  I’ve used this

//  one (and his 16-bit version) a lot.  Thanks, Bowen!

 

PROCEDURE HourGlass(cOnOff)

 *-----------------------------------------------------------------------

 *-- Programmer..: Bowen Moursund (CIS: 72662,436)

 *-- Date........: 09/09/94

 *-- Notes.......: This routine will set the mousepointer to the Windows

 *--               'hourglass' and inhibit mouse and keyboard imput until

 *--               called with 'OFF'.

 *-- Written for.: dBASEWin, v5.0

 *-- Rev. History: None

 *-- Calls.......: Windows API

 *-- Called by...: Any

 *-- Usage.......: HourGlass(<cOnOff>)

 *-- Example.....: HourGlass("ON")

 *-- Returns.....: None

 *-- Parameters..: cOnOff = character expression, "ON" or "OFF"

 *-----------------------------------------------------------------------

 

     #define IDC_WAIT 32514

 

     if type("_app.SetCursor") # "FP"

         extern CHANDLE SetCursor ( CHANDLE )  USER.EXE

     endif

 

     if type("_app.LoadCursor") # "FP"

         extern CHANDLE LoadCursor ( CSTRING,CHANDLE ) USER.EXE

     endif

 

     if type("_app.SetCapture") # "FP"

         extern CHANDLE SetCapture ( CHANDLE ) USER.EXE

     endif

 

     if type("_app.ReleaseCapture") # "FP"

         extern CVOID ReleaseCapture ( CVOID ) USER.EXE

     endif

 

     *-- Now, to business

     cOnOFF = upper(cOnOff)

     if cOnOff = "ON"  && Install 'hourglass' mousepointer

         SetCursor(LoadCursor(0,IDC_WAIT))

         SetCapture(_App.Framewin.hWnd)

     else  && default mousepointer

         ReleaseCapture()

     endif

 

 RETURN

 *-- EoP: HourGlass


/* FreeMem.PRG: Return amount of free memory/resources

   Call with "System," "GDI," or "User".  First character is significant.

*/

Function FreeMem

   parameter cRType

   if type("cRType") # 'C'

      return -1

   endif

   nRType = at(upper(left(cRType,1)),'SGU') - 1

   if nRType < 0

      return -1

   endif

   if type('GetFreeSystemResources') # 'FP'

      extern cint GetFreeSystemResources(cint) rsrc32.dll ;

             from '_MyGetFreeSystemResources32@4'

   endif

  

return GetFreeSystemResources(nRType)

 

 

 

/* ThisEXE: Returns the name of the currently running EXE. 

            Handy in case where users have been playing around! 

 

    This function converts the buffer returned from GetModuleFileName

    to a standard dBASE Unicode string. 

   

    Most all of this code comes from Jim Sare

*/

Function ThisEXE

   LOCAL cBuff, nCount, nHand, cRetVal, i

   if type("GetModuleHandleN1") # "FP"

      extern CHANDLE GetModuleHandle(CLONG) Kernel32;

             From "GetModuleHandleA"

   endif

   if type("GetModuleFileName") # "FP"

      extern CLONG GetModuleFileName(CHANDLE, CPTR, CLONG) Kernel32;

             From "GetModuleFileNameA"

   endif

   cBuff  = space(128) // Actualy 256 bytes!

   nHand  = GetModuleHandleN1(0) // 0 = calling process

   nCount = GetModuleFileName(nHand, cBuff, len(cBuff) * 2)

   cRetVal = ""

   for i = 0 to nCount - 1             // add characters one at a time

      cRetVal += chr(cBuff.GetByte(i)) // except null terminator

   next

return cRetVal

 


/* NetConnect.PRG: Make a Win31-style network connection

 *

 * Usage:  nResult = NetConnect("\\Server\Resource","Local_Device","Password")

 *         Works for both drive and printer connections

 */

 

Function NetConnect

  parameter cResource, cLocalRes, cPassword

 

  if pcount() < 2  // Gotta have at least a resource and local hook

     return -1

  endif

  if type("cPassword") # "C"

     cPassword = ""

  endif

 

  if type("WNetAddConnection") # "FP"

     extern CULONG WNetAddConnection(CSTRING, CSTRING, CSTRING) mpr.dll ;

             from "WNetAddConnectionA"

  endif

 

Return WNetAddConnection(cResource,cPassword,cLocalRes)

 

 

 

// REPAINT.PRG: Force a repaint of any object with an HWND

//              Really handy inside those long loops!

 

PROCEDURE Repaint(h)

      // By Jim Sare - jimsare@ameritech.net

      // Last Updated - 05/22/98

      // Pass oRef.hWND to repaint a VdB object.

      // Pass 0 to repaint the Windows desktop.

      LOCAL hWND

 

      If h = 0

         If Type("GetDesktopWindow") # "FP"

            #if __vdb__ >= 7

               extern CHANDLE GetDesktopWindow() User32

            #else

               extern CHANDLE GetDesktopWindow() User

            #endif

         EndIf

         hWND = GetDesktopWindow()

      Else

         hWND = h

      EndIf

      If Type("UpdateWindow") # "FP"

         #if __vdb__ >= 7

            extern CLOGICAL UpdateWindow(CHANDLE) User32

         #else

            extern CVOID UpdateWindow(CHANDLE) User

         #endif

      EndIf

      UpdateWindow(hWND)

RETURN


 

 

// SysClose.PRG: This one was posted by Gary White. 

// Toggles the Close menu option on the system menu as well as the

// “X” button.

 

function sysClose

/* Enables or disables the "close" item on the system menu as well

   as the Win95 X.

 

   Syntax:    sysClose( oForm, lEnabled )

   Example: sysClose( _app.FrameWin, false )

 

   Returns the previous state of the menu item.

*/

parameters oForm, lEnabled

local hMenu, lRetVal

#define MF_ENABLED      0x00000000

#define MF_GRAYED       0x00000001

#define SC_CLOSE        0xF060

 

   if argCount() # 2 or ;

         type("oForm.hWnd") # "N" ;

         or type("lEnabled") # "L"

      msgbox( "Correct syntax is:  sysClose( oForm, lEnabled )", ;

         "Error", 16 )

      return

   endif

   if empty( oForm.hWnd )

      msgbox( "Form is not open", "Error", 16 )

      return

   endif

 

   if type( "GetSystemMenu" ) # "FP"

     extern cint GetSystemMenu( cHandle, cInt ) USER32

   endif

   if type( "EnableMenuItem" ) # "FP"

      extern CLOGICAL EnableMenuItem( cHandle, cInt, cInt) user32

   endif

        hMenu = GetSystemMenu( oForm.hWnd, 0 )

   if lEnabled

        lRetVal = EnableMenuItem( hMenu, SC_CLOSE, MF_ENABLED )

   else

        lRetVal = EnableMenuItem( hMenu, SC_CLOSE, MF_GRAYED )

        endif

   return not lRetVal


When NOT to use the API

 

Sometimes the API is the hard way.  When dBASE provides the function you need natively, use it.  If complex functions can be accomplished via OLE or DDE, those avenues should be investigated.

 

For example, the MAPI (Microsoft’s Mail API) functions are, well, a bit of a pain to figure out.  But the OLE interface to MAPI is relatively simple.  Here’s a MAPI object wrapper that handles simple mailing tasks easily.  Note that it uses OLE exclusively.

 

// MAPI.CC: Ripped off from some sample code somewhere…

 

CLASS MAPI(cProfile)  // pass the user profile you want to use (optional)

 

   this.Profile  = cProfile

   this.LoggedOn = false

   this.MapiObj  = new OLEAutoclient("MAPI.Session")

 

   function logon

      if type("this.Profile") == "C"     

         this.MapiObj.logon(this.Profile)

      else

         this.MapiObj.logon()

      endif

 

   function logoff

       this.MapiObj.logoff()

 

   function SendMsg(cName, cSubject, cText, lMsgDlg)

      local oMsg, oRecip

      this.logon()

      oMsg = this.MapiObj.outbox.messages.add()

      oMsg.Text = cText

      oMsg.Subject = cSubject

      oRecip = oMsg.Recipients.add()

      oRecip.Name = cName

      oRecip.resolve()

      oMsg.update()

      oMsg.send(0, iif(lMsgDlg,1,0))

      this.logoff()

 

   function release

      if this.LoggedOn

         this.MapiObj.logoff()

      endif

      release object this     

 

ENDCLASS

 

Where do I go from here?

To get the most from the Windows API, you’ll want to have at least one good reference/tutorial at hand.  The Win32 help file provides the prototypes, but you’ll want examples and such to go with it.  We’ll talk about some of these during the tutorial.  Also, I highly recommend the Borland newsgroups;  you’ll learn as much there as anywhere!