Tech Note 33: Using the Grid Control

July 15, 2009

© NSB Corporation. All rights reserved.

The Grid control provides an easy way to display your data in a formatted table. It is made up of horizontal rows and vertical columns. The intersection of a row and a column is called a cell. Have a look at the sample "Grid.prj" in the Samples project to see how this control works. In the description below, the words in bold below are properties and methods of the grid control. To use them, preface them by the name of your control followed by a period.

A. Setting up the Grid in the IDE

To place a Grid on a form in your project, select the Grid object and click in the Palm Screen at the top right hand corner where you want it to be placed. Set the number of Cols and Visible Rows you wish to have. Changing the number of Visible Rows will increase the Height of the grid with rows the same size. Change the Height of the Grid to change the size of each row. The columns are all equally spaced at Grid creation time: you can change the width at runtime. You cannot have the grid automatically hidden when the form is loaded.

If you choose the Has Scrollbar option, make sure the Width leaves room for the scrollbar to appear to the right of the object. Allow about 10 pixels for this.

B. Initializing the Grid at Runtime

You can complete the setup of your grid at runtime in the Form After code. It's a good idea to start by doing a Hide, so the updates you do to the table do not constantly update the screen. You can then set the column widths, types and initial values for the grid before doing a Show.

Set the column widths by using the ColWidth(colNo)=p function, where colNo is the column number and p is the number of pixels the column should be wide.  Columns can hold text, number or check boxes.

To set the type of each columns, use the ColType colNo, type, formatString call. Type can be "text","numeric" or "checkbox".

If the type of a column is "text", you can put string values into it. The formatString is used for the initial value of any new rows that you Add. It is normally "", an empty string. "text" is the default column type. Data is left justified in a text column.

If the type of a column is "numeric", the formatString defines how numbers are shown in it, using the same conventions as the FORMAT statement. For example, a formatString of "nnn" will display a 3 digit right justified number. The default value of a new row is 0.

If the type of a column is "checkbox", the formatString is used for an optional string value that is shown to the right of the checkbox. The default value is "", an empty string. For checkboxes without any text, a colWidth is 12 works well. String or numeric values can be assigned to a checkbox column cell. If the value assigned is 0 or "0", the checkbox is unchecked; otherwise it is checked. The default is that a column is unchecked.

Example
'set up a grid with 3 columns: text, numeric and checkbox
grid1.hide
grid1.colwidth(1)=121
grid1.ColType 1,"text",""
grid1.colwidth(2)=14
grid1.ColType 2,"numeric","nnn"
grid1.colWidth(3)=12
grid1.ColType 3,"checkbox",""
displayGrid 'call another function to set initial values
grid1.show

C. Populating a Grid with your own data

Once you have the grid set up, you can populate the cells with data using the Text, Value, TextMatrix and ValueMatrix functions. Text and Value set string and numeric values respectively to the current cell. The current cell is defined as the cell that is at row Row and column Col. You can change the current cell by assigning new values to the grid's Row and Col properties.

A quicker way to do this is to use the TextMatrix and ValueMatrix functions. These take the row and column values as arguments and do not require you to set the Row and Col properties separately. The Redraw method can then be used after all cells are updated.

To add a row, use the Add method. The RowData property can be used to store a unique value for each row, that does not get displayed. To get rid of all the rows in a grid, use the Clear method. The value of Rows will then be 0. You can also add or delete rows to a grid by changing the value of Rows. To get rid of one particular row, use the Remove method.

Example
MyGrid.Clear
For r=1 to 10
   MyGrid.add
   Grid.TextMatrix(r,1)="some data"
   Grid.ValueMatrix(r,2)=2
   Grid.ValueMatrix(r,3)=1
Next

D. Populating a Grid from a Database

You can also populate your grid with information from a database. To do this, you'll need to do a bit of preparation. First, you will need to DIM a database variable to identify which database will be loading from. Use the DIM AS DATABASE statement. Here's an example:

Dim blueKey as Integer
Type dbBluesLayout
    Name as String
    age as Integer
    active as Integer
End Type
Dim dbBlues1 as Database "Blues", DbBluesRec, dbBluesLayout, BlueKey

We want to read in data from a database on our Palm called "Blues". The database variable (we will refer to the database using this) is called dbBlues1. Each record in the database has the field in dbBluesLayout. When we read a record in, it will be put into the variable dbBluesRec. The key to the database Is blueKey, an integer. This statement should be done in the same routine that you do your BindToDatabase call, since dbBluesRec is automatically dimensioned as a regular variable (not a global).

Now that we have defined our database, we can copy the information directly into our grid using the BindToDatabase call

Grid1.bindToDatabase(dbBlues1, dbBluesRec.name, dbBluesRec.Age, _
 dbBluesRec.active) Where dbBluesRec.age>=70

This statement copies data from the database referred to by dbBlues1 into our grid, Grid1.  The next three arguments list the fields in the database record which go into each of the columns. The columns do not need to be in the same order as the fields in the record layout (dbBluesLayout), nor do you need to use all the fields. The optional WHERE clause allows you to select which records to copy to the grid. You may use any expression that you could put into a normal IF statement.

If there are more records in the database selected than Visible Rows and you have Has ScrollBar set, a scrollbar will appear allowing you to see all the rows. Keep in mind that if you have a large number of rows, the scroll arrows do not work very precisely. Records are only copied into the grid as they are displayed, so there is no speed penalty for displaying large databases.

As the records are read into each row, the rowData value of each row is set to the record number in the database.  This is useful if you want to recall a database record for a selected row in the grid:

Dim recNo as integer
Dim err as integer
recNo=Grid1.rowData(Grid1.Row) 'get rowData for the selected row
err=dbPosition(dbBlues, recNo)
err=dbRead(dbBlues, dbBluesLayout)

E. Interacting with a Grid

When a user taps on a grid, the code for the grid object is executed. Data cannot be typed directly into a cell. There are several useful variables that can be checked in that routine to determine what to do. The Row and Col properties will have the current row and column. You can use Row to get the RowData value to get back to the original database record (if it is a bound grid) or to access other data.

You can modify the values in a grid using Text, Value, TextMatrix and RowMatrix. However, changing these values will not update the database automatically in a grid that is bound to a database. To update the database, use the rowData property to locate the database record, read it in, modify it and write it out again using your own code.

TopRow is a useful value when dealing with grids which scroll. It gives the row number of the top row that is currently displayed. You can also force a grid to scroll by changing the value of TopRow.


Grid Object

The Grid object allows you display a table of data, supplied either from your program or automatically loaded from a database. You define the basic appearance of the grid at design time and populate it with data at runtime.

When a cell in the grid is tapped, the grid's row and col properties are set and can be used in your program. While the fields in the grid cannot be directly edited by the user, you can change their values using TextMatrix and ValueMatrix for string and numeric fields respectively. Text and Value can be used for the currently selected cell.

Please see the Handbook for a more detailed description of the use of this object.

Properties and Methods Specific to this Object

.BINDTODATABASE dbName, dbFieldNameList [Where condition]

Automatically loads a grid with data from a database. DbName is a database variable, set up by a previous DIM AS DATABASE statement. DbFIeldNameList is a list of fieldnames in the database, or the name of a Type structure in the database. Condition is an optional argument which selects which records to display. The format is the same as in an IF statement.

.COL                           Get or set the current column. Range is 1 to COLS.

.COLS                         Get the number of columns

.COLTYPE colNo, type, formatString

Set the type and format of a column. Columns default to text with null values. If you want a different type of data in a column, you must use this method to set it at runtime. ColNo is an existing column number in the Grid. Type is either "text","numeric" or "checkbox". FormatString depends on type: for "text", it will be the default value of the cell. For "numeric", it is a format string as used in the FORMAT statement. For  "checkbox", the formatString is displayed to the right of a checkbox.

.COLWIDTH(colNo) Get or set the width of column columnNo

.ROW                         Get or set the current row. Range is 1 to ROWS.

.ROWDATA               Get or set the rowdata value of the row. This is a user defined value.

.ROWS                                   Get or set the number of rows.

.TEXT                         Get or set the value of the current cell with text in it.

.TEXTMATRIX(rowNo, ColNo) Get or set the text value of the cell at rowNo, ColNo.

.VALUE                                  Get or set the value of the current cell with a number in it.

.VALUEMATRIX(rowNo, ColNo) Get or set the numeric value of the cell at rowNo, ColNo.

Properties Supported (Set at design time)

Cols, Has Scrollbar, Height, Left, Top, Visible Rows, Width

Other Methods Supported (See "Methods")

Add, Clear, Hide, Redraw, Remove, Show

Some Additional Comments

Here are some additional comments on the Grid object, from John S. Adams, MD:

For all uses of the Grid object

The manual indicates that a grid can be unselected by setting the .ROW property to zero. I find this does nothing. In order to deselect a previous selection, the .ROW property must be set to minus one (-1). Since this will result in the grid scrolling to the absolute top, the user would need to use code such as the following to maintain the current grid position:

               Dim TRow as Integer

                ...

                TRow = TheGrid.TOPROW                       'Capture the currently displayed top row

                TheGrid.ROW = -1                                    'Deselect the grid; scroll to the absolute top

                TheGrid.TOPROW = TRow                       'Reset the displayed top row to the previous value prior to scroll

The .TOP property, which is accessible at runtime, does not return the pixel position set at design time as the TOP of the grid. Rather, it returns the pixel position of the bottom of the grid as defined at design time (= design time TOP + design time HEIGHT). At runtime, resetting the value of .TOP does move the grid to the new position on the screen. However, a subsequent read of the value of the parameter (eg: Foo = TheGrid.TOP) returns the original design time bottom pixel position without regard to any run time reset.

 

