Killer Win32 API
Originally Presented at the
9th Annual Borland Conference
August 9, 1998
Keith G. Chuvala, First Intermark Corp.
mailto:kgc@pdq.net - http://www.sckans.edu/~kgc
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.
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!
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!
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:
dBASEKeyword |
As Pointer |
dBASEData 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.
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.
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.
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.
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!
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:
"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”
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!
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
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
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
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!