Griddy with Anticipation
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.
Griddy with Anticipation
Andy Kramek and Marcia Akins
As promised last month, Andy Kramek and Marcia Akins take a detailed look at the new features in the Visual FoxPro 8.0 grid class. Despite, or perhaps because of, its complexity, the VFP grid hasn't seen many major changes over the years. Whilst by no means the most significant things in this release, there have been some real improvements to the grid class that will make our lives easier.
Marcia: We finished up last month by mentioning that there had been several changes to the grid class in VFP 8.0, but you wouldn't let me talk about them. I'm really excited by some of the things that we can now do with grids.
Andy: Really? I know that you've always been a great proponent of the grid, but I thought that it was pretty much a done deal. It hasn't changed much the past couple of versions anyway—though I'm glad that the row highlighting has now been simplified (see last month's column for details). What else is new?
Marcia: Probably the first thing that you'd notice is that the grid's native Refresh() method now causes the record marker to be updated, even when the grid doesn't have focus.
Andy: You mean it didn't before? (See, I hadn't even noticed that.)
Marcia: Absolutely not. Figure 1 shows a form where the code in the button's click moves the record pointer 10 records and then calls the form's Refresh() method. After clicking the button under VFP 7.0 the grid updates and the thumb on the scrollbar moves, but the record mark remains on record 1 unless you explicitly set focus to the grid. In VFP 8.0 running the same form, the record mark is correctly positioned on the current record without the need to set focus back to the grid.
Andy: A small change, admittedly, but significant nonetheless. Okay, what next?
Marcia: The grid column's Visible property has been fixed, too. This one was always a bit of a pain in the past because if you wanted to hide a column, you had to store its width, and then set the width to 0 and restore it later.
Andy: Why, what happened when you set Visible to .F.?
Marcia: See Figure 2! The results were a little less than useful.
Andy: Oh, I see what you mean. Luckily I never used this feature—that would have driven me crazy.
Marcia: It's a short drive, my dear. To move on, another fix that has been made is that resizing a column no longer fires the header's Click() method.
Andy: Huh! So what? I don't see the significance of that one.
Marcia: That's because you don't have a grid class that allows the users to change the sort order by clicking on the header of the column they want to sort by. If you did, you'd know that every time the users want to change the width of a column they also end up re-sorting the grid by that column.
Andy: Oh! Yes, I can see where that could be very irritating for users. Good, so those are the fixes—what's new?
Marcia: Well, the first thing—included by popular demand—is the ability to lock one or more columns in the grid. Users can do this interactively by right-clicking on the line that separates two columns in the header region of the grid. When you do so, the column separator shows as a thickened line and all of the columns to the left are locked.
Andy: By locked, you mean that as you scroll the grid horizontally only the columns to right of the lock line move?
Marcia: Yes. But notice that you can only do it this way as long as the left-most column of the grid is in the visible portion of the grid. As soon as you scroll the left-most column out of sight, you can no longer use the interactive method to lock columns.
Andy: What happens if you don't have a header row in your grid? Is interactive locking disabled?
Marcia: Yes, it is. Like column resizing, you can only do it interactively when the grid's HeaderHeight property is greater than 0. However, you can always do it programmatically—irrespective of what's visible at the time. All you need to do is set the grid's LockColumns property to the required number.
Andy: Hang on, what exactly do you mean by "required number"? If I set it to 3, what happens? Is column number 3 locked, or is it columns 1, 2, and 3 that get locked?
Marcia: Neither! If you set LockColumns to 3, those columns that currently have a ColumnOrder of 3 or less are locked. In other words, it always works from the left-most edge of the grid.
Andy: But hang on. In the Help file it says that:
When any of the locked columns scroll out of the Grid window to the left, they remain out of view and do not become visible again until locking is removed. For example, suppose a grid contains five columns, and the data source has 10 columns. When you scroll two columns to the right, the first and second columns scroll out of the grid to the left.
Marcia: That has to be wrong. The whole point of locking a column is that it cannot be scrolled. So the scenario that's described makes no sense at all. In fact, setting the LockColumns property to any non-zero value always makes the left-most column visible (see Figure 3).
Andy: Whilst clicking around in this grid, I've noticed something else that seems to be new. If you double-click on the column separator, the column to the left resizes itself to the maximum width of its contained values (including the width of the header's caption).
Marcia: Yes, and if you double-click the square immediately preceding the first column header in the upper and left-most corner of the grid, you can auto-fit all columns in the visible portion of the grid at once. Notice that this only applies to the visible portion of the grid. This means that if there are 10 visible rows in the grid, only the contents of those rows are considered when sizing the columns. So if the 11th row contained an extra long value in one of the columns, that column would still only be sized to suit the maximum width that was found in the first 10 rows (and would be truncated).
Andy: I see that the grid has a new property named AllowAutoColumnFit that I guess allows you to turn this auto-fit function on and off?
Marcia: Not exactly. Despite its name, this isn't an On/Off property. Rather, it has the following possible values:
- 0 = Allow auto-resize for all and individual columns (default).
- 1 = Allow auto-resize for individual columns only.
- 2 = Do not allow auto-resize.
Andy: I hate it when they do that. By the way, I see from the Help file that there's an AutoFit() method on the grid, too. Presumably this is the same as clicking that little square and it auto-fits all columns to their contents?
Marcia: Yes. But I'd guess that it's really only a "set-all" because each column has its own AutoFit() method. I suppose it would have been better if the grid level method had been called something else. There's one more thing you need to know about Auto-Fit, though.
Andy: And that is...
Marcia: Auto-fit works for columns that only contain text boxes. If you have some other control in the column (a combo box, for instance), auto-fit no longer works on that column, whether you do it for the grid as a whole, or just for the column in question. It doesn't matter whether the non-text box control is the active control or not, nor whether the column has focus or not. As soon as you include anything else, the auto-fit functionality stops working.
Andy: Hmm. That does seem to limit its usefulness somewhat. Still, it's better than anything we've had to date. One thing we should mention at this point is that although we've been talking exclusively about grids, everything we've covered so far applies to the browse window, doesn't it?
Marcia: Oh yes—the browse window is just a grid, after all. But I see what you're getting at. You can use all of these features in a browse window, as the following code shows:
USE (HOME(2) + "data\customer" ) BROWSE NAME oCust oCust.AutoFit() oCust.Column4.Visible = .F. oCust.LockColumns = 2
Andy: By the way, I notice that if you lock the first two columns, and then make the second column invisible, the lock remains effective and, when you restore the column, it's still locked. Neat!
Marcia: There's one more new property that we should mention at this point—and you can use it in browse windows, too—and that's AllowCellSelection. This property acts at the grid level and prevents any access to any cell in the grid.
Andy: I see that it also keeps the mouse pointer as an arrow, as opposed to what happens when a grid is simply set as Read-Only and you get the "I-Beam" style mouse cursor even though you can't actually change any data.
Marcia: Hmm, that's odd!
Andy: What is?
Marcia: I just set AllowCellSelection to FALSE on a browse window, and take a look at Figure 4 to see what happened.
USE (HOME(2) + "data\customer" ) BROWSE NAME oCust oCust.AllowCellSelection = .F.
Andy: That's weird. Does it happen in a grid in a form too?
Marcia: Nope! This only happens in a browse window. It looks like a bug to me. But simply setting the ForeColor**property to itself restores the normal highlighting though, so it's not too serious (see Figure 5).
oCust.ForeColor = 0
Andy: Nice one; pity you didn't find it until the day after VFP 8.0 was released to manufacturing. Is there anything else we need to cover?
Marcia: It's worth mentioning that the grid has been given a KeyPress() event of its own. I assume that this was necessitated by the addition of the AllowCellSelection property, because when that property is set you can no longer use the contained controls' KeyPress() to trap keystrokes.
Andy: Seems a reasonable assumption. Now, I know we haven't yet mentioned the one thing that I see as a really big change, so you've obviously been saving it for last. Are we there yet?
Marcia: Yes, I think so—enter the "member classes." This is one of the most useful enhancements in this release, and though it doesn't only apply to grids, it's probably most immediately useful when working with them.
Andy: What exactly is a member class?
Marcia: Member classes are those classes that can only exist when contained by other (container) classes. For example, the Page is the member class of the PageFrame class, the OptionButton is the member class of the OptionGroup, and, of course, the Column is the member class of the Grid and the Header is the member class of the Column.
Andy: So what's new about that? These have all been around since VFP 3.0.
Marcia: What's new is the ability to define subclasses of all of these things and to be able to have the parent containers use those subclasses instead of the VFP base classes. Up until VFP 8.0, if you increased the ColumnCount of a grid, what got added was always a VFP base class column. In fact, the only way to use anything other than a VFP base class column was to build the grid programmatically at runtime. Now you can simply set two properties on the grid class (MemberClass and MemberClassLibrary) and even in the visual designers your column classes get added instead of the base class.
Andy: Wow! This is a big change. So can we also design column classes visually now?
Marcia: Actually, no. The column classes still have to be defined programmatically, but you can use the class definitions in the visual designers, and you'll see the visual representation of those columns at both design time and runtime. This is a huge improvement over the old way of doing it when we had to write a ton of code to remove base class columns created in the designer and add back the correct class of column at runtime.
Andy: What about headers?
Marcia: Similar, but not identical. Your custom header class has to be defined in code, but you can specify that a column class use a custom header by setting two properties.
Andy: Don't tell me, I can guess: MemberClass and MemberClassLibrary, right?
Marcia: Sorry, no! For some reason the properties are named HeaderClass and HeaderClassLibrary—I have no idea why they should be different, but they are. Of course, the ability to implement custom header classes now gives additional meaning to the fact that the header base class has been given a Picture property.
Andy: Oh, that's new too. I see that the Help file explains in its usual clear style how the setting of the alignment affects how the picture is displayed:
For Header objects, if the header does not contain a caption, the image appears in the center of the header. If the header contains a caption, the Header Alignment property determines how the image aligns with caption text. If Header Alignment is set to left or center, the image appears to the right of the caption. If Alignment is set to right, the image appears to the left of the caption.
Marcia: Well, it's close. In fact, it seems that if you have no caption, your options are as you'd expect. The picture can be left-justified, centered, or right-justified in the header, but the only vertical positioning option is "middle." However, if you have both a caption and a picture, the text can be positioned "top," "middle," or "bottom," and if the justification is either "Left" or "Center," the picture is placed to the right of the text; otherwise, it goes to the left.
Andy: Well, there are certainly lots of good things there. I suppose the next task is to show how we can use them to our advantage.
Marcia: Well, that will have to wait. We're out of space, but next month we'll see what we can do to create some custom classes that make use of these new features.
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 April 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.