April 02, 2010

Life Has Just Gotten Easier

This is going to be a "short and sweet" blog. OK, since I was vaccinated with a phonograph needle, it will be as "short" as *I* can make it.

The purpose of this blog is twofold. First, to help spread the word about a Visual FoxPro issue (and the solution) that has been annoying me for several years. Second, to give credit where credit is due and point you to the source of the solution; namely a recent Doug Hennig blog.

As I said, it has been an annoying issue, but one that was not so bad as to actually cause me to get off my big, fat fanny and do something about it. Fortunately, the ever-tenacious Doug Hennig did and was generous enough (isn't he always?) to publish his findings.

OK, the issue is... most of us Visual FoxPro developers know that it is good form to NOT save the printer environment with the report form. Sorry, but if you do not know this, it is a topic for discussion at another time. Or you could "Google it". Nevertheless, as diligent as I may be in not saving the printer environment, there have been times where it was saved. I could never capture that point in the development of the report form when it happened. The main reason for that is because (usually) when I noticed the printer environment had been saved, it was way past the point at which it happened. Like I said, annoying like a gnat buzzing around your face at a picnic; not like a nine inch nail driven into your brain.

Enter Doug Hennig to the rescue. I am not going to reveal the solution here, but suffice to say that it is a relatively easy fix which can be accomplished in about five minutes. Here is the URL to Doug's blog... http://doughennig.blogspot.com/ Look for the blog entitled, "Fixing a Report Designer Bug".

Thanks again, Doug for sharing.

Posted by Dave Aring on April 2, 2010 | Permalink | Comments (3) | TrackBack

October 17, 2008

Phoenix in the fall; Arizona in the autumn

If you aren't at Southwest Fox 2008 in Mesa, AZ (http://www.swfox.net/) this weekend, you're missing a real treat.  Tamar Granor, Doug Hennig and Rick Schummer have once again done an excellent job organizing a conference that is a tremendous value for your money.

To give you taste, here are some of the sessions I attended today:

- In "Profiling and Refactoring: How to Analyze and Clean up Your Code", Andrew Ross MacNeill talked about a powerful tool in VFPX called Code Analyst that can help you identify "issues" in your code (e.g., too many lines of code in a single routine, too many comments, too many loop structures, has more than one return value, etc.).  (VFPX is a Visual FoxPro Community effort to create open source add-ons for Visual FoxPro 9.0).  You can select which rules you want run against your code; you can also create your own rules.

You can download Code Analyst for free from the VFPX CodePlex web site: http://www.codeplex.com/VFPX.

Andrew also discussed an add-in for the Coverage Profiler.

- Christof Wollenhaupt gave an introduction to mobile development; employing his product, Guineu (Catalan for "Fox"), you can develop mobile applications from within Visual FoxPro. Cool!

- Doug Hennig talked about Advantage Database Server for VFP Developers; with this alternate back-end, you can either read VFP .DBF files directly or you can move your data into the proprietary ADD format.  With Advantage Database Server (ADS), your .DBF files are no longer limited to 2 GB.  "In ADS, there isn't a direct limit on the size of the file; instead, the limit is a maximum of 2 billion (2,147,483,648) records. Of course, if your DBF becomes larger than 2 GB, you'll only be able to access it through ADS since VFP will see it as invalid."

"ADS [also] has a fast and powerful full text search (FTS) feature. FTS uses an index on each word in a memo field to provide fast, index-based lookups for desired words."

- Rick Schummer did a presentation focusing exclusively on "Using VFPX Components"; he demo-ed various controls like Themed Controls (e.g., OutlookNavBar), the ctl32 project (e.g., StatusBar and BalloonTips [tooltips on steroids]) and Desktop Alerts.  VFPX controls add pizzazz to your VFP applications, giving them a more polished/professional appearance. You can download these controls for free from the VFPX CodePlex web site (http://www.codeplex.com/VFPX) and then implement all of these controls in your VFP applications

- In "Creating Owner Drawn Controls in VFP", Christof Wollenhaupt showed us how to "exploit the power of GDI+ to create our own controls" in VFP forms (e.g., "new pageframes, progress bars, formatted text, chart controls, basic text input or Unicode text output").  He also covered "dealing with mouse and keyboard input."

- Bo Durban showed us how to create custom report controls with VFP 9, employing the GDIPlusX library; rotated text, dynamic text formatting, graphs and custom shapes are a few examples of things you can do/include in your reports.

I continue to see the common thread/theme of EXTENSIBILITY with respect to Visual FoxPro (that was also displayed in a 2-day West-Wind Web Connection training I attended on Tuesday and Wednesay this week [Rick Strahl's product enables you to create web applications using VFP and some other technologies like CSS, JavaScript, JQuery, etc.]).

Posted by abergquist on October 17, 2008 | Permalink | Comments (6) | TrackBack

September 25, 2008

Dump unnecessary leading equals signs ("=")

You will often see a leading equals sign ("=") in older VFP code (especially in code ported over from FoxPro 2.x).  A leading equals sign is necessary only in a couple of instances in VFP:

  - when calling the SEEK() function  and not storing the return value, e.g.,

      USE (HOME(2) + 'Data\Customer') IN 0
      =SEEK('Great Lakes Food Market', 'Customer', 'Company')

  - when denoting an expression in a property in the Properties Sheet/Window; for example, when setting the .Caption property of _Screen:

      ='My Application (Version # ' + m.gcVersionNo + ')'

Side note, when setting the .Picture property for an Image control, include the equals sign as follows:

      ='MySplash.JPG'

If you specify MySplash.JPG without the equals sign and character delimiters, VFP will attempt to insert the path in front of the picture's file name; the problem is that that path may not (in fact, most likely will not) be on your customer's PC (when your application is production).

Posted by abergquist on September 25, 2008 | Permalink | Comments (0) | TrackBack

September 04, 2008

Best practices when macro substituting

When working with macro substitutions …

1) bring “closure” to them

  Get in the habit of always “closing” your macro substitution with a period (“.”); for example:

    &lcSQL.

  That way, when you employ macro substitution as part of an object hierarchy, VFP will properly parse the line; for example, if you have a form with a commandbutton whose .Name is ' cmdButton':

    The following code sets the .Caption of 'cmdButton' to 'Process':

      LOCAL lcCommandButton
      lcCommandButton = 'cmdButton'
      ThisForm.&lcCommandButton..Caption = 'Process' && Two (2) periods before .Caption

    whereas the following code triggers a “Property CMDBUTTONCAPTION is not found” error:

      LOCAL lcCommandButton
      lcCommandButton = 'cmdButton'
      ThisForm.&lcCommandButton.Caption = 'Process' && Only one (1) period before .Caption

2) don’t embed them

    lcControlName = 'cmdButton'

  Replace the cryptic, embedded macro substitution:

    ? 'oForm.&lcControlName.'

  with the following, easier-to-read (and, therefore, easier-to-maintain) code:

    ? 'oForm.' + m.lcControlName

  (The above should run faster, too, since it does not employ macro substitution.)

Posted by abergquist on September 4, 2008 | Permalink | Comments (1) | TrackBack

August 26, 2008

Don't Think "Tomorrow"; Think "A Decade from Now"

The always entertaining and thought provoking, Rick Schummer, had a blog recently, ( Rick's Blog - “Getting Started with Visual FoxPro") that caused me to stop and reflect upon his heartfelt advice. What I came up with is not particularly pretty for me, but it was a moment of truth for me.


Before I go any further, let me say I have been using FoxPro in one version or another since 1989. I have made a good living thanks to that fine product. My custom license plates read “FOXPRO”. Get the point?  I L-U-V the software! I will continue to use and work with the product until the day I retire. Fortunately, for me, that day is not that far away. Timing is everything. For me, the timing is that I should be able to run out my career and the last line of code I write will be... RETURN. However there are others who have 25, 30, 40 years left in the work force and my advice to them is... DON’T WASTE YOUR TIME WITH FOXPRO.


I can hear many of you shouting, “Blasphemer!”. Maybe I should even be shouting at myself, but the truth of the matter is, FoxPro will not be viable software in a few (plug in your own definition of “few”) years. Rick’s advice for anyone wanting to learn FoxPro was dead on. He did an excellent job of covering all of the bases. My question is WHY would anyone want to learn FoxPro at this point in history? The only two reasons I could come up with are because the dwindling ranks of FoxPro developers have caused companies, still using legacy FoxPro application, to get someone, anyone, to maintain and enhance their applications OR the individual is wanting to learn VFP because it is still an awesome hunk of software that is VERY cost effective; particularly in third world countries.

In the first case, the boss probably went to an employee and said, “We need to have you learn FoxPro.”  The apparent up front cost of converting a legacy application is, in many cases, too much for a small to medium business to bear in these somewhat difficult economic times. The application, basically, still does what they need it to do, but once in a while, it just needs some tweaking.  The “quick fix”, in management’s mind, is to invest a minimum amount and “throw another body” at the problem. This only delays the final decision to rewrite or replace the existing legacy application. Ignoring the thought that if the employee refuses to “waste” their time learning “old technology” they might be fired, any developer worth their weight in salt would prefer to learn current technologies, thus making themselves more valuable both to their present company and when they re-enter the job market. Learning FoxPro serves his company well (momentarily), but it does not serve the developer’s best interests. Soon, that developer will realize his stagnant position and move on causing the problem to arise again and the same solution to be more expensive than it was earlier. See a pattern here?


In scenario number two, each desire to learn VFP has many reasons. I have no problem with anyone weighing their options and deciding that Visual FoxPro is the answer to their solution.  Many times, it actually IS the best solution; even now. However, it would seem to me that it would have to be a very special set of circumstances to arrive at that conclusion. In that case, once again, Rick’s advice is the way to go.


Returning to scenario number one... What would Rick’s answer have been had the question been, “How do I get started learning COBOL? My guess is that Rick would have asked WHY they wanted to learn COBOL. Now is the time to convert or rewrite legacy applications. It will never be less expensive; aren’t labor costs always going to increase? It will never be easier; the project will only grow in scope as the legacy app grows. At the very least, a far-seeing IT manager should realize what is inevitable and begin to make plans to move forward. In the meantime, and this will sound like an advertisement, the most cost effective solution in the long run is to hire the right people who already have the experience and knowledge necessary to keep the application afloat while your company prepares to move forward and stay ahead of the competition. The people you hire to maintain the legacy app could even be involved in the conversion.


Comments?  I would love to hear them; I have thick skin.

Posted by Dave Aring on August 26, 2008 | Permalink | Comments (4) | TrackBack

August 25, 2008

When a "Watch Window" expression goes in and out of scope

It used to be extremely frustrating to me when a "Watch Window" expression went in and out of scope.  Now, I simply employ a technique I learned in Nancy Folsom's short but excellent book Debugging Visual FoxPro Applications (http://www.hentzenwerke.com/catalog/debugvfp.htm).

On p. 53, Nancy indicates that:

  "If you know the value of the expression you're looking for, you can create a 'Break when expression is true' breakpoint and enter a conditional IIF() in the Expression field.  The expression would look like the following:

  IIF(TYPE('SomeExpression') = 'C', SomeExpression = SomeValue, .F.)

  "This expression will cause the program to be suspended only when the value of the expression is what you expect."

Here's an example of an expression:

  IIF(VARTYPE(RECCOUNT('lv_WDates')) = 'N', RECCOUNT('lv_WDates') = 6, .F.)

Michael Cummings of the Los Angeles FoxPro Users Group (LA Fox) suggested an even better technique [viz., test for VARTYPE(…) = 'U']; here's a translation of the above:

  IIF(VARTYPE(RECCOUNT('lv_WDates')) = 'U', .F., RECCOUNT('lv_WDates') = 6)

Testing for an 'U'ndefined data type obviates having to get the variable's data type "right" in the breakpoint expression.

Patrick O'Hara of the Chicago FoxPro User/Developer Group (CFUDG) reminded me that [Ctrl + B] brings up the Breakpoints dialog box.

Posted by abergquist on August 25, 2008 | Permalink | Comments (2) | TrackBack

August 21, 2008

Debugging errors that occur only in production code

There are a few techniques to debug errors that occur only in production:

1. Insert the following line:

MESSAGEBOX('<n>. ' + PROGRAM() + ' ...')

at "strategic" points in the program (e.g., before calling a function, method, procedure, etc.).  I start "n" at 1 and increment by 1 in subsequent MESSAGEBOX() calls.  The last number that displays in a MESSAGEBOX() before an error occurs helps me narrow down exactly where the problem is occurring.  If necessary, I delete the original MESSAGEBOX() calls and then insert new MESSAGEBOX() calls "closer" to where the error is occurring.  (Repeat as necessary [and, yes, you can -- and should -- try this at home <g>].)

2. Another technique is to write text to a log file (.Log, .Txt, etc.) at various points of/during program execution.  (This technique, by the way, is also useful for debugging COM objects since you cannot step through code in the VFP debugger nor can you [or are supposed to] send output to the screen from a COM object.)

3. In VFP 9, you can do coverage logging not only at design time but also in .EXEs (via the SET COVERAGE command).  Your client will need to send you the coverage log file  (generated by the SET COVERAGE command) which you can then run through the Coverage Profiler yourself.  Put code like the following toward the top of your main calling routine:

ON ERROR SET COVERAGE TO
SET COVERAGE TO <filename of your choice>.Txt

Posted by abergquist on August 21, 2008 | Permalink | Comments (6) | TrackBack

August 16, 2008

Make use of methods for complex Dynamic... grid settings

In a grid's Init() event method:

  *--------------------------------------------------------------------------
  * Set the .DynamicBackColor property for this column.
  *--------------------------------------------------------------------------
  This.Column1.DynamicBackColor = [ThisForm.DynamicBackColorForThisColumn()]

In the form's custom DynamicBackColorForThisColumn() method:

  *-----------------------------------------------------------------------------------
  *  Method: DynamicBackColorForThisColumn()
  * Purpose: Handles the initialization of DynamicBackColor for the grid's Column
  *
  *   Notes:  It is *far* easier to put the logic into this method (and allows
  *     us to document it thoroughly) rather than trying to "stuff" text
  *   into the grid's .Column1.DynamicBackColor property.
  *
  * Plan Codes:
  *  01 - Bill full cost
  *  07 - Zero AtP (do not send bill)
  *  08 - Zero AtP (do send bill)
  *-----------------------------------------------------------------------------------

  LOCAL lcColor
  DO CASE
    CASE AtP_PlanCd = '01'
      lcColor = 'RGB(255, 255, 255)'

    CASE AtP_PlanCd = '07'
      lcColor = 'RGB(0, 0, 0)'

    CASE AtP_PlanCd = '08'
      lcColor = 'RGB(192, 192, 192)'

    OTHERWISE
      lcColor = '<some default color>'
  ENDCASE

  RETURN lcColor

The above could have been coded in the grid's Init() event method as follows:

  *-----------------------------------------------------
  * Set the .DynamicBackColor property for this column.
  *-----------------------------------------------------
  This.Column1.DynamicBackColor = [IIF(AtP_PlanCd = '01', 'RGB(255, 255, 255)', ;
    IIF(AtP_PlanCd = '07', 'RGB(0, 0, 0)', IIF(AtP_PlanCd = '08', ;
  'RGB(192, 192, 192)', '<some default color>')))]

Which implementation do you think is easier to read, debug and maintain?

Even with VFP 9's new ICASE() function, it's still challenging to read, debug and maintain:

  This.Column1.DynamicBackColor = [ICASE(AtP_PlanCd = '01', 'RGB(255, 255, 255)', ;
    AtP_PlanCd = '07', 'RGB(0, 0, 0)', AtP_PlanCd = '08', 'RGB(192, 192, 192)', ;
    '<some default color>')]

(By the way, the form-level method could alternatively be implemented as a [class-level] method of a grid class.)

Posted by abergquist on August 16, 2008 | Permalink | Comments (0) | TrackBack

August 12, 2008

Employ SQL-SELECTs "TO SCREEN" clause to determine if a record exists

To determine if a record exists in a table, I used to send the results of a SQL-SELECT to an array, like so:

  LOCAL ARRAY la[FieldName][1]
  _Tally = 0
  SELECT [FieldName]     ;
    FROM [Database]![TableName]    ;
    WHERE [FieldName] = [Memory Variable]  ;
    INTO ARRAY la[FieldName]
  IF _Tally = 0
    MESSAGEBOX(TRANSFORM([Memory Variable]) + ' is not a valid [Whatever] ',
              0, '[MESSAGEBOX() Caption]')
  ENDIF

Sending the results of the SQL-SELECT TO SCREEN NOCONSOLE  obviates the need to declare an array prior to doing the SQL-SELECT:

  _Tally = 0
  SELECT [FieldName]     ;
    FROM [Database]![TableName]    ;
    WHERE [FieldName] = [Memory Variable]  ;
    TO SCREEN NOCONSOLE
  IF _Tally = 0
    MESSAGEBOX(TRANSFORM([Memory Variable]) + ' is not a valid [Whatever] ',
              0, '[MESSAGEBOX() Caption]')
  ENDIF

In different versions of VFP up to and including VFP 8, _Tally was not always reliably accurate (cf. http://foxproadvisor.com/doc/12139 [Cathy Pountney's "_TALLY trap tip]); if that is your experience, simply send the output to a CURSOR and then check the RECCOUNT() of the cursor.

Enjoy!

Art Bergquist

Posted by abergquist on August 12, 2008 | Permalink | Comments (1) | TrackBack

August 09, 2008

USE IN SELECT() to close a work area

In lieu of:

  IF USED(<Work Area>)
    USE IN '<Work Area>'
  ENDIF

and:

  USE IN IIF(USED(<Work Area>) …

employ code like the following:

  USE IN SELECT('<Work Area>')

Like the first example above, this one will work regardless of whether the work area is in use or not (the second example will bomb if the specified work area is not in use).

Yours for more concise (yet still readable) code,

Art Bergquist

Posted by abergquist on August 9, 2008 | Permalink | Comments (0) | TrackBack