C&L Nicholls - Spacefold     Lisa's Page     >L<'s Fox Stuff     FoxSpoken Volume Index

   FoxSpoken Volume 4

articles on this page retired Dec.98

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.

Contents of this page:

  1. Automated Testing: do try this at home
  2. Running stuff without RUN
  3. LoadPicture: really good for something
  4. List and Comboboxes from related tables
  5. Menu Tricks: keystroke interactions and you
  6. CLASSDOC update again ?!?
  7. The Future of FoxPro: Somebody else's thoughts

Runner and more about automated testing

Pertinent files: RUNNER.ZIP updated May.97, ~34k.

I've been getting a lot of compliments and requests for information about the "Runner" timer class, which handles demo and automated testing runs, since I showed it at DevCon. I'm glad people are finding it useful! (DevCon attendees: the lion's share of my source was left off the conference CD. If you don't yet have Runner, go to the special Advisor update page, which you'll find listed in your session notes, for Runner and the rest of my Advanced Testing session materials. You'll find stuff from a lot of the other speakers, too.)

For all those who've asked, no, Runner has not been available anywhere else except my DevCon notes (unless somebody's posted it without my permission). However, it was not written specifically for DevCon, and I'm pleased to make it available, here and now.

Runner is something I wrote for myself, originally. As a "developer's consultant", I do a lot of testing and have occasion to create such things frequently. I had no idea whether other people would like it or not, until I got the post-DevCon feedback.

If you download RUNNER.ZIP, you'll find RUNNER.VCX, which holds a single custom object you can attach to any application for table-driven automated runs. RUNNER.TXT describes Runner's API and FRONTRUN.SCX provides "Front Runner", a simple front end to help you develop scripts for Runner to execute. Along with RUNNER.TXT, there is a driver table for FRONTRUN.SCX, FRONTRUN.DBF, which contains information about each of the available runner script action types.

You will need to re-compile both the class library and the form for proper use in VFP 5. Otherwise, you will find that they use no VFP6-specific features.

Unfortunately FRONTRUN.DBF and RUNNER.TXT are the only information I can make available to you as documentation for Runner at this point. DevCon attendees received a sample application and sample script that made use of Runner, but this sample material is VFP6-specific, I'm afraid. This lack of support materials has been my hesitation in releasing Runner in any other arena. I can't support it endlessly for free; it is what it is, and its users are on their own!

I explained this to Paul Kearney, one of the people who's been encouraging me to release Runner, and he responded as follows:

Actually, we don't need any support. It is easy to use, extendable, and just all around-great! Thanks again for making it available to the attendees. I hope others will benefit from it as well.

Needless to say, I'm happy to hear this <g>. So, Foxes and Vixens, have at it. And enjoy!


A better Run than RUN -- and more information than I had at DevCon

I requested the topic of Advanced Testing at DevCon because I think it's so important to think through a testing strategy, and to develop a suite of testing tools.

Runner isn't the only tool you'll need when you test, of course. It's a great help for automated testing, but automated testing is only part of throughly testing your apps.

Even within the realm of automated testing, Runner should be considered in combination with other tools, if you have the resources. I've experimented with combining Runner with other tools written outside of FoxPro, to increase the availability of mouse-automating events for ActiveX objects. (You'll find some notes about this in RUNNER.TXT.)

If you really want to do automated testing thoroughly, you want to do it with as little "intrusive" impact on the operating system as possible. I suggest you investigate the hardware-assisted approach you'll find in a tool called AutoScriptor Inferno, to become familiar with this possibility. ASI, its vendor, makes a point of supporting VFP developers. A refreshing scenario indeed!

My DevCon notes also suggested you take a look at Windows Scripting extensions, which are available for Win 95 and NT, and delivered as part of Win 98. (I'm not going to try to provide the appropriate MS site reference here, but you will find it!) At the time I wrote the notes, however, I had little information about this beast and what it could do for us. Well, folks, I'm happy to say that the answer is... plenty!

You want to throw away the RUN command and that ugly DOS window forever (even if you get it to Close on Exit on the client's machine with no hassles)? Try this:

oShell= createobject("wscript.shell")

No DOS window!! No waiting!! No fooling around with macro-ized commands and worrying about their delimiters! Yeah!

You'll find that the Windows Scripting Host can do a heckuva lot more than replace the RUN command... it houses several different objects that understand shortcut-creation, drivemapping, registry use, you name it... but frankly this one benefit was enough to make my day.

How does this topic fit in with Runner and automated testing? I found an interesting shareware tool, Macro Scheduler, that you can integrate with Runner, but I couldn't invoke it within VFP (and within a Runner script) without a RUN command... until now.

Come to think of it, for use with Runner, you might actually want to use the third parameter in the oShell.Run line above, to force a wait -- this would solve a major scripting headache that I had doing the Runner-MacroScheduler integration.


LoadPicture() use in HTML Image Tag Generation

If any of you have used the LoadPicture() function to get an object reference to an image, I bet you used it to hand that object reference to an ActiveX control requiring such a reference. Until recently, I couldn't think of any other good reason to use the reference in VFP. (All our image-bearing controls take a filename rather than an object reference, to specify their pictures.)

Turns out we have a really great reason to use LoadPicture()...

As you probably know, when you write HTML Image tags, you should always specify the HEIGHT and WIDTH attributes of the image, even if these attributes are to be left at their defaults (the actual HEIGHT and WIDTH of the image file). If you specify them explicitly, the browser can render the page much more quickly, because it knows how much space to leave for the image.

If, like me, you've been generating a lot of HTML pages from VFP data recently, you need to provide these default attribute values, using the picture file information, where the page design does not specify them. Often, these values will be different for every row in a table, as every row will specify its own image file information.

Of course, if you have endless patience, you can master the various picture file formats and read this information out of the header for each image. You either have to know the file format for each image, using another field in the table, or you need to rely on the file extensions to know how to read the headers. Relying on extensions is always a dangerous practice -- please don't remind me that Windows does it this way! Besides, within any given image format, there are variations in the file headers that will curl your hair (is that how my hair got this way??), especially if the image has been converted from some other format, possibly several times, to get the file you are actually using.

Loadpicture() to the rescue! It turns out that the object reference you get when you pass Loadpicture() a filename has a perfectly respectable set of .Height and .Width properties. Only catch is that they are not in pixels, which is what you want for the browser; they are in a cute little unit that Microsoft endearingly calls "himetrics".

Without boring you with the details, or with the sojourn through MSDN-land I made to understand the relationship between these units, I will simply give you the function I wrote to do the conversion. We're basically looking at the millimeters of screen size, versus pixels of screen size, in each dimension, given that we know the number of himetric units to a millimeter.

Please note that you have to call the conversion function twice, specifying each dimension, because the relationship will not be the same for both Height and Width. Pixels are screen-relative, and Himetrics are not. I am not sure that this code is perfect, but it is pretty close. I will post an update if I find a more elegant solution:

* use it like this:

* load up your image reference:
* get the width in pixels:
? ConvertHiMetricsToPixels(oPicture.Width)
* get the height in pixels:
? ConvertHiMetricsToPixels(oPicture.Height, .t.)
* now you can write your default
* Image tag height and width...

 Function ConvertHiMetricsToPixels(tiUnits, tlVertical) 
   DECLARE integer GetDC ;
      IN WIN32API integer
   DECLARE integer ReleaseDC ;
      IN WIN32API integer, integer
   DECLARE integer GetDeviceCaps ;
      IN WIN32API integer, integer

    #DEFINE HORIZ_MM             4
    #DEFINE VERT_MM              6
    LOCAL ligDC, liRatio, liPixels
    ligDC = GetDC(0)
    IF (NOT tlVertical)
        liRatio = tiUnits/((GetDeviceCaps(ligDC, HORIZ_MM))*HIMETRICS_PER_MM)
        liPixels = liRatio*SYSMETRIC(1)
        liRatio = tiUnits/((GetDeviceCaps(ligDC, VERT_MM))*HIMETRICS_PER_MM)
        liPixels = liRatio*SYSMETRIC(2)        
    ligDC = ReleaseDC(0, ligDC)
    RETURN ROUND(liPixels,0)


"PROMPT FIELDS"-type listboxes and comboxes with related tables

Pertinent files: PROMPTFIELDS.TXT updated Jul.97, ~3k.

In an earlier volume of FoxSpoken, I posed this question: Is there anybody interested in finding out how to use listboxes with a rowsourcetype of 6 (fields) and a rowsource that includes fields from multiple related tables in the list?

As it happened, people were interested.

So, as promised, here's the solution, and as promised I don't cheat with a view<g>: if you use an IIF() for a column expression, even if the IIF() condition is simply .T., you can put anything in the column expression you like.

To show you what I mean, I wrote a runnable code example in this file, PROMPTFIELDS.TXT. It's really a PRG but I've given it a TXT extension so you can look at the simple code on line if you like. It includes both a listbox and a combobox demonstration of this little technique.

Thanks to Miles Sandin, who was the first one to respond to this FoxSpoken question.

Next up for an "is anybody interested" tip: would you like to know how to put colors into a popup used for a listbox or combobox, on a per-line basis? Is this of value, does anybody care, or does everybody already know how to do this one?


Menuing and keyboard interactions

Let me dispose of one more "feedback subject" on this grab-bag-of-a-page: I get a lot of questions about menu use, especially about use of native menu options, since I discussed them and the SYS(1500) function in an earlier FoxSpoken volume. I also see a lot of noise on this subject on both the beta and public forums. The basic question is always something like this:

I'm using a menu option [or an ON KEY LABEL] [or a PLAY MACRO] to invoke a series of keyboard-centered actions. Suddenly [or in some specific version of Fox] they don't work. Is this a bug?

Usually, these "keyboard-centered actions" involve the editing menu shortcuts or other native menu items. The critical factor to the problem is the act of linking together these actions in a sequence. Because the person with the question has managed it successfully in the past, s/he is stymied when the sequence or the technique "suddenly" stops working.

I am going to quote from a question put to me e-mail by Stephen Morgan, because it's a fairly typical example of the problem. He has a series of editing shortcuts he's developed to change case of highlighted text while he edits a program, which were working in FoxPro 2.x. Stephen uses an ON KEY LABEL to UPPER(), LOWER(), or PROPER() the current value of _CLIPTEXT, and he uses a Ctrl-key assignment to invoke this action during a sequence, like this:


Stephen's analysis of the problem:

The problem seems to be that the COPY and PASTE functions do not work. If the text is highlighted and the copy is manually performed (Ctrl-C is pressed) and then the assigned intermediate function key pressed and then a manual paste, the operation works, but if the copy and paste is not manually performed, or Ctrl-F5 is used instead of F5, no action is performed on the highlighted text and the contents of the clipboard are unchanged. The actual problem appeared to be that the KEYBOARD command was not working. I rewrote the code to use the new SYS(1500) function but the COPY function still did not work....

In fact, Stephen could have had this problem in FoxPro 2.x, and might not see it on some machines in VFP, because the issue is not version-specific, but rather a timing problem that will occur when you do any such sequence in FoxPro. In this case, the contents of the _cliptext var has to register the Ctrl-C before that contents can be properly transformed by the next action. There is literally no way to be sure that such chained actions will occur in the proper sequence, on every machine, in every version of FoxPro, unless you slow them down significantly.

This issue is one reason why many people choose to affect the editor using an FLL or through the FOXTOOLS functions that access the editor rather than by manipulating such key sequences with FoxPro code. (If you would like to see one such implementation, and vastly improve your ability to customize the FoxPro editor, you want to go straight to COB System Designs, and check out CEE.) However, you can usually take care of it with FoxPro code, as well.

For those of you who are unaware of this, you can often take care of problems in KEYBOARDed or PLAY MACRO sequences by including {PAUSE N} where N is either a literal number of seconds (including a decimal place) or a macro expression resolving to a number of seconds. (The latter is useful because the PAUSE you need tends to be machine-specific.)

In Stephen's case, I could see that the problem was very clearly a case of the second element of the sequence overtaking the first one. Instead of a KEYBOARD'd {PAUSE}, I thought it would be cleanest and best-controlled to transfer the "action" to a procedure, inserting a wait state at the appropriate moment, so that I could omit the intermediate KEY LABEL, like this:


PROC ToUpper
INKEY(.01) && increase as necessary

It's not "superior programming" to use SYS(1500) and INKEY() rather than a keyboarded emulation of user actions with {PAUSE}, although (for me) it often clarifies such a sequence to do so. Whether you use SYS(1500) or KEYBOARD/PLAY MACRO to invoke a series of actions, be sure to think carefully about how fast they occur, and about what actions in the sequence depend on the successful conclusion of other actions in the sequence. Then add wait states, mid-sequence, as necessary.

As always when you use SYS(1500), be sure that the _MED_COPY and _MED_PASTE are DEFINEd BARs on your _MEDIT POPUP, even if this popup does not show up on the menu bar. For example, if you're in a login dialog but you want editing shortcuts to be available, you must DEFINE the BARs and the POPUP _MEDIT first, along with their key shortcuts, even though the menu does not yet exist.

Also, for what it's worth, if you intend to write automated testing scripts, this is one area you need to master! Runner is very helpful in this regard, because -- regardless of its interval -- it turns itself off in between actions, but you still need to be on your toes. Are you keyboarding a long string into a textbox? Is its valid supposed to fire? Make sure you give it time, possibly by slowing Runner's interval temporarily. Remember that Runner thinks the KEYBOARD action finishes after it issues that instruction, and has no idea about additional events this action may have triggered.


New and improved CLASSDOC

Pertinent files: CLASSDOC.ZIP updated Jul.97, ~25k.

Work on my documenting classes continues. This time, it's not a bug fix or a minor change but a true sea-change in class design as well as the depths of implementation. In fact, there is so much change that you are likely to find some bugs in this version, although I hope not! The basic engine, which works with the VCXs, is quite safe and well tested. It did not change at all here.

If you have previously enhanced or subclassed ClassDoc, I'm sorry to say that these changes or subclasses will not be immediately transferrable to the new versions. Please talk to me about what you've done if you would like some advice. My only defense is that the newly-rationalized classtree should be easier to work with in the future!

If you just want to use ClassDoc as-is, without subclassing or customizing, you will find more varied output types (including HTML output) in this version, and reporting on more of the fields and contents of the VCX than before.

All the preference-setting #DEFINEs and notes have been moved to a separate .H file, where they are now joined by localization #DEFINEs for all ClassDoc strings and additional notes on the changes in this version. Separation to the separate .H file was required because, at the moment, there are now two versions of CLASSDOC, both of which share these #DEFINEs.

These two PRGs represent the biggest change in ClassDoc implementation. CLASSDOCTXT.PRG uses the original implementation approach, but CLASSDOCLLFN.PRG uses low level file functions instead. Beyond a measurable speed gain, I discovered that, with large class trees, it's possible for TEXTMERGE to fail because Fox runs out of memory for the textmerge "file map"; this is never an issue with the low levels, which don't keep much in memory, dumping a line at a time to disk.

I updated CLASSDOCTXT.PRG to match the improvements in CLASSDOCLLFN.PRG because I know that some people have been working hard on their own CLASSDOC subclasses, based on TEXTMERGE, and also because I know that some people find TEXTMERGE code clearer to read and grok. However, this will be the last version of CLASSDOC that will be produced in two versions, and all future work will continue using low levels.


FoxPro Directions and FoxPro Personal Growth

BTW... DevCon was great, thanks for asking <s>. Everybody is all pumped up about VFP 6, and I'll probably start talking more about VFP6- specific techniques pretty soon. However, rest assured, everything in this FoxSpoken volume should work fine for you in VFP 5.

Meanwhile, the debate continues, both at DevCon and outside it, about "the direction in which MS is taking Fox", "how the marketing campaigns are going", yada yada. (Yes, that expression has reached us in New Zealand -- at the approximate time Seinfeld is going off the air in the United States <g>.)

Hank Fay recently made these astute comments to me, and graciously gave me permission to Share Them With The Group:

I have decided that most developers don't know why they are using VFP. Sure, the data engine and access to it, and c/s stuff. But those points can be argued away, mostly, when going against sql server is the context. The difference is that VFP is a program product environment (e.g. creating builders ad hoc from the command window; e.g. populating the dbc from prg's tuned from the command window) which is also object oriented. It is the only one of MS's tools which is both OO and a production environment. Because of the high latency of feedback in VC++ and VJ, and the distance between design and runtime modes, they don't fit my idea of a production environment. [And] neither VB nor Access are OO.

This, it seems to me, is an important point, easily overlooked. So much of what I know about any programming language and development environment is determined by my ability to play in it and get quick results as I play. The quicker I can test and fine-tune an assumption, the quicker I can move forward. In VFP, I write a quick DEFINE CLASS construct, do a _SCREEN.AddObject(..) to instantiate the class, and I can see instantly what I need to do next. If I need to make some kind of global change, I can apply what I've learned to my class libraries. If there is something specific I'm trying to solve, I can apply whatever I've found out to the elements of the errant form by writing a quicky builder on the spot.

How about you? Can you think of another development environment that makes experimenting this accessible, and this rewarding?

Hank was a DevCon virgin this time around, but he's a longtime Fox developer. So far as I can see he's happier'n a clam doing business with VFP, and so are many other developers who plan to get their work done using this excellent product.

So, personally, I'm still finding it difficult to get worked up over MS's marketing strategy one way or the other. As always, if you feel differently, you should give whatever feedback you deem appropriate to MS representatives. You can "vent" to me (or preferably on the public newsgroups, where I'll listen as patiently as anybody) if you want, too, but it isn't going to do much good, IMHO.


Lisa Slater Nicholls