The Kit Box: And Here are the Highlights... 

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.

And Here are the Highlights...

Andy Kramek and Marcia Akins

This month, Andy Kramek and Marcia Akins discuss a simple task: how to highlight the currently selected row of a grid. As Andy finds out, it's actually pretty complicated to do this properly in VFP 7.0 but has been made extremely simple in VFP 8.0, though there are a few minor quirks. Yet another reason to upgrade to VFP 8.0 earlier rather than later.

Andy: Hey, Marcia, how do you make a grid row highlighted? I know this is an easy thing to do, but I just can't seem to get it right.

Marcia: It depends on what version of Visual FoxPro you're using. In all versions prior to 8.0 you had to handle that task with a little bit of code in your grid's base class.

Andy: Oh! I didn't realize that things had changed in Version 8.0. Perhaps you'd better tell me how to do it in Version 7 first (I'm not sure when my users will upgrade to Version 8.0), and then we'd better see what's changed. Does it really make much difference?

Marcia: Yes. It's very much simpler in Version 8.0. As with so many of the new features, the Visual FoxPro team has really listened to their users and implemented things that people have asked for. This is just one of many great enhancements.

Andy: Okay, where do we start then? My grid base class is basically just a copy of the Version 7.0 base class, but even though I have the HighlightRow property set to True, the row doesn't seem to highlight properly.

Marcia: Oh Andy, really! Surely you know that the HighlightRow property has nothing to do with highlighting the row. That would be too easy! What it does is outline all of the cells in the currently active row; it doesn't really "highlight" it in the normally accepted sense of the word. You'd better set that property to False in your class because it doesn't do what you want and it also degrades performance.

Andy: Well, you may be surprised, but I didn't know that—after all, you're the one they call "Queen o' the Grids," not me. So I turn off the property, what then?

Marcia: To get a real highlight, we need a couple of custom properties. First we need one to store the record number that's being pointed to by the grid's ActiveRow property. We should also add a couple of properties to define the colors to use for the highlighting. I personally like to use the Windows Script Host to find out what the user has set up in the Windows Control Panel and use those colors. This is all the code required:

  loShell = CreateObject( 'WScript.Shell' )
lcColor = loShell.RegRead( ;
                 'HKCU\Control Panel\Colors\Hilight' )
lnBgColor = EVALUATE( 'RGB( ' + ;
                STRTRAN( lcColor, ' ', ', ' ) + ' )' )
lcColor = loShell.RegRead( ;
             'HKCU\Control Panel\Colors\HilightText' ) 
lnFgColor = EVALUATE( 'RGB( ' + ;
                STRTRAN( lcColor, ' ', ', ' ) + ' )' )

Andy: However, in this day and age, you can't be certain that some system administrator hasn't disabled the Windows Scripting Host. In that case, we'd need to use the Windows API to get those defaults. There's a single function that will retrieve the specified color setting, and it gives you back the color number directly—no need to use RGB() to convert it. All you need is the numeric constant for the ones you want. In this case, the constant for the highlight color is 13, and for highlighted text it's 14, so we can just use this:

  DECLARE INTEGER GetSysColor IN "user32" INTEGER nIndex
lnBgColor = GetSysColor( 13 )
lnFgColor = GetSysColor( 14 )

Marcia: Either way will work for me. For the sake of this example, why don't we just keep it simple and use navy blue text on a cyan background. We can store this information in two custom properties: nHighFGColor and nHighBGColor.

Andy: Let's see, navy blue is RGB(0, 0, 128). That translates to the value 8388608; cyan, which is RGB(128, 255, 255) translates as 16777088. So I'll just use those values as the defaults, yes?

Marcia: Yes. Now you need a little code that can be called from the grid's Init() to ensure that it all works.

Andy: When you say "called from the grid's Init()" I assume that you actually mean that the code is placed in a method named SetGrid() that's called from the Init()? After all, we never code in events, do we—always in methods.

Marcia: Of course! So place the following code in your SetGrid() method:

  *** Set up for highlighting current row
This.nRecNo = RECNO( This.RecordSource )
This.SetAll( 'DynamicForeColor', ;
 'IIF( RECNO( This.RecordSource ) = This.nRecNo, ;
       This.nHighFGColor, This.ForeColor )', 'COLUMN' )
This.SetAll( 'DynamicBackColor', ;
 'IIF( RECNO( This.RecordSource ) = This.nRecNo, ;
       This.nHighBGColor, This.BackColor )', 'COLUMN' )

Andy: That's all? That seems pretty straightforward. If the record number in the underlying data matches the one that we have stored, we highlight the row; otherwise, we set the row to its default colors. Though I have to say it's not exactly obvious how that will handle the highlighting as I navigate between rows.

Marcia: There's a good reason for that. It won't. You now need some code in the When() event of every control in your grid to force the record number property to be updated so that the highlighting will work. It's pretty simple too:

  WITH This.Parent.Parent
  .nRecNo = RECNO( .RecordSource )
ENDWITH  

Andy: Oh, that's a bit of a pain to have to do manually. We'd better create a special grid text box class that has that code already in it. Of course, we then have to make sure that we use the special text box in our grid class. To do that we'd better create a builder to set things up for us. This is getting complicated!

Marcia: It's worse than you think, since you'll also need a special combo box class, and an edit box and any other control that you may want to use inside a grid. They'll need to be handled by your builder too. In fact, I wrote such a builder for KiloFox (for details of this builder, see Chapter 12 of 1001 Things You Wanted to Know About Visual FoxPro, Hentzenwerke Publishing, 2000).

Andy: Ouch! Well, for the moment let's just create the text box class and manually add it to the grid in the instance. There are only two columns anyway. It looks just fine to me (see Figure 1).

Marcia: There's just one other thing that we need to deal with. If you only ever use the cursor keys to navigate in the grid, this is all going to work just fine. However, if you use the mouse to click on a row that isn't highlighted, there's a problem (see Figure 2).

Andy: Looks like the grid isn't re-evaluating the Dynamic color value for anything but the selected row.

Marcia: Exactly. So what we have to do is set our custom nRecNo property to zero in the grid's BeforeRowColChange(). That will force the colors for the current row to be reset to the defaults before we navigate to a new row—thereby clearing the highlight. However, there's still one issue we haven't addressed. What do you want to happen to the highlight when the grid loses focus?

Andy: What do you mean, "happen to it"? Won't the current row just stay highlighted when the grid doesn't have focus? That's certainly the behavior I'd expect.

Marcia: Well, if we set the nRecNo property to zero unconditionally in the BeforeRowColChange(), this means that it will also be set to zero whenever the grid loses focus. The result is that the highlight will disappear.

Andy: Well, we definitely need to fix that, whatever it takes. I can't have the highlight only as long as the grid has focus. It would be very confusing for users to see it appear and disappear as they worked with the form.

Marcia: Fortunately it's not too bad. We need to add a flag property to the grid class (lAbout2LeaveGrid) that indicates whether we're merely changing rows (in which case we want to reset nRecNo), or navigating to a new control on the form (in which case we don't). So the final code in BeforeRowColChange() looks like this:

  IF NOT This.lAbout2LeaveGrid 
  This.nRecNo = 0
ENDIF  

Andy: That makes sense. The only question is where should we set this flag?

Marcia: The simplest approach is to use the When() and Valid() events of the grid because they fire as the grid gains and loses focus. We just set the flag to False in the When() and True in Valid().

Andy: Simple, really! Whew! This was hard work—no wonder I couldn't get it to work properly on my own. Although there's not very much code, it's cumulative and it's all critical. How do we do this in VFP 8.0?

Marcia: It's much easier. The VFP 8.0 grid class has three new properties that deal with highlighting the current row:

  • HighlightBackColor
  • HighlightForeColor
  • HighlightStyle

Andy: Let me guess, the first two are for choosing the highlight colors for the background and text. So they replace the necessity to use DynamicBackColor and DynamicForeColor. That's certainly simpler—I never found those "dynamic" properties very intuitive.

Marcia: They are simpler, but they still don't work in exactly the way that you might expect them to. The first thing that you have to do is set the HighlightStyle to something other than the default value of "0-No grid row colored highlighting."

Andy: Presumably this is the default so as to maintain backward compatibility for grid classes created using earlier versions of the product. All of the code that we wrote to highlight the current grid row in Version 7 still works in Version 8, but only so long as the grid's HighlightStyle property is left at its default value.

Marcia: If you don't set the HighlightBackColor and HighlightForeColor properties explicitly, VFP reads the values from the Windows Control Panel. This is a good thing. However, by default, VFP uses a 50 percent gradient of the colors specified. This is a bad thing! Why on earth they chose to do it like this is beyond me because, presumably, if I've chosen highlight colors in the Control Panel, they're the ones that I actually want to use, not some weird looking "gradient" color (see Figure 3)!

Andy: It does seem like an odd decision, but at least if you do specify the colors to use, they're displayed correctly. So we can use the same code as we did in our VFP 7.0 grid class to get the colors (either using the WSH or the Windows API) but then, instead of setting DynamicBackColor, we set the new properties (see Figure 4):

  This.HighlightBackColor = lnBgColor
This.HighlightForeColor = lnFgColor

Marcia: We still haven't discussed the HighlightStyle property. It has two values that can be used to set the grid highlight: "1-Current row highlighting enabled" and "2-Current row highlighting enabled with visual persistence." The only difference between the two is that using the first setting causes the grid's highlight to disappear when the grid loses focus.

Andy: Well, in my VFP 8.0 base grid class, I'll want HighlightStyle set to "2-Current row highlighting enabled with visual persistence." Personally, I don't find that first setting very useful at all!

Marcia: I can only assume that this setting is used to make the grid behave in the same way as other controls that have a HideSelection property. However, if that's the case I wonder why they didn't just give the grid a HideSelection property instead of overloading the style setting like this.

Andy: Beats me too. Is there anything else we have to do, though?

Marcia: No, but there is something that I think is probably a bug. If a column in the grid has its Sparse property set to False, it doesn't get highlighted (see Figure 5). This means that if you have a grid that changes the setting of Sparse in order to display controls in every row—when you have a check box in the grid, for example—you have to handle the row highlighting the old-fashioned way.

Andy: That certainly does seem like a bug. I noticed another new property in the grid, too—does the setting of the AllowCellSelection property affect row highlighting at all?

Marcia: No. It's a grid-level setting that determines whether you can actually enter any of the grid's cells. This property makes it much easier to create a grid that behaves like a list box.

Andy: Hang on! That sounds like another discussion altogether. We really have spent much more time than we planned on highlighting, so let's continue next month with a more detailed look at the new VFP 8.0 grid.

Marcia: Goody. There are lots of really cool things you can do with the new grid class.

Download 03KITBOX.exe

To find out more about FoxTalk and Pinnacle Publishing, visit their website at http://www.pinpub.com/html/main.isx?sub=57

Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.

This article is reproduced from the March 2003 issue of FoxTalk. Copyright 2003, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. FoxTalk is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-493-4867 x4209.