December 1997

Learning the ropes of TStringGrid

by Gerry Myers

On my current project, I needed a visual matrix to hold some data and to allow the user to make changes. I figured a grid component would do the trick nicely, so I clicked on the Additional tab in the component palette and selected the StringGrid component. When I dropped the component on the form, it presented a nice grid-like appearance and some fairly straightforward properties in the Object Inspector. As I was patting myself on the back for migrating to C++Builder and making my programming so much easier, I opened the VCL help file to read the information about TStringGrid--but I couldn't find any. At first I thought I'd typed in the wrong name in the Help Topics dialog box--but no. I even checked the paper reference manuals that shipped with C++Builder, but I still found nothing on TStringGrid.

I called the Borland Assist line and was told that, sure enough, they'd left several of the component help files out of the original VCL help file during the rush to get a first-release product to market. Fortunately, you can download an updated VCL help file from Borland's Web site, www.borland.com; look in the C++Builder section under Developer Support.

Now, don't stop reading just because you can get the new Help file that includes TStringGrid. In this article, I'll describe how to use some of TStringGrid's unique properties.

A grid by any other name

Not knowing about the undated VCL help file, I convinced myself that help files were for wimps anyway, and started digging into the TStringGrid class. I knew about the DBGrid component (a database control), so I checked its help information first. Although it was informative, it didn't help me with TStringGrid--TDBGrid and TStringGrid come from different branches of the hierarchical tree. I finally broke down and did a text search for TStringGrid in the C++Builder Include directory, to find the header file for the class. What I found was the Grids.HPP file in the \Include\Vcl directory. You can look for yourself if you're interested in the private, protected, and published items. I was concerned with the public section, along with TStringGrid's base class.

It turns out that TStringGrid is derived from TDrawGrid. And guess what? TDrawGrid was in the VCL help file. But although it was helpful to understand the properties, methods, and events of TDrawGrid (and indirectly TStringGrid), I still had to figure out how the public properties that were unique to TStringGrid worked. For that I had to turn back to the TStringGrid header file.

Cells

The TStringGrid constructor contains four public properties, as follows:
__property System::AnsiString Cells
  [int ACol][int ARow] = {read=GetCells, 
  write=SetCells};
__property Classes::TStrings* Cols[int Index] = 
  {read=GetCols, write=SetCols};
__property System::TObject* Objects
  [int ACol][int ARow] = {read=GetObjects,
  write=SetObjects};
__property Classes::TStrings* Rows[int Index] = 
  {read=GetRows, write=SetRows};
The most straight-forward property is Cells[i][j], which you can use to read or write the string value of any cell. For example, you can set the value of every cell in the grid by performing a double loop like this:
for ( int i=0;  i<MyGrid->ColCount;  i++ )
  for ( int j=0;  j<MyGrid->RowCount;  j++ )
     MyGrid->Cells[ i ][ j ] = "n/a";
As the component name implies, this grid works with strings (AnsiString, to be specific). Therefore, you can either assign property Cells[i][j] a string, or the property can assign its string to another AnsiString object.

You may have noticed something peculiar (at least, it seemed backwards to me): The cells are indexed in a column major format, meaning that the first index is the column and the second is the row. You should be aware of this indexing, since it mixed me up a couple of times.

Rows and Cols

The Cells property is all well and good--but wouldn't it be easier, at times, to assign the value of an entire row or column in one statement? Luckily, TStringGrid provides the Rows[i] and Cols[i] properties for doing just that. These properties read and write objects of type TStrings (remember how Cells[i][j] reads and writes objects of type AnsiString). Let's touch briefly on the TStrings class. If you're familiar with the AnsiString class, you know that it can contain only a single string. The TStrings class is able to hold multiple AnsiString objects--you can think of it as an array of strings.

The Rows and Cols properties work with TStrings in such a way that the first string in the TStrings object goes to the first cell in that row or column; the second string goes to the next cell, and so on. By using the Rows and Cols properties, you can avoid the double-loop needed when using Cells[i][j].

An added benefit of using the Rows and Cols properties is that you can download the TStrings class's strings automatically from a disk file. TStrings has a LoadFromFile(filename) method (and its cousin LoadFromStream(stream)). Each line in the text file becomes a different string in the TStrings object and hence a different cell in the grid. For example, the single statement

MyGrid->Rows[0]->LoadFromFile( "xyz.txt" ) 
will fill the first row of the grid with successive lines from the text file. Another helpful property of the TStrings class is CommaText, which allows the array of strings to be built from a single comma delimited string. For example, the line

 

MyGrid->Rows[0]->CommaText = "MeasId, 
  Description, Units"
will fill the first cell of row 0 with the string MeasId, the second cell of row 0 with Description, and the third cell with Units--all using a single statement.

Objects

The last public property unique to TStringGrid is Objects[i][j]. It's very similar to the Cells property, but instead of reading and writing strings, it reads and writes items of type TObject. You may recall that all VCL components are derived either directly or indirectly from TObject--if you look at any VCL component's hierarchy, you'll see TObject at the top. TStringGrid's Objects property lets you associate any VCL object with a grid cell. For example, let's say you have a CheckBox component on your form, as shown in Figure A, and you want to be able to toggle its visibility when a certain grid cell is selected.

Figure A: Selecting the first cell in the second row toggles the appearance of a check box.
[ Figure A ]

To do this, you can write code like that in Listing A.

Listing A: Associating an object with a cell

void __fastcall TStringGridForm::FormCreate(TObject *Sender)
{
  // Set up the string and object association 
  // for the cell in column 1 row 2. Text 
  //string MUST be set before the object.
  StringGrid1->Cells[ 1 ][ 2 ] = 
    "Toggle CheckBox1";

  // CheckBox1 is an existing component.
  StringGrid1->Objects[ 1 ][ 2 ] = CheckBox1;
}

void __fastcall TStringGridForm::StringGrid1SelectCell( 
  TObject *Sender, long Col, long Row, 
  bool &CanSelect)
{
  // Get object associated with selected cell.
  TCheckBox* cBox = ( TCheckBox* )
    ( StringGrid1-> Objects[ Col ][ Row ] );

  // If selected cell is associated with a 
  // checkbox, toggle checkbox's visibility.
  if ( cBox ) cBox->Visible = !cBox->Visible;  
}
First, the TCheckBox component and the text string are associated with the grid cell when the grid is created. (Note that a string must be loaded into the cell before the object is associated with it, or you'll get a runtime error.) Then, you set up an OnSelectCell event handler to toggle the checkbox's visibility. (You can download our sample project from www.cobb.com/cpb as part of the file dec97.zip; click the Source Code hyperlink.)

When you execute the application, the check box will appear and disappear each time you select the specified cell. You could use this approach to present the user with a matrix (grid) of pseudo-buttons for toggling the visibility of various controls.

Conclusion

The TStringGrid component offers four unique properties: Cells, Rows, Cols, and Objects. You can access individual cells in the grid by indexing the Cells property, or index entire rows and columns through the Rows and Cols properties. Indexing the Objects property lets you associate individual TObject components with any cell in the grid. In this article, we've discussed the major functionality of this grid class to the point that you should feel comfortable playing with it. As we've demonstrated, the TStringGrid component is quite versatile and easy to use.