Randy is a consultant for Sierra Systems, specializing in FoxPro development on Macintosh, Windows, and DOS. He is the author of FoxPro 2.5 OLE and DDE (Pinnacle Publishing, 1994) and is currently working on a new book, FoxPro MAChete: Hacking FoxPro for Macintosh, to be published by Brady. He can be reached via CompuServe at 71141,3014.
I have been writing database applications in FoxPro for a number of years--first on DOS, then Windows, and more recently on the Macintosh (including FoxBase+/Mac). FoxPro for Macintosh (FPM) was closely adapted from its predecessors, FoxPro for Windows (FPW) and FoxPro for DOS (FPD), hence its emphasis on cross-platform database development.
Among other features, FPM provides a rich API and a powerful command set that includes SQL commands to accompany its query builder. It also includes a comprehensive screen builder that offers three-dimensional objects and supports System 7 features such as AppleScript, Balloon Help, and QuickTime movies. FPM also provides FoxPro's first wizard tools for automated building of screens and reports. (Microsoft's recently released version 2.6 of FPD and FPW also includes wizards.)
But just as there's no such thing as a free lunch, neither is it entirely possible to effortlessly move software from one platform to another--particularly when it comes to user interfaces. Consequently, in this article, I'll present some hard-won strategies for cross-developing FoxPro screens, focusing in particular on screen objects and font characteristics. Additionally, I'll share some more-general, FoxPro cross-platform strategies; see the accompanying text box entitled, "FoxPro Development Tips."
Early in my cross-platform development efforts, I made a conscious decision to isolate screen code into two basic components:
Code snippets are merely expressions with calls to procedures/functions in the same or higher calling program (PRG), which contains mostly platform-transparent database operation code. The SCX/SCT files, on the other hand, hold the platform-specific interface code. Editing a PRG is quicker and doesn't call GENSCRN each time a change is made. If you are storing code in your screen snippets, you must ensure that any change is also made to those same objects for all other platforms in the SCX/SCT file.
In general, working between FPM and FPW is relatively easy because both support virtually the same set of screen objects; see Table 1. While FPD makes use of many GUI-like controls, it cannot include many of the options supported by FPW and FPM. (There are third-party tools available, such as Espia, from Espia Corp. of Indianapolis, IN, that provide a true graphics feeling to FPD applications.)
While it's easy to say, "only use objects supported by all platforms being used," I don't adopt this strategy or want such a limit on my development. There is no reason why FPW shouldn't be able to use picture buttons and FPD normal pushbuttons. (This isn't to say there aren't limitations: People still use monochrome Macs, and Apple still makes 640x400 Powerbooks and Macs with 9-inch, 512x384 displays.)
When a screen is ported to another platform and opened for the first time, FoxPro invokes the "transporter"--a program (TRANSPRT.PRG) that creates new platform screen objects from existing ones. As you might imagine, complex heuristics are involved in mapping an object from a character-based coordinate system (FPD) to a graphical one (FPW or FPM). Topping the list of these calculations is font handling. Transporting between FPM and FPW, however, is simply a matter of remapping fonts with similar characteristics--fontmetrics. In fact, it is likely that less than 10 percent of the 384K TRANSPRT.PRG file is devoted to GUI transports.
The FoxPro transporter does an adequate job of transporting files between FPW and FPM. You should expect to make minor adjustments to both the position and size of many objects. Once a screen is converted, however, the transporter does an excellent job of keeping objects consistent between platforms.
Listing One (page 102), written entirely in the FoxPro native language, provides an alternate, yet basic, transporter for converting a screen. The main advantage of this program over the FoxPro transporter is that it gives you the ability to specify both default screen and object fonts.
To understand what is happening with the transporter, you need to understand how fonts work in FoxPro. One of the dilemmas Microsoft faced when developing FPW was how to address object size and positioning, since the company wanted software to be compatible on both GUI and character-based platforms. The solution was a unit of measurement known as a "foxel"--a cross between a pixel and a FoxPro row/column. Example 1 is a FoxPro command that displays an input field on a screen at coordinates of 9.063,40.125. These are actually rows and columns on the screen. These coordinates, however, are not controlled by the object's font (Geneva,9,Normal), but are based entirely on the default screen font. This is often set by window definitions such as Example 2. (Examples 1 and 2 are both GENSCRN output from a sample screen file.)
Each font has its own unique set of attributes, commonly known as "fontmetrics." These values are always measured in pixels in both FPM and FPW. The values I'll examine here primarily affect the font's height and width dimensions:
FoxPro Screens
When code is generated for screen SCX/SCT files, "code snippets" and interface/environment code are generated simultaneously. These snippets (screen setup, cleanup, object valids, and the like), which are embedded directly within SCX/SCT files, define how the application handles typical database operations such as record movement or deletion. In essence, the screen--all its objects and their functionality--can be entirely self-contained in a manner similar to that of object-oriented programming. As with any other non-library FoxPro file, an SCX/SCT file can be ported directly to another platform. When a screen file is opened on another platform, the FoxPro transporter intercedes to create a duplicate set of screen objects specifically for that platform. And when you regenerate screen code, often twice as much code is created, much of it redundant. Consequently, I avoid using snippets with my screen files.Fontmetrics
For example, the formula in Example 3 yields the single foxel row and column values, as well as the calculations of the example field's position. When you calculate the foxel values as in Example 3(b), the result is the location of the @..GET field; see Example 3(c). The pixel values 145 and 321 represent the coordinate position of the field from the upper left of the defined window. The field sizes (1.000,3.200) are based on the fontmetric values of the object font (Geneva,9). As you might expect, the foxel values for the object are smaller; see Example 4.
When you take another look at the DEFINE WINDOW command (refer to Example 2) for the screen definition, you see a similar analogy. SIZE coordinates are based on the window's own font, but the AT coordinates are based on the global, FoxPro default font. FoxPro works with multiple-coordinate systems. All objects within a single screen are based on the local coordinate system of that screen, while the screen itself is based on a more global coordinate screen. Each coordinate system varies because of the differences in foxel values.
The FoxPro transporter only allows one font per platform--the object font. Although Microsoft may change this in future releases, there are currently no accommodations for specifying the default screen font used in the window definition. In fact, FoxPro defaults to these two default fonts (one on each platform) when transporting between the platforms; see Table 2(a).
It is virtually impossible to obtain an identical-looking screen when you first transport. The single-pixel differences in the fontmetrics of the platforms affect two critical components of the transport process. First, the position of objects will be off because their coordinate system is based on a font with different dimensions. Second, the size of the screen is altered, since its definition is based on this same font. It is much more crucial that the fontmetrics of default screen fonts match those of object fonts.
It just so happens that the two fonts mentioned are also the default screen fonts used when a new screen is created (using the CREATE SCREEN command). Most programmers don't bother changing the default screen font because it doesn't visually impact the look of a screen, since each object can have its own font. The key to a truly successful transport is finding default screen fonts with exact fontmetrics. Table 2(b) lists my preferences for default screen fonts. I chose these fonts because both are common for their respective platforms. No doubt, there are countless combinations of fonts with similar matching values. If possible, you should use common fonts that you know exist on the computers running your screens. This is especially important if you plan to redistribute your applications.
There is more room for variation in the fontmetrics of the object fonts, since most people leave extra space on the screen. The transporter is actually quite smart in how it handles object fonts. You can specify a single object font for all objects to convert. If you choose, however, not to specify an object font and instead use the default transporter font (Geneva for FPM), both font size and style are retained. With FPM, the font used will either be Geneva or Chicago, depending on which font it finds in the FPW objects. The transporter even transports controls, such as pushbuttons, to default system fonts.
The screen's look-and-feel is up to your discretion. If you're looking for a simple cross-platform font strategy for screen objects, you may want to try the fonts in Table 2(c), which have a similar look-and-feel on their respective platforms and are close in fontmetrics. In addition, these are common Windows and Mac system fonts.
Much of the screen fontmetric discussion can be applied to other aspects of FoxPro, such as reports. Spend some time up front devising your cross-platform strategies. It will be well worth the effort in the long run.
Screen Object DOS Windows Macintosh
-------------------------------------------------------
Static text x x x
Fields x x x
Lines x x x
Boxes x x x
Rounded rectangles x x
Pushbuttons x x x
Invisible buttons x x x
Picture buttons x x
Radio buttons x x x
Picture radio buttons x x
Check boxes x x x
Picture check boxes x x
Popups x x x
Lists x x x
Edit regions x x x
Spinners x x
3-D effects x
OLE objects x x
Pictures BMP BMP.PICT
@ 9.063,40.125 GET m.state
SIZE 1.000,3.200 ;
DEFAULT " " ;
FONT "Geneva", 9 ;
PICTURE "@K XX" ;
COLOR ,RGB(,,,255,255,255)
IF NOT WEXIST("_qls1cbchi")
DEFINE WINDOW _qls1cbchi ;
AT 0.000, 0.000 ;
SIZE 18.188,62.500 ;
TITLE "Customer" ;
FONT "Geneva", 13 ;
FLOAT ;
COLOR RGB(,,,192,192,192)
MOVE WINDOW _qls1cbchi CENTER
ENDIF
(a) 1 Foxel Row = FontMetric(1) + FontMetric(5) 1 Foxel Column = FontMetric(6) (b) 1 Foxel Row = Font(1,'Geneva',13) + Font(5,'Geneva',13) = 16 pixels 1 Foxel Column = Font(6,'Geneva',13) = 8 pixels (c) = number of rows * pixels per foxel row = 9.063 * 16 = 145 = number of columns * pixels per foxel col = 40.125 * 8 = 321
1 Foxel Row = Font(1,'Geneva',9) + Font(5,'Geneva',9) = 12 pixels 1 Foxel Column = Font(6,'Geneva',9) = 5 pixels
Platform Default Screen Font FontMetric(1) FontMetric(6)
--------------------------------------------------------------------
(a) Windows MS Sans Serif,8 13 5
Macintosh Geneva,10 12 6
(b) Windows MS Sans Serif,10,B 16 8
Macintosh Geneva,13,N 16 8
(c) Windows MS Sans Serif,8 13 5
Macintosh Geneva,9 12 5
FoxPro Development Tips
Before starting any cross-platform database project with FoxPro, you might want to make adjustments to your Macintosh CONFIG.FPM configuration file (Figure 1), which is loaded when FoxPro is launched. I don't touch the MACDESKTOP and KEYCOMP options in my CONFIG.FPM file since I'd rather Macintosh users have a true Mac look-and-feel. The MACDESKTOP setting (set with the SET command) can make FoxPro act like a true Windows app, in which all windows exist within the confines of the main FoxPro desktop window. Normally when this setting is on (by default), all windows exist within the Macintosh desktop. The KEYCOMP setting gives FPM Windows-equivalent keyboard shortcuts. Experienced FoxPro developers often increase the MVCOUNT setting, which tells FoxPro the maximum allocation for memory variables. This ceiling has been raised from 256 to 1024 with FPM (MVCOUNT limits for PC versions 2.6 have also been raised to 1024).
Once set up, you can begin writing code, porting files, and generating applications. One of FoxPro's unique features is that an application can run without recompilation on any FoxPro platform. This means you can create an application in FPW, port the APP file to the Macintosh, and run it unmodified in FPM. While binary APP files port directly, it's still your responsibility that the code contained within be platform-ready.
The VOLUME setting (Figure 1) reassigns the name of my Mac startup volume to a DOS-like C:\ drive notation. This hard-drive reassignment setting allows me to port project, screen, and report files from the Mac to a PC without error when I reopen them. One of the more powerful uses of the VOLUME setting is that you can assign any Mac path to a specific single-letter drive VOLUME (such as C, D, Z, or X) and refer to that abbreviated path reference whenever you need to specify the path in your code.
Internally, FoxPro treats commands and functions that use filenames and paths by standard DOS shorthand notation. The FPM function SYS(2027) returns the file/pathname in true Macintosh notation; see Figure 2. Even though native FoxPro commands and functions properly handle paths regardless of platform, there are circumstances when you'll use SYS(2027). Example 5, for instance, uses the API function fxNewFolder (found in FOXTOOLS.MLB) to create a new Macintosh folder. Since the routine uses native-Macintosh toolbox routines requiring Mac pathing conventions, it would fail without SYS(2027).
FPM is reasonably smart in handling paths, especially those with spaces. As you might expect, spaces (common in Macintosh paths) will cause applications to bomb on PCs, which have more rigid naming conventions. It's a good idea to use quotes around paths. When you are working with cross-platform applications, avoid hardcoding paths in your applications and use memory variables for storing paths and/or filenames.
The FoxPro language is generally platform transparent. However, users are always going to want applications to adhere to platform standards. Consequently, it is a good idea to bracket platform code using IF..ENDIF or DO CASE..ENDCASE statements; see Example 6(a). While it is easy to hide specific platform commands within these CASE statements, you will likely encounter problems working with the FoxPro Project Manager (PM) on different platforms. For example, if you try to rebuild an application in FPW with a program containing the code in Example 6(b), a compile error will be generated since the Windows version was developed prior to the Mac version and has no awareness of certain FPM commands and functions. A way around this is macro substitution; see Example 6(c). In fact, you might create a function whose sole purpose is to execute a command passed it as a character string.
Working with API libraries also presents a problem for the FoxPro PM. Any reference to SET LIBRARY pulls in that library as an external project file; see Example 7(a). This helps the PM resolve any calls made by other programs to functions contained within the library.
A useful coding strategy is to make a call to that library indirectly so the PM doesn't pull in the file; see Example 7(b). The PM will still cause an error when a function call is made which can't be located (such as one in the indirect library name). One popular option used by developers is to add a dummy program file containing just the function names. The dummy program, like Example 7(c), is never called, but the PM searches each program for procedures/functions.
Taking this a step further, the Foxtools library, which ships with both FPW and FPM, contains many functions that you can call from your applications. Unfortunately, there is no equivalent library in FPD (although, there is a file called FPATH.PLB that contains some of the functions). One way around this is to use a SET PROCEDURE file. As Example 7(d) shows, you can place a function such as YMsgbox in a program called DOS_PROC.PRG.
--J.R.B.
pull in the file; (c) adding a dummy program file, which isn't called; (d) an alternate method for FPD, which lacks an equivalent library.
Copyright © 1994, Dr. Dobb's JournalFigure 1: This Macintosh CONFIG.FPM configuration file is loaded when FoxPro is launched. It is configured for a Windows user.
VOLUME c=\
MACDESKTOP=off
KEYCOMP=windows
Figure 2: The FPM function SYS(2027) returns the file/pathname in true Macintosh notation.
? CURDIR()
\FOXPRO\
? SYS(2027,CURDIR())
COSMIC II:FOXPRO:
Example 5: Using the API function fxNewFolder to create a new Macintosh folder.
SET LIBRARY TO foxtools
mydir=GETDIR('Select directory:')
retval=fxNewFolder(SYS(2027,m.mydir+'TEMP FOLDER'))
Example 6: (a) Bracketing platform code using IF..ENDIF or DO CASE..ENDCASE statements; (b) rebuilding an application in FPW with a program containing this code will generate a compile error; (c) this macro substitution will help you avoid such errors.
(a)
DO CASE
CASE _DOS
CASE _WINDOWS
CASE _MAC
CASE _UNIX
ENDCASE
(b)
IF _MAC
SET XCMDFILE TO 'xalert'
ENDIF
(c)
IF _MAC
XMac_Cmd='SET XCMDFILE TO "xalert"'
&XMac_Cmd
ENDIF
Example 7: (a) Reference to SET LIBRARY pulls in the specified library as an external project file; (b) making a call to that library indirectly so that the PM doesn't
(a)
SET LIBRARY TO foxtools
(b)
SET LIBRARY TO ('foxtools')
(c)
*dummy.prg
FUNCTION justpath
FUNCTION juststem
FUNCTION msgbox
(d)
IF _DOS
SET PROCEDURE TO dos_proc
ENDIF
[LISTING ONE]
PRIVATE objfont,objfsize,objfstyle
PRIVATE scrnfont,scrnfsize,scrnfstyle
PRIVATE nosize,nostyle,SysControl
PRIVATE mystamp,splatform,splatform2
PRIVATE tmparr,tmpcurs,tmpalias,scrnfile
* Select fonts you want to use in transporting
DO CASE
CASE _MAC
* default screen font
scrnfont='Geneva'
scrnfsize=13
scrnfstyle=0
* object font
objfont='Geneva'
objfsize=9
objfstyle=0
CASE _WINDOWS
* default screen font
scrnfont='MS Sans Serif'
scrnfsize=10
scrnfstyle=1
* object font
objfont='MS Sans Serif'
objfsize=8
objfstyle=0
ENDCASE
splatform = IIF(_MAC,'MAC','WINDOWS')
splatform2 = IIF(_MAC,'WINDOWS','MAC')
m.nosize = .F. &&retain original font style
m.nostyle = .T. &&retain original font size
m.SysControl = .F. &&use system font for controls
* Select screen file to transport
m.scrnfile=GETFILE('SCX','Select Screen File:')
IF !'.SCX'$UPPER(m.scrnfile)
RETURN
ENDIF
* If the file already has platform objects it is kicked out.
* You can manually delete these objects and retransport.
m.tmpalias='_'+LEFT(SYS(3),7)
SELECT 0
USE (m.scrnfile) ALIAS (m.tmpalias) EXCLUSIVE
LOCATE FOR platform=m.splatform
IF FOUND()
WAIT WINDOW 'File has already been transported.'
USE IN (m.tmpalias)
RETURN
ENDIF
WAIT WINDOW 'Transporting Screen...' NOWAIT
* Create cursor of new platform objects to be appended to original file later.
=AFIELDS(tmparr)
m.tmpcurs='_'+LEFT(SYS(3),7)
CREATE CURSOR (m.tmpcurs) FROM ARRAY tmparr
APPEND FROM DBF(tmpalias) FOR platform = m.splatform2
* Add new platform
REPLACE ALL platform WITH m.splatform
* Handle porting of objects
DO CASE
CASE m.nostyle AND m.nosize &&change only fontface
REPLACE ALL fontface WITH m.objfont;
FOR INLIST(objtype,5,11,12,13,14,15,16,22,23)
CASE m.nostyle &&dont' change fontstyle
REPLACE ALL fontface WITH m.objfont,;
fontsize WITH m.objfsize;
FOR INLIST(objtype,5,11,12,13,14,15,16,22,23)
CASE m.nosize &&dont' change fontsize
REPLACE ALL fontface WITH m.objfont,;
fontstyle WITH m.objfstyle;
FOR INLIST(objtype,5,11,12,13,14,15,16,22,23)
OTHERWISE
REPLACE ALL fontface WITH m.objfont,;
fontsize WITH m.objfsize,fontstyle WITH m.objfstyle;
FOR INLIST(objtype,5,11,12,13,14,15,16,22,23)
ENDCASE
* Add system fonts for controls if option set
IF m.SysControl
DO CASE
CASE _MAC
* use Geneva,10,N for controls
REPLACE ALL fontface WITH 'Geneva',;
fontsize WITH 10,fontstyle WITH 0;
FOR INLIST(objtype,11,13,14,16,22)
* use Geneva,10,B for text buttons
REPLACE ALL fontface WITH 'Geneva',;
fontsize WITH 10,fontstyle WITH 1;
FOR objtype=12
CASE _WINDOWS
* use MS Sans Serif,8,B for controls
REPLACE ALL fontface WITH 'MS Sans Serif',;
fontsize WITH 8,fontstyle WITH 1;
FOR INLIST(objtype,12,13,14,16,22)
* use MS Sans Serif,8,N for lists
REPLACE ALL fontface WITH 'MS Sans Serif',;
fontsize WITH 8,fontstyle WITH 0 FOR objtype=11
ENDCASE
ENDIF
* Handle screen default font objects
* - picture buttons, invisible buttons
* - picture check boxes, picture radios
REPLACE ALL fontface WITH m.scrnfont,;
fontsize WITH m.scrnfsize,fontstyle WITH m.scrnfstyle ;
FOR INLIST(objtype,1,20) OR '@*B'$picture OR ;
'@*RB'$picture OR '@*CB'$picture
* Note: can add code here to replace objtype 23 info
* Cleanup a little
SELECT (m.tmpalias)
APPEND FROM DBF(m.tmpcurs)
USE IN (m.tmpalias)
USE IN (m.tmpcurs)
WAIT CLEAR
MODIFY SCREEN (m.scrnfile) NOWAIT
RETURN