C&L Nicholls - Spacefold     Lisa's Page     FoxSpoken Volume Index

   FoxSpoken SoftSpoken-Style

This was the last published FoxSpoken volume...
Check out this page for an index to other volumes/articles and tools.

This page is devoted to VFP RAD tools, techniques, concepts, and other points I think are of interest to the FoxPro community. I include some commentary that is either too, um, idiosyncratic, or just plain too unwieldy, to post on the newsgroups or publish elsewhere.

Before you use this material, please be sure you have read and understand the various provisos *3 with which I provide it for your use.

Current contents of this page (updated Dec.98):

  1. New Happenings
  2. New Tech: VFP 6-Specific Comments and Tips
  3. Whipped Tip #1: What's a Top Form For?
  4. Whipped Tip #2: Formsets: Just say "yes"!
  5. Random Acts of Kindness: Miscellaneous VFP Tips
  6. Special pull-out soapbox section: Why I don't use FoxPro for Windows 2.x
  7. Data strategy: different strokes for different folks & requirements
  8. A few good references: Lots of other people's thoughts

So what's new with us? Colin and I are moving, February 99, that's what. And this site is going to be moving too.

Meanwhile, it's been a long time since my last Fox-Confession, and it may be a long time before the next one. So this is going to be a lengthy volume, full of catch-up, whipped tips, soap boxes/larger issues, responses, and miscellany...

What's new with Fox?

Since I last wrote a FoxSpoken volume, we've had a DevCon and the release of Visual FoxPro 6. A whole lot is new. I've written a great deal about two features of the new product already, in two white papers you'll find on the Microsoft MSDN site.

The VFP 6 Application Framework and Foundation Classes abound with good examples for you to use in your own development work. I've abandoned work on the MasterClass Framework in this version of the product, in fact, since I created it to provide teaching examples. I'll use examples from code that ships with the product, from now on, instead.

Another reason I created MasterClass was to provide a sample framework that was capable of hosting a collection of application objects. This may not seem to be a very important thing, but it's useful for people who create developers' tools (as I do), rather than READ EVENTS style, or monolithic, applications. We needed a way to make sure that we had proper scope and protection for applications that had to co-exist with other applications.

Now that VFP 6 is so good at creating COM objects, this aspect of MasterClass is also unnecessary. With VFP COM servers, we have better methods of making sure applications don't collide with each other, yet can interact with each other.

This section will give you just a few scattered thoughts on new-to-VFP 6 features I think will interest you. This is a great product, folks, fast on its feet and full of depth.

Project building improvements abound

I don't have time, energy, or space enough to tell you about project hooks and the project object here in detail. This is the second most exciting new thing about VFP 6, IMHO. (The first most exciting thing is the advent of access and assign methods, discussed below.)

But I can't forbear giving you just a little taste of what you can do with this stuff. Some of you may remember that I used a GENMENU hack by Matt Peirse of Cornerstone Software in my GENMENUL.PRG, both for MasterClass Framework and general use. Matt's addition caused certain code to be run, for special MNX files, every time you built an APP or EXE. You could force code to execute during the project run that had nothing to do with menus. You could even open up the PJX file and make adjustments to the project files, if you were careful.

One of the best reasons to use Matt's hack was what we called the "autoversioning" mechanism. Even after VFP gave us more access to the "standard" version properties for an EXE, I continued to use our own autoversioniong for several reasons: I could set up the increment to work however I wanted, I could access it easily within Fox, both during development work and at runtime, and it was just plain more elastic than the Microsoft feature.

Not in VFP 6.

In VFP 6, forcing code to run during a build is dead easy. You just associate a project hook object with the project, and place your code in the appropriate hook event. Do you want to autoincrement, but evaluate how the incrementing will work, and whether it should increment for this run? Write whatever code you want in the BeforeBuild event. Put up a build-time dialog if you want.

Where will you store the build/versioning information? Now that we have complete control over it, I'll put it in the standard place (where it will be available when a user rightclicks on my EXE). How do I reach that standard place from my project hook code? Easy -- I just use the _VFP.ActiveProject reference and place the right value in the project's VersionNumber property.

How will I reach this information at runtime, for my own running EXE or any other file? Easy too -- Just use the new AGETFILEVERSION() function.

All very yummy.

A quick mention of VARTYPE()

People seem to be rather confused about what VARTYPE() is for, and what it is not for.

First, VARTYPE() is much faster than TYPE(). Second, VARTYPE() doesn't do the kind of string-evaluation TYPE() does; it works directly on a variable, not on a string representing a variable. Where you might initialize a variable, myvar, and then check the type of its contents by checking TYPE("myvar"), you would check VARTYPE(myvar). Notice that the former has string delimiters and the latter does not.

All the statements in the preceding paragraph are linked to each other. VARTYPE() is faster than TYPE() because it doesn't do the same kind of string evaluation that TYPE() does, which is great. You don't have to type the quotes, and you get the speed, right?

