Conditional formatting in a Visual FoxPro grid

How to apply formatting to some of the cells or rows in a VFP grid but not to others.

By Dan Macleod

If you work with grids in Visual FoxPro, you will sooner or later need conditional formatting. Put simply, conditional formatting lets you change the appearance or behavior of individual cells - or entire rows - in the grid according to conditions in the underlying data.

Here are some examples of things you can do with conditional formatting:

Introducing the "dynamic" properties

The key to conditional formatting in VFP grids is a group of properties whose names begin with the word "Dynamic". Examples include DynamicFontBold and DynamicForeColor. Each of these properties controls a particular type of formatting, as indicated by its name.

There are 13 such properties. They are:

DynamicAlignmentDynamicFontItalicDynamicFontSize
DynamicBackColorDynamicFontNameDynamicFontStrikeThru
DynamicCurrentControlDynamicFontOutlineDynamicFontUnderline
DynamicFontBoldDynamicFontShadowDynamicForeColor
DynamicInputMask

To see how these properties work, let's look at the above examples in more detail.

Highlighting exceptional data

The grid in Figure 1 shows stock levels for a products table. In order to draw attention to out-of-stock items, we want to show the names of these items in red.

Figure 1

Figure 1: This grid shows out-of-stock items in red.

To achieve that, we add the following code to the grid's Init method:

This.Column2.DynamicForeColor = "IIF(Products.In_Stock>0, 0, 255)"

As you can see, we're using the DynamicForeColor property to dynamically control the foreground color of the Name column.

Like all the "dynamic" properties, DynamicForeColor is a property of the column. In each case, the property is a character string which contains a Visual FoxPro expression. The expression is evaluated when the grid is initialized, and again each time it's refreshed. The value of the expression determines the value of the corresponding property (which in this case is the ForeColor property) for each row in turn for that column.

In this example, the expression is an IIF() function. For each row in the grid, the function looks at the value of the In_Stock field. If this is greater than zero, it means that the product is in stock, so the function returns 0 (which is the RGB code for black). Otherwise, it returns 255 (the code for red).

There are two important points here. First, the expression must include a reference to at least one of the fields in the underlying table. If it didn't, it would return the same value for each row. While that's perfectly legal, it defeats the object of using a dynamic property.

The second point is that the expression must be delimited by double-quotes (or one of the other string delimiters). Remember, the property contains an expression, not the value of the expression. If the delimiters were omitted, the expression would be evaluated straight away, and its result (0 or 255 in this case) placed in the DynamicForeColor property. That would lead to an "invalid data type" error (because the expression evaluates to a number but the property expects a string).

Note that the string delimiters are only needed if you assign the property from within your program code, as we're doing here. If you prefer to type the expression in the Properties window, you would omit the delimiters, just as you would for any other character-type property.

Formatting an entire row

In the above example, it was the Name column that we wanted to show in red. So it was that column whose DynamicForeColor property was used to control the formatting.

But what if we wanted the conditional formatting to apply not just to one column but to the entire row? We could achieve that by storing the same expression in the DynamicForeColor property of each of the columns in turn. But there's a shortcut:

This.SetAll("DynamicForeColor", "IIF(Products.In_Stock>0, 0, 255)")

Here, we are using the grid's SetAll method to store the same expression in the DynamicForeColor property of all the columns at the same time. All VFP container objects have a SetAll method that can be used to make bulk assignments in this way. It's got nothing to do with conditional formatting, but it is a convenient way of setting the properties of multiple objects - in this case, column objects - within a container.

Shading alternate rows

Users sometimes like their grids to look like the one in Figure 2, with a different background color or shading in alternating rows. This makes the grid easier to read and more visually appealing.

Figure 2

Figure 2: Shading alternate rows makes the grid easier to read.

This is easy to achieve. All you need is code like the following in the grid's Init method:

this.SetAll("DynamicBackColor", ;
  "IIF(MOD(RECNO('Products'), 2) = 0, ;
    RGB(255, 255, 255), RGB(255, 255, 160))")

In this case, we're using the grid's DynamicBackColor property to control the formatting. As before, the property contains an IIF() function. Where the record number in the underlying table is even, the function returns the color code for white; and where it's odd, it returns the color code for pale yellow, thus producing the desired effect.

Obviously, this will only work if the record numbers are consecutive and in the right order. It won't work if the table has an index in force, or if not all the records are present in the grid (because of deleted or out-filtered records). In those cases, the easiest workaround is to use a SQL SELECT statement to create a cursor which you then use to populate the grid; the cursor will contain the records that are to be shown in the grid, in the order in which they are to appear.

Showing different images in different rows

For our last example, take a look at the grid in Figure 3. This shows a list of orders, with a small icon in the left-most column to highlight any problems with an order. Different icons are used to indicate the severity of the problem.

Figure 3

Figure 3: This grid shows different icons in different rows.

Unfortunately, Visual FoxPro does not provide a DynamicPicture property. But we can achieve the same goal with another conditional formatting property: DynamicCurrentControl.

What we need is three image controls in column 1. These will represent, respectively, a valid order, a minor problem and a severe error. We'll name these controls imgOK, imgWarning and imgError respectively. (For information about how to add controls to the columns of a grid, see my article, Understanding the Visual FoxPro grid control.)

Next, assign a suitable graphic file to each of the image controls' Picture properties (for the imgOK control, I used a "blank" image, that is, an image in which every pixel is fully transparent). Set the Stretch property of each image control to "1 - Isometric", and set Column 1's Sparse property to .F.

Finally, add this code to the grid's Init:

this.Column1.DynamicCurrentControl = ;
  "ICASE(Orders.Error_Level = 1, 'imgWarning', " + ;
  "Orders.Error_Level = 2, 'imgError', 'imgOK')"

The underlying Orders table has a field named Error_Level that tells us how severe the problem is (0 = OK, 1 = warning, 2 = severe error). The ICASE() function in the above code returns the name of the image control that is to be displayed for each of these three values (note that it's the name of the control, not the name of the image file that's returned). This in turn becomes the current control for the row in question. In this way, the grid shows the appropriate icon in each case.

Summing up

Conditional formatting can provide many useful ways of enhancing your Visual FoxPro grids. I hope this article has shown you some of the possibilities, and has given you some ideas for using these techniques in your own applications.

March 2012

Please note: The information given on this site has been carefully checked and is believed to be correct, but no legal liability can be accepted for its use. Do not use code, components or techniques unless you are satisfied that they will work correctly with your sites or applications.

If you found this article useful, please tell your friends: