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.
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.
__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.
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.
Figure A: Selecting the first cell in the second row toggles the appearance of a check
box.
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.