Right, if you know the variable exists. But because VARTYPE() doesn't do that string evaluation piece, it doesn't have TYPE()'s errortrapping built in. If you check VARTYPE(myvar) and myvar hasn't been initialized or is out of scope, you will get an error, rather than the "U" with which TYPE() represents an "unknown" datatype.

Use VARTYPE() when you know the reference you're checking exists. First and foremost among the times to use VARTYPE() is to check the parameters you've been passed on the way into a procedure. These always exist. You can use VARTYPE() in ASSERTs for required parameters, and you can use VARTYPE() in initialization code for optional parameters, at the top of your functions and methods. Make a habit of it.

The second most obvious time to use VARTYPE() is when you're checking that a member property of an object contains a valid object reference. You know that this member property exists, so you will not get an error. (If this is generic code and you have any doubt, you can check first, using PEMSTATUS() with a 3rd argument of 5, of course.) Unlike TYPE(), VARTYPE() will not require a second check of ISNULL() if it has RETURNed a type of "O" for object. A NULL value for any property or var will RETURN "X" rather than the type of any previous value it might have had before it became NULL. O blessed day.

If you have some bizarre reason to want the previous value's type for a NULL-holding property or var, VARTYPE() can give it to you. With a second parameter of .T.,VARTYPE() gives you the same RETURN information you would have gotten from TYPE() for this property or var. Remember, you still don't use the quotes, and you still would get an error if the reference doesn't exist.

A dream comes true

Folks,this is totally serious: the most important new thing that's in VFP 6 is access and assign methods.

I know I am not going to be able to prove this to you. I know this, because I've tried to prove it a lot of times, in a lot of ways, to quite a few of my clients. If they've used access and assign in a previous language, they already understand. If they haven't, it takes a while for the light to dawn... and the event that brings epiphany is different in every case.

It depends on your needs, I guess. But when you hit the item that you could never do properly before, and suddenly see how either access or assign makes it all come right, there you are.

Because I can't prove this to you, I thought I would just relate (with permission) something written to me by one of my clients, John O'Duffy, of Tailor Made Software, here in New Zealand. He convinced me that, not only had my VFP dreams come true with access and assign, but that I could also finally hang up my whips, secure in the notion that my clients are so well trained that none of 'em need even a virtual whipping anymore...

The weirdest thing happened to me this morning that I just have to tell you about. (It's a little long, but there are no support questions...and as you'll see, you've done that already!)

I've been developing a new app in VFP6 (yep 'proper framework') and it has the very popular<s> MS DatePicker ActiX object added to the app_standardtoolbar...all went wonderfully for weeks until the first time I hit a modal form, then bang-crash with error messages about ole.Refresh and other confusing stuff. I had no idea.

So having finally discovered newsgroups I posted a message to which Andres replied with enough to make me think again. The Refresh mentioned in the error message confused us both and he thought I was calling it explicity and it just needed a ole.Object.Refresh....but no, I then realised that the _framework must be trying to disable it after opening a modal form.....so trying to think logically .... trace what happens ..... ah ha found the _modalawaretoolbar.ldisabledformodal_access method and then near the bottom the line

THIS.SetAll('Enabled', NOT llDisableAll)

and this was the problem, the ole.DtPicker did not like its .Enabled prop being set like this with the SetAll('Enabled').

Oh woe, what to do....the ole won't take a Enabled = .T. directly , it needs a .Object.Enabled = .T. to make it work. So (bad thinking) I'm thinking I'm going to have to change the code of SetAll('Enabled') to cope with these pesky ActiX objects to give a .Object.Enabled ; BUT that means changing base code and I'm LOATHE to do that. By this time another msg from Andres had come through suggesting this too, so by late last night I was resigning myself to do this, but not quite wanting too.

So went to sleep depressed. Now it gets weird.

At 6:45am my eyes flew open, you were standing there, hands on hips, eyebrows crossed, looking furious and yelling "Add a Enabled_assign method to your subclass $#!%#"

'Ah OK yes thanks' said I, and went back to sleep but didn't forget. And of course that fixed the prob, no base code changes needed and in the _assign method of course I could "This.Object.Enabled = m.vNewVal"

This was a bit strange<g> in that I must confess up to now I'd never made a _assign method of my own (these things take time) the concept is not locked in yet, I had used lots of myprop_access methods (I'd needed them, so caught on quick) but no _assign methods.........so where did that thought come from, don't think it was mine.

Leaving aside all the obviously bizarre points of this story <g> I have to point out that I was surprised to hear that John had been using access methods for a long time before he found a need for assign. I personally use assign about 5 times as often as access. It's evidently a very individual thing.

While I'm thinking about it, I'd like you all to be a little careful with access methods. Evaluate the performance impact that they may have on your code. Don't use them in a loop if their result isn't going to change, or in a procedure called very frequently.

If you have a procedure that will use this value frequently, whether in a loop or not, it's more efficient to access the member once at the top of a proc, transferring its value to a local variable. Use the var for the rest of the procedure. This is similar to what we sometimes do with object references where there is a long string of parent.parent pointers, and to the value of the WITH/ENDWITH syntax.

Don't use an access method automatically wherever you've used a Set... method before, either. For example, a datapath member may currently have its value set by a SetDataPath() method in the Init of your application object. Remember that, if you place the code in an access method instead, you ensure that this code will run every time the datapath value is needed. However, if its value rarely changes, do you really want to run this code every time you access the value? Or do you want to leave that SetDataPath() code alone?

If, in this example, you decide you do want to validate the datapath member every time you access it, go ahead and use the access method, by all means. But be careful to do two things:

  1. Be sure that your access code does a quick evaluation of the current value of the member and a super-fast RETURN if nothing is wrong, without running any unnecessary and lengthy code.
  2. For safety's sake, set the member's value to itself on startup, ensuring that the code will run once on startup, initializing the value properly just as the explicit call to SetDataPath() did before. If you decide against this tactic, at least evaluate whether omitting that explicit code on startup will cause you any other problems.

Additionally (and just as critical), be sure that your assign methods always make good use of the VARTYPE() function, as described above. There is absolutely no excuse for omitting this check before running additional code against the new value -- plus, it gives us the opportunity to strongly-type our properties, if we want to do that.

All cautions aside, access and assign can cut your work in half when you get used to them. All the work you have to do to make sure code is triggered when conditions change is eliminated, because that code can run automatically. So, mainly, I was just happy that John had "got it", however it happened. And it will happen to all of you too, if it hasn't already! Happy trails...

Trivia tip: Tooltips have improved

Was anybody besides me driven mad by the seemingly-arbitrary limitation on tooltip length in previous versions of VFP?

It wasn't just the silliness of the number -- I could never remember, exactly, but it was around 50 characters -- but the fact that you didn't get an error when you went over the limit. You just didn't get a tooltip at all.

Tooltips have improved in VFP 6 on both counts. You can now have a tooltip of a length that matches certain other string limitations in VFP (127 chars), and if you exceed it you get an error, as you'd expect, suitable for debugging and fixing.

Unfortunately, the error you get doesn't appear to be trappable in the Error method of controls. If it were, I'd tell you to put some code in your base class Error methods for all your visible controls, right now, checking for this particular error and SUBSTR()ing as necessary. Since it doesn't appear to be, I suggest you SUBSTR() or LEFT() at the moment your classes set tooltip text. I don't care whether it's in a collaborating object that iterates through all items on your form, whether you're reading something from the DBC, or handling #DEFINEd strings for language localization purposes. Wherever you're doing it, make sure that you have appropriately truncated the string.

This is exactly what you are already doing (or should be doing) every time you construct a WAIT WINDOW message or a DEFINE BAR PROMPT string or MESSAGE string, except that these are subject to the 254-character string length restriction rather than the 128-character one. Hey, I never said everything was going to make sense <g>. 128 is better than 50, isn't it?

Just wait, there's more...

MS Fox team members have been doing some sneak previews of features in "VFP Next" (no version numbers, but they're promising a release in the not-too-distant future).

Here are some of the items that I particularly like for use by VFP COM objects:

Another new feature I particularly like, for COM objects or otherwise, is the ability to create a standalone data session object, independent of a form or formset. It's low on overhead and can be subclassed programmatically.

A VFPCOM.DLL, which will probably be released before the next version of the main product, allows WithEvents use and ADORecordset<->cursor translation. So watch the MS web site for this!

Whipped Tip #1:
Did I just hear somebody say SDI again?

This particular heading covers information I've seen published or available to Fox developers that either needs amplification to be useful or is Just Plain Wrong. Warning: I will not always be good tempered in this section, or perhaps not even on the rest of the page. Sometimes I wonder why I gave up carrying a whip <g>.

All together now, let me hear you say it:
TopForms # SDI
... and don't you ever forget it.

All kinds of people who should know better make this mistake, from MS support people to gurus to helpful-types on the newsgroups. I'll quote from one example, from the lead paragraph of a lead article in FoxTalk engagingly titled "Using VFP 5.0 Top-Level Forms". (I single out this example because it perfectly illustrates the conceptual problem. However, its author, Richard A. Schummer, is no better and no worse than many other FoxPro experts who might have written these words. Don't blame him. Also, this particular article was written in 1997, so perhaps Mr. Schummer knows better by now!)

One of the most requested interface capabilities in the past few years has been the ability to provide single document interface (SDI) forms, and they're here in Visual FoxPro 5.0. Also known as top-level forms, they run outside of the Visual FoxPro frame or main window. Each SDI form is independent and appears separately on the Windows desktop... A top-level form is a modeless form that runs without a parent form... Each instance of a top-levelform will appear on the Windows taskbar and can contain childframes as well. The VFP Debugger application is an example of an SDI Form.

Folks, Mr. Schummer is entirely correct about the meaning of SDI. Since SDI means "single document interface", and since top forms, such as the VFP Debugger, contain child forms (as he also knows), he should realize that both top forms in general and the VFP Debugger in particular are examples of MDI (Microsoft's "multiple document interface"), not SDI, just like MS Office applications.

An MDI application has an application-wide "container window", just like _SCREEN. In VFP 5 and above, we use top forms (forms with the ShowWindow attribute set to 2) to perform this service. These container windows can contain toolbars and documents, the latter being forms with the ShowWindow attribute set to 1.

Caveat: You can also get a form with ShowWindow set to 0 to appear inside a top form rather than _SCREEN if you want, but it's difficult. You'll find the code in the _application object of VFP 6's framework (found in WIZARDS\_FRAMEWK.VCX), if you want to see how it's done.

You can also learn what you'll need to know about creating MDI applications in VFP -- such as how to get your toolbars to appear in the right place, maintain a collection of document forms for your application, and manage a top form menu -- from the VFP 6 framework. Related Foundation Classes used by the framework (found in FFC\_REPORTS.VCX) will also teach you about getting your report previews to behave in a top form application.

Typically, an MDI application also has a CONFIG.FPW with the line SCREEN=OFF, to dismiss the VFP standard application window from a distributed VFP MDI application. Why do we use a top form instead of _SCREEN? Simple: we can manage its events better, since we create and write code for the form subclass that serves as the application frame ourselves, like any other class. (You'll find fleshed-out examples of these strategies in the VFP 6 framework, too.)

A VFP top form (ShowWindow = 2) can also be used to create an SDI application, if you like. In this case, you typically put controls directly into the top form, to create something like a utility applet in the desktop. There are no child documents in this case, and if you need to spawn additional windows for your application you create them in the desktop. Typically these spawned windows use the Desktop attribute rather than being created as additional top forms, unless you have a good reason to want them all to appear in the Windows taskbar separately.

You see, the idea of "top form" actually has nothing to do with SDI versus MDI. It means "a window that exists in the Windows desktop and has its own presence, representing an application, in the Windows taskbar". The VFP "Desktop" attribute, by contrast, means "a window that exists in the Windows desktop, outside its application container, but not representing a separate application or behaving like one in the taskbar". The concepts of ShowWindow, DeskTop, and SDI|MDI are separate and incommensurable.

Officially, the VFP form attributes of ShowWindow and Desktop are readonly, although it is possible to tweak the ShowWindow property to change values under some arcane circumstances. There's a good reason for their readonly status. As you can see, the states of each property are fundamentally incompatible with each other. Once a window exists, and is visible, you can't suddenly decide that it shouldn't contain child windows (since it might) or that it shouldn't be in the desktop (when it is).

I suspect that people got confused because VFP forms also have an MDIForm property, so they thought top forms must have been provided to perform the opposite service. Nothing could be further from the truth. The MDIForm property exists mostly to create backwards compatibility with the MDI|NOMDI keyword on the DEFINE WINDOW statement. It changes the way a child window behaves when you change its state within the parent container window, toggling that child window's behavior between MDI-compliance and, er, something else.

There is rarely any reason to mess with the MDIForm attribute, because MDI-compliance governs child window maximizing behavior, and we rarely want our database application document windows to maximize (spoils our careful layouts). But if you'd like to see the behavior in action, go ahead and CREATEOBJECT("Form"). To see the difference, you'll want to play with a couple of child windows and see what happens when you maximize one of them.

Change your object's MDIForm attribute; it's not readonly, and it's not dangerous. Notice where the window's controls go when you maximize the window. Change its MDIForm property, and try again. Notice what happens when you've activated different MDI-compliant windows, when one is maximized, too.

One final comment: the simple reason why a top form is "modeless", as Mr. Schummer states above, is that VFP modal windows are only modal within a single application ("application modal"). Within VFP, we don't have the means to create system modal dialogs, which stop everything else in the Windows desktop until you pay attention to them. Since a top form represents a separate application within the Windows desktop, sure, you can click back and forth between them and other VFP windows, even if you used Show(1). But this is a meaningless distinction. If you want a modal dialog that is part of your application but you want it to appear in the desktop, use the Desktop attribute. If we ever get "system modal" capacity, top forms will probably have the capacity to be "system modal" like any other forms.

Whipped Tip #2:
Formsets are supremely useful

Yes, another one.

What do I have to do to convince you guys of this???

I'll grant you, formsets are a mindtrick in VFP. Underneath, you can see the seams, the most hideously gaping of which is the fact that VFP only has one ActiveForm, which belongs to _SCREEN, and formsets have no native way to identify which of their child forms "has focus". The second flaw in formset scenarios is that you can't easily relate them to MDI applications; there is a chasm, in the object model, between the role of formsets and the role of top forms as MDI containers. This chasm can be bridged -- the code to the VFP 6 Coverage application will show you how -- but the fact remains that formsets and top forms have no natural relationship.

Still, a formset with an invisible toolbar as a member is an excellent starting off point for an MDI application that exists in a top form frame but needs to instantiate an unknown number of toolbars and forms at run time. (This is exactly what the Coverage application does.) The formset actually exists "in _SCREEN" but that doesn't matter. It still serves as the containership parent, or owner, for the forms in the top form app, including the top form MDI frame -- with a native forms collection, like _SCREEN.Forms(), which top forms don't otherwise have.

A formset also gives you a host object for an application with its own datasession. It doesn't have to have any forms, or any visible presence, to do this. (In the next version of VFP, we'll have another way to get this datasession, but not until then.)

Formsets provide clustering of objects into a container that is supremely useful. In addition to its forms collection and its datasession, it allows them to share a container for purposes of release. Don't knock it until you've tried it.

Several people have asked me how I create a formset without any forms in it. I have three methods, as follows:

  1. DEFINE the formset class in code -- this is the easiest way. You can still define the forms visually, but you have to use ADDOBJECT to get them in at runtime.

  2. DEFINE your base formset with a small form called dummy in it, use THIS.RemoveObject("Dummy") in the Init of the formset class. Now ignore this form for your design purposes.

  3. Cheat <g>. If you know, as I do, that you need an invisible toolbar in this formset (used as a container for custom members of my application), then you can do it this way and get a visual formset class with no forms:
    x = createobject("formset")
    ... et voilà, a VCX-based formset with only a toolbar.
    You can do something similar if you have a visible toolbar that you'll need of course, by creating a formset class visually in the normal fashion, adding your toolbar, and then choosing to Remove its lone form at that point. However, this is less flexible for top form apps, since any visible toolbars should be appearing in my top form frame, not in the formset, which thinks it exists in _SCREEN. The *invisible* toolbar I use as a member container can happily exist within _SCREEN. It doesn't cause any problems to a top form app, and it doesn't matter whether _SCREEN is visible or not.

As a related question, some people have trouble getting a toolbar to show up in a top form instead of _SCREEN. It's really simple; just make sure that you .Show() your top form frame directly before the Createobject() line that creates the toolbar. Additionally, you should make sure that the top form frame is active when you make the toolbar Visible or .Show() it.

I often get the feeling that Microsoft people are not familiar with formset use or don't quite see the point of formsets. They may wish they'd never included this baseclass; perhaps its inclusion was forced the needs for converting FoxPro 2.x code. Both designtime issues and PEM/runtime issues or decisions that they've made for formsets are sometimes "clumsy". For example, between VFP 3 and 5 we lost the Form Designer window's dropdown box for activating different forms within the Designer surface. This makes visual formset design work harder than it needs to be.

For this reason, I practice "defensive programming" even more vigorously than usual, whenever I use this baseclass. Microsoft may not have thought of what I am about to do with a formset, so I need to test every assumption I may have.

Now, personally I think I could make a good case for the statement " Toolbars are not useful". But that's another (elaborate) story, probably best left for some long DevCon evening <g>.

VFP notes, cross-version

This section contains a group of notes that are not VFP 6-specific, but they all certainly work in VFP 6. They are not related to each other, except that they are all items I've been meaning to give you for a while.

The caret and the strict date (in VFP 5 too!)

The caret or circumflex character came in to Fox syntax for use in date literal syntax during VFP 5, although it was woefully undocumented. If you use curley brackets as delimiters for a date literal and include a '^' as the first non-blank character after the opening bracket, the delimited components of this date literal will be assumed to be in YMD order (optionally including time components, as well) -- no matter what the state of SET DATE or SET SYSFORMATS.

This is a tremendous boon, especially if you #DEFINE date constants. It's important to realize that #DEFINEd constants are evaluated using the SET("DATE") and SET("SYSFORMATS") status as available when the code is compiled, which is often be different from their state at runtime.

Therefore #DEFINEd date constants should always use strict date format, taking advantage of the '^' character, without fail, and without reference to Y2K problems. The issue of #DEFINEd date constants has plagued international Fox users who bought American source code that used #DEFINEs carelessly, since we first got #DEFINEs. (I will not name names here, although I'd like to <g>.) Y2K only exacerbates these existing questions.

In VFP 6 and above, if you have included the ^ and have not SET STRICTDATE TO 0 for backwards compatibility, your date literal will be assumed to take the form YYYYMMDD (plus optional time components as before), of course.

How do you read the Timestamp field?

You've all seen it : the Timestamp field shared by the Fox metadata tables holds a value used internally to see if you've made any changes in an object described by the rest of its record. This object may be a source file (in a PJX), or a class definition (in a VCX) or any other type of Fox beast, depending on table type... but the Timestamp field has the same job and the same silly-looking construction.

If you just want to check for the latest version of something, as Fox does, that's really all you have to know. But if you would like to display this value more intelligently for documentation purposes, you need to transform it for human beings to read. There are probably a million ways to do this -- all based on a description, given by Fox Software many years ago, of how this value is created -- but the question still comes up regularly... so, for your edification and use, here's the way I do it:

* TimeStampString.PRG
* >L<
* to convert Fox system file timestamp 
IF EMPTY(tiStamp) OR TYPE("tiStamp") # "N"  
   RETURN ""
LOCAL lnYearoffset,lcYear,lcMonth,;
lnYearoffset = BITRSHIFT(tiStamp,25)
lcYear = STR(1980 + lnYearoffset)
lcMonth = STR(BITRSHIFT(tiStamp,21) % 2^4)
lcDay = STR(BITRSHIFT(tiStamp,16) % 2^5)
lcHour = STR(BITRSHIFT(tiStamp,11) % 2^5)
lcMinute = STR(BITRSHIFT(tiStamp,5) % 2^6)
lcSecond = STR(BITLSHIFT(tiStamp%2^5,1))
RETURN TTOC({^&lcYear./&lcMonth./&lcDay. &lcHour.:&lcMinute.:&lcSecond.})

I like this particular method because it uses the bit-twiddling functions in Fox, which I rarely have an excuse to do, and also because it illustrates careful use of the caret, or circumflex, character in a date literal to enforce strict date description as I build up the value, as discussed above, and yet it returns a string that conveniently observes current date-setting conventions (using TTOC()). Of course you can easily change the RETURN line to build up any other version of the string you prefer to display.

Cute, eh?

Opening a combobox on demand

Pertinent file: DROPCOMBO.TXT updated Dec.98, ~2k. This is a runnable PRG, renamed to a .TXT file so you can view it on line.

I have written this solution out for so many clients for so many reasons I thought it was time I made it available publicly, so I could just point people to it on-line! Access 95 has a DropDown method for its combobox, I believe; perhaps this is what gives people the idea that it "should be easy".

There are probably many different ways to force a combobox to open programmatically in Visual FoxPro. I like this one because you can use it from any event or at any time you want. Plus, it has the advantage of teaching some important syntax that you may not already know. It's quite instructive to see OBJTOCLIENT() in action, I think, and many people don't twig to the value of the MOUSE command or how to use its coordinate clauses effectively.

In this runnable code example, there is only one line of really meaningful code, which I've marked using *&*. You don't even have to worry about the second MOUSE statement in the DropMeDown method if you don't care about restoring the mouse position. Also, you don't have to worry about the SetFormRef method code or the oForm member property if you always know you are in a form; just use THISFORM. In this generic code, I don't assume THISFORM exists because the combobox might be in _Screen.


Coloring separate items within listboxes and comboboxes

In a previous tip appearing in FoxSpoken, I asked, "is anyone interested to know how to put colors into a popup used for a listbox or combobox, on a per-line basis?" ... and Stephen Morgan was, so here goes.

It's a dead-simple trick: you use a RowSourceType of 9 (popup). You can define a popup with each bar showing in different colors (and you can re-define bars as necessary). Below is a little simple, runnable code so you can see the effect.

* The first section of code merely enables you to 
* run this code multiple times,
* so you can see the effect in both baseclasses:
if type("_screen.y.baseclass") = "C"
   if upper(_screen.y.baseclass) = "LISTBOX"
      myclass = "combobox"
      myclass = "listbox"
   myclass = "listbox"

* This middle section of code defines the popup
* we'll use as rowsource, so you
* can see the color-handling syntax.
* Please note the *comma* in the color clauses below
* -- we need to change the contents of 
* the *second color pair* for each bar as needed:
define popup x
define bar 1 of x prompt "red on white" ;
       color ,rgb(255,0,0,255,255,255)
define bar 2 of x prompt "not touched"
define bar 3 of x prompt "red also" ;
       color ,rgb(255,0,0,255,255,255)
define bar 4 of x prompt "not touched"
define bar 5 of x prompt "purple on green" ;
       color ,rgb(255,0,255,125,255,0)
define bar 6 of x prompt "blue on yellow" ;
       color , rgb(0,0,255,255,255,0)

* And now to see the popup as a rowsource:
with _screen.y
  .rowsourcetype = 9
  .rowsource = "x"
  .visible = .t.

You'll note that the documentation specifies RowSourceType of 9 as "for backward compatibility only". Well, this seems like a waste of perfectly useful feature to me. If anybody else can create the same effect without a RowSourceType of 9, I'll be willing to believe the docs.

The FRX Giveth and the FRX Taketh Away

Who ever first said "plus ça change, plus c'est la même chose" didn't know the half of it, because s/he lived before Fox report forms.

Very few things change in the GUI Fox Report Writer from version to version, but when they do, they tend to perplex, rather than soothe, if you know what I mean. Well, even if you don't, somewhere around VFP 5, the RW developed a few new quirks, both good and bad... this section will acquaint you with two of them.

The good news (and I don't mean to minimize it) is that we can finally use an indirect reference to reference image files. Why is this important? let's recap:

As anybody who has tried General fields knows, they are painful. They bloat FPTs, you can't get their contents out easily (or at least not programmatically-easily) for editing by other programs, they're difficult to maintain and supervise for about a billion reasons. Most people prefer to reference images in their tables using a character field, which holds the filename of the related image file for a given record. Whether you use a full pathname, a relative pathname, or no path at all is up to you. You can then use this filename as required when you want to place an image in a form, and your users can create them in any program they like, re-editing them as they like.

Printing those files as part of report forms, however, has been difficult. The only usable method was a cursor with one record and one general field. You could use a UDF to APPEND GENERAL at runtime, re-filling this one general field on a record- or group- or page- basis, as needed, so that the appropriate file existed in the one general field and the stupid FRX would have the appropriate contents.

Why not run a UDF to copy each filename, in turn, to a temporary file and reference that dummy filename in the FRX, you say? Simple: Fox caches image resources, based on filename, and it didn't work. Ah, you say, but VFP 5 added CLEAR RESOURCES. That's nice, but -- even if you put it directly into the UDF run during the report -- CLEAR RESOURCES didn't effect the images at the right time to get the right-image- per-detail-record. To be honest, I'm not sure that CLEAR RESOURCES or its earlier cousin, SYS(whatsis), worked properly with FRXs at all, but they absolutely did not affect FRXs on a per-detail level, and they didn't work and play well with REPORT FORM PREVIEW, either.

Until VFP 5, you couldn't put a macro-expression or an indirect reference into the Report Picture dialog's File textbox, but now you can. So you simply have to create a public or report variable, and place its name in this location, surrounded by parens. Your UDF (on a group, detail, page, or Whatever basis) now fills this variable with the appropriate filename, and everything just works.

If you would like to test this, you don't even need the variable. Just create a report with Picture object in the detail band, and use this expression in the above-mentioned File textbox (don't forget those outer parens):


... now USE any table with two or more records, and do a REPORT PREVIEW. You should get a series of GETFILE()s, in which you should pick different bitmaps. (You actually see as many GETFILE() dialogs as detail bands will fit on the first page, or as there are records in your table, whichever is less.) And then your report will show multiple images.

Note: If you use this technique, and if your images may be different sizes, you should size and position the picture object in your layout as you need for the largest size you expect. Generally, you'll also want to use the "Scale picture, retain shape" option setting.

Why did this suddenly start working? They actually store the image object filename information differently in a VFP 5 FRX than they used to in VFP 3 and FPW. (It's now in the Name field rather than the Picture field of the object record.) So, if you have any problems with the technique in upwards-converted FRXs, it will be best to create a fresh one. You can copy and paste all the non-Picture objects into your new layout from the old one.

Well, one small battle won for reporting kind. Now the bad news (or as Colin would say "what the FRX is wrong now"): something strange has happened to summary bands in VFP.

Again, you'll need a little history: why do we have title and summary bands at all? Why not just use an outer group band? After all, you can group on .T. and be assured of exactly one group header and footer for the entire report.

If used with the "separate page" option, however, title and summary bands are indeed different and special. They allow your opening and closing report sections to have entirely different layouts from the rest of your report. This is because they don't include page headers and footers, standard across the rest of your pages. There are many situations in which such a distinction is highly desirable.

Unfortunately, this distinction is also slightly broken in VFP, as follows: A summary band on a separate page has no page header or footer, yet the size of the summary band on a separate page is calculated as if it does have a page header. If the summary band height plus page header height is larger than a full page height, you will get an error ("Summary band is too large to fit on a page"), and you cannot run the report.

You heard me. To test it out, just create a sample report with title and summary bands using the "separate page" option. Make the title and summary bands the same size, the full height of the page (so that presumably you can design their distinctive appearances for the full page). If you try to Preview, you may or may not be able to, depending on the size of the page header at present. Now make the page header band lots bigger, and you'll see the error. Never mind that the title band and the summary band are the same height (and didn't change from before). Never mind that the page footer band is apparently not consulted.

Don't ask me to explain how this one happened when the RW was ported between FPW and VFP...

SOAPBOX: FPW... Just say No!

This section is clearly labelled "soapbox". You have been warned. Feel free to skip to the next section if you do not want my personal opinions along with my technical advice. If you read this section, do NOT feel free to write to me to argue about it.<g>

I have less-than-zero interest in FoxPro for Windows 2.x problems, from a technical point of view. I am interested in the Fox developer community, and many of you choose to use FPW, or are forced to do so. Many of you are also my clients, and you regularly come to me with the technical problems FPW forces you to solve.

Although I do try to help, I thought it was time to say something about these issues in general and why they don't interest me.

From my point of view, FoxPro for Windows is, and always was, an abomination unto any Lord you care to name. It added nothing -- no deep, real functionality -- to a sublimely useful product: FoxPro for DOS.

Some of you may point out DDE as an asset, and I will admit that this is "real functionality", although not "deep", since it is fairly crippled. (I absolutely refuse to count General fields as "real functionality".)

When you look beyond the issue of "real functionality", you have only "Windows-like GUI behavior" as a possible asset of FPW over FPD. However, FPW's ability to "prettify" an interface is questionable at best, no matter what set of criteria you use. When you start evaluating it using the criteria of "Windows-standard behavior", it is laughable. Under Windows 95 and beyond, its deviation from standards is even more unbearable than it was under Windows 3, of course.

When my developer clients write applications for people who have to use low-end hardware, I tell them to write really good FP DOS programs. I don't care if they have to run as a DOS session under Windows or whether they are running under DOS directly.

I think that people should run FP DOS programs under DOS and Win3, if they have low-end hardware, and VFP 5-or-above under Win 95/98/NT/2000. That's the choice. Two superb products for different environments. Just so there's no misunderstanding, I didn't advise using VFP 3 with Win32s either. I don't see why anybody would want to run VFP under Windows 3 limitations.

I admit that I am not very interested in screen design or icons or bitmaps, and this undoubtedly influences my opinions on this issue. Still, I believe that a pleasantly-designed FP DOS screen, with reliable, comfortable behavior, will serve your clients and customers well until they are ready to upgrade their equipment. As for the frills you may be able to offer through DDE (and, if you're foolhardy, using General fields), they aren't going to meet people's expectations very well. Automation and COM do much better jobs. When they really want these items, they're going to want Office 9x or 2000 too -- and, again, they'll be upgrading their hardware.

I realize that this is a bitter pill for many developers to swallow, especially if they are Vertical Market people who feel they "must" have a "Windows" product and they "must" support a wide range of hardware. This is still the best advice I can give them, and you:
Write two, equally great applications, one of which supports a subset of your app's features in a robust and blazingly fast style (in FoxPro DOS), and the other of which has full-blown, knock-their-socks-off functionality (in Visual FoxPro).

I said "equally great", and I meant it. Only when you resort to FPW does the word "mediocre" become inescapably associated with the word "Fox".

Most of the problems people bring to me in FPW occur because FPW isn't really a Windows app at all. You're all putting giant kludges on top of the other giant kludges internal to the product, just to get it to limp along. No matter what you do, I don't think you are serving your purposes, or your clients, well to do this. I don't spend a lot of my own time researching these problems, as a result.

Data-Structuring Strategy:
Why would anybody ever want a FileAs field?

Something very odd happened to me a couple of weeks ago, as I was watching a developer demonstrate the features of his app. I can only describe it as a kind of conversion: I finally understood one piece of the rationale behind the file structure of Outlook's contact files.

I don't mean I understood it with contempt or as a kludge, but rather as the right solution for the right problem. The kind of thing you and I are accustomed to achieving with clean living and normalized, relational tables.

If you're shocked, please don't blame the developer I was with (he wasn't even using Outlook at the time). Here's how it happened: He was showing me an alphabetized list of companies in Access, and we couldn't find the particular company we were looking for, "The Acme Group", at the top of the list, where we expected to see it. "Ah," he said, and snapped his finger, "it'll be in the T's, because of the 'The'". And so it was.

You can imagine how I felt.

Why didn't they just index on an intelligent expression, which would have removed such words as "A", "The", or whatever from the ordering process? That's what we would do in Fox, right? Of course, we'd end up writing a UDF of some sort so we could tweak the intelligence, or at least a really, really complicated IIF() statement. Well, perhaps they can't do that in Access. (Don't feel too superior just yet.)

Note that we're always talking about an "alphabetized list" here, not the capability to support multiple sort orders. We're just looking for that "alphabetized list" to be as intelligent and useful as possible.

That's when I started to think about how Outlook handles the same problem. When you enter a new Contact, Outlook has a FileAs field. When you enter the contact's full name and/or company name, it offers some intelligent choices for this field, and lets you pick between them, but it also lets you type something completely unrelated, if you like.

Previously, every time I encountered that FileAs field, especially while writing code to synchronize my Fox and Outlook contact lists through automation, I chafed. I felt FileAs was both a waste of space and a redundancy that caused potential problems on every data update. How much better, I thought, was the Fox method of maintaining an index that did all this work without requiring a separate field in the table for it!

Now I'm not so sure. Here's why:

  1. We may have saved space in the table, but we haven't really saved very much space overall, since the index we've created will store the expressions to order each row in the table, using pretty much the same space as the FileAs field.
  2. Our complex expression, whether IIF() or UDF(), will be accessed and evaluated every time index maintenance needs to be done, and it won't be speedy. This is a minor point, I know.
  3. Here's the major point: our complex expression will never be as complex as the user's intentions and needs. Outlook (or Access) can use exactly the same logic that we would use in the index, at exactly the right moment (when the user chooses to add or edit a contact entry), but it does not assume that this logic models the world perfectly. The user still gets a chance (at exactly the right moment), to further adjust the FileAs string. If the user has to adjust it too often or too drastically, we can refine future versions of the logic to include additional patterns that we've identified -- but we never assume that this logic is perfect, so the user never has to settle for an irksome, machine-ordained, intractable order. And I think that's really great.

Sure, it takes time and space on the update form for the extra field, sure the user may not want to bother. (Remember the user doesn't have to bother, since our logic, available during the update, will give exactly as intelligent a default choice as it would have when it was in the index expression.) But the user spends a lot more time looking through that ordered contact list than s/he does updating its entries, right? And isn't that, in the end, the point? Not data maintenance, but data use.

Think about it. Perhaps you'll tell me all this was obvious. But I'll tell you, it wasn't obvious to me. These are tricks I didn't learn down on the Daisy Hill Normalized Puppy Farm. (How many of you still have a copy of that poster, from Database Programming & Design magazine or whatever it mutated into?)

"Fox-Happy Hour" on Lisa's Channel...

School's out. It's relaxation time at Radio SoftSpoken. Listen in as we spin some of these crowd-pleasing platters, top of the charts:*4

... Additional soulful Fox-World-Music titles will be added here soon, I haven't had a chance to do this yet... I'm sort of picky on this point...:*4


Lisa Slater Nicholls