The .HEIGHT property, also accessible at runtime, returns a value equal to the actual height of the grid as filled. In other words, if a grid of height 96 pixels and 8 displayed rows in the IDE is filled with four rows of data, .HEIGHT will return 48. At runtime, resetting the value of .HEIGHT results in unusual behavior. The grid height is, indeed, changed to the new value. However, the grid appearance varies depending on whether or not it was filled with "local" data or bound to an existing DB. In the case of the former, the grid appears to behave normally, but in the latter case, the grid appears to have lengthened, overlapping objects below it on the screen even though the physical top of the grid is unchanged. In any case, a read of the value of .HEIGHT returns the original design time value without regard to any run time reset. I would suggest not attempting to change either the .TOP or the .HEIGHT property values at run time.

 

The .ROWS property is quirky. Setting it after loading data into the grid does, indeed, change the displayed number of rows to the new value. However, a subsequent read of the value returns the number of rows originally loaded, even though some are not visible. No errors or freezes appear to result.

 

For Grid implementation using "local" data

The grid object appears to behave, and the various properties and methods appear to function, as stated in the manual. I have not tested every one, however. The .ROWDATA property requires a "rowN" value be specified in parentheses. This is not clearly documented.

 

For Grid implementation via DB binding (using the .BINDTODATABASE method)

The .TEXTMATRIX and .TEXT properties are read-only! Any attempt to change these properties for specified or selected cells will result in a fatal error if the grid is bound. They work fine as read/write only in grids using "local" data.

 

The .VALUEMATRIX and .VALUE properties are also read-only in a bound grid. However, attempting to change these properties for specified or selected cells has no effect in a bound grid (i.e.: the displayed value does not change, but no error occurs). Again, they work as billed in grids using "local" data.

 

The .ROWS property appears also to be read-only! Changing the property value for a bound grid may result in a complete PalmOS freeze. As above, it is quirky for grids using "local" data.

 

If a bound grid contains fewer rows of data than the number of visible rows specified at design time special care is required. Should the user tap below the last displayed row of data but still within the bounds of the grid as specified at design time, a fatal error can result. The error only appears if the code then attempts to read the selected row number or column number (RNum = TheGrid.ROW or CNum = TheGrid.COL), however, this error is apparently untrappable. The use of the .ROWS property, as described above, provides no solution for a bound grid. Of note, tapping in the equivalent unused area of a grid populated with "local" data results in no error, though the "selected" cell remains highlighted unless deselected by setting .ROW to -1. Fortunately, the potential error in a bound grid can be circumvented via the following code:

        In the Startup Code add

                  Global gVPix as Integer

                  Global gHPix as Integer

 

       In the Event Code for the form containing the bound grid place

                  Dim EventType as Integer

                  Dim PenStatus as Integer

                  EventType = GetEventType()

                  If EventType = nsbPenDown Then

                        GetPen gHPix, gVPix, PenStatus                     'Capture the vertical and horizontal pixel position tapped in global vars

                  End If

 

       Finally, in the code for the grid itself, before any .ROW or .COL statements, place (or setup as a subroutine, if multiple bound grids used)

                 Dim TopPix as Integer

                 Dim Height as Integer

                 Dim VisRws as Integer

                 Dim NumRecs as Integer

                 Dim EffGridBtm as Integer

                 Dim IDEGridBtm as Integer

                 '

                TopPix = 16                                                                             'Top of grid from IDE (the IDE numbers are for example)

                Height = 96                                                                              'Height of grid from IDE

                VisRws = 8                                                                              '# Visible Rows from IDE

                NumRecs = TheGrid.Rows                                                       '# of records in grid

                EffGridBtm = TopPix + (NumRecs * Height/VisRws) - 1          'Calculate effective grid bottom

                IDEGridBtm = TopPix + Height                                                'Calculate actual grid bottom

                '

                '"gVPix" has been set by the event code to the vertical pixel position tapped, before

                ' proceeding, this must be compared to any unused grid space to ensure that a tap there

                ' is ignored before an error can be generated. If "VisRws" or more records have been bound,

                ' then there are no "empty" grid rows and the tapped row is immaterial.

                '

                If (gVPix >= EffGridBtm) And (gVPix <= IDEGridBtm) And (NumRecs < VisRws) Then

                         TheGrid.ROW = -1                                                          'Deselect the grid. The scroll that results is immaterial as

                                                                                                                  ' the # of displayed rows is less than the designed limit

                         Exit Sub                                                                            'Exit the grid code before executing any .ROW or .COL lines

                End If

 

I believe this covers what I have discovered from serial testing of various grid configurations. I should specify, however, that the foregoing only applies to code run on the Simulator. As I have only been working with a more recent (Garnet) PalmOS device, I have not been using POSE. It is possible that the matters above are specific to the new OS and not to previous versions. I have tested the foregoing several times and believe the conclusions to be correct. Although I often attempt to do unusual things when I am programming, I suspect that someone else at some point will tickle one of the above idiosycrasies of the language. Hopefully, this will save them from having to rediscover what I have learned.

 

Respectfully,

 

John S. Adams, MD