Products > GRID

The Grid Control

Lcc grid

Lcc grid is a control that allows you to quickly display tabular data. The grid has two modes:

As a standalone application, the grid will create a standalone window, open a frame with a menu, where the user can edit the data if wished, and save it to disk. The command line parameters are explained below. The general logical outline of a table looks like this :

  Column Titles
Row Cells
Titles
 
 
 
 

The column titles and the row titles are predefined as numbers from 1 to the number of columns or rows.

It is possible to change the column titles to any string, but not the row titles. Top of page lcc-win32 main page Products main page

The default user interface

Keyboard: If there is a selected cell, the arrow keys move the selected cell up,down, left or right. Page up and page down have their usual meanings. Mouse:

Left click selects a cell. If it is in a column title, it will select a column. If it is in a row title it will select a row.

Double click opens a cell for editing. When editing, Esc means abort the editing, return means validate entry.

Left click in a column title boundary allows the user to resize a column.

Right click in a column will open a context menu for sorting a column.

Top of page lcc-win32 main page Products main page

Programming interface

The table structure itself is not public, and it is hidden behind a void pointer. This shields you from any changes in the implementation of the grid, preserving your source code. This interface can change, but the changes will not be done in an incompatible way. The functions here described will not change. If new interfaces, or new functions are needed, they will have new names. The old ones will continue to be as here described.

Creating a grid

void *CreateGrid(HWND hwnd,int rows,int cols);
This function will create an initially empty table with the given number of rows and columns. All necessary windows (the table window, the edit field window, and the tool-tips window are created also. The resulting pointer identifies the table, and should be used in subsequent calls. The input parameters are the parent window, and the desired number of initial rows and columns.
void *LoadCSV(char *fname,HWND hwnd);
This function will load a table from a Comma Separated Values (CSV) type of file. The character that is used to delimit the fields is searched in the file, it can be < , >, < ; > or < \t >. The same character is used to write back the file, unless you set another one:
char GetCsvSeparator(void *tab);
char SetCsvSeparator(void *tab, char Separator);

The second function returns the character set. It is 0 if the character you give is not one of < , >, < ; > or < \t >.
void *GridOpenDatabase(HWND hDlg, char *DSNname);
int GridSelectTable(HWND hwnd, void *Database);
int GridSelectTableOrView(HWND hwnd, void *Database);
void *LoadODBC(void *Database,HWND hwnd);

The first function GridOpenDatabase without any DSN name opens a database using the standard Windows interface, allowing us to open all the databases that are recognised by Windows. You can also give a complete connexion string, like DSN=DatabaseName; DBQ=c:\\Directory\\Data.mdb; DriverId=25; FIL=MS Access; MaxBufferSize=2048; PageTimeout=5; UID=admin; PWD=password;
The second function GridSelectTable opens a dialog box that allows us to choose the table of the database we want to open. The function GridSelectTableOrView shows all tables and views. The last function LoadODBC loads the choosen table of the database.
Alternately, you can give a DSN name or a connexion string to GridOpenDatabase and use two other functions:
int GetTablesOfDatabase(void *Database, int *NbOfTables, char ***TableNames);
int SetSelectedTable(void *Database, int TableNr);

The function GetTablesOfDatabase gives you the list of the tables of the opened database, and the function SetSelectedTable sets the table you want to open. You have to call LoadODBC to create the grid and load the data.
If you use the GridOpenDatabase function, and abort before having created the grid, you should use this function to close the database:
int GridCloseDatabase(void *Database);
Here is an example of code, where we choose a database by selecting a DSN file 

   void *newtab, *Database;
   newtab=0;
   if (NULL != (Database = GridOpenDatabase(hwnd, DSNname))) {
      if (GridSelectTable(hwnd, Database)) {
         newtab = LoadODBC(Database,hwndChildMdi);
      }
   }
   if (newtab == NULL) {
      // Close the database if we dont choose a table
      // or if it is not readable
      GridCloseDatabase(Database);
   }

When the grid is destroyed, generally when you close the window that contains it, all elements are correctly closed. You can explicitly destroy the table with :

int DestroyTable(void *tab);

Note that all functions use indexes that are zero based : the first column is always zero and runs until the number of columns minus one, the first row is always zero and runs until the number of rows minus one.

Top of page lcc-win32 main page Products main page

Setting the data of the grid.

int SetCell(void *tab,int row, int col,char *data);

This will set the data to display in a given cell. Returns 1 if the operation is successful, zero otherwise (index error). The data pointer can be NULL, in this case the information in the cell (if any) is just discarded.

int GetFromClipboard(void *tab);

This function puts the content of the clipboard in the selected cell.

int SetColumnTitle(void *tab,int col,char *data);

This will set the column title for the given column. Returns 1 if successful, zero otherwise. A previous title is discarded, and freed.
Top of page lcc-win32 main page Products main page

Just a test

Getting the data from the grid.

You can get a whole column/row of data with :

int GetColumnData(void *tab,int col,char ***pResult);
int GetRowData(void *tab,int row,char ***pResult);


You pass to these functions a pointer on an empty array of pointers to character data that the grid fills. The result is the number of rows/columns that were written to the passed array. You don't have to allocate the memory for that, but you should free it with :

int FreeBlockData(void *tab, char ***pResult);

Your pointer becomes NULL, avoiding future illegal use.

You can access the data in a single cell with :

char *GetCellData(void *tab,int row,int col);

Be carefull with the usage of the pointer you receive : it may become obsolete, and more frequently it will contain a different data than that you expect. You should copy the data for a future use.

If you are sure that a cell contains numeric data you can use :

double GetCellNumericData(void *tab,int row,int col);

This is the same as

atof(GetCellData(tab,row,col));

If the substitution is effective on the cell, the data given by these functions correspond to the original values, before any substitution.

To get the substituted data, use the function :

int GetSubstitution(void *tab, int col, char *input, char *output, int len);

The string in data may also be something else that the content of the cell. If the value is not found in the substitution table, the result is 0. Otherwise it is set to 1, and the output buffer is written on a maximum of len bytes.
Top of page lcc-win32 main page Products main page

Saving the data in the grid.

int SaveRtf(void *tab ,char *filename);

This will output an rtf representation of the table suitable for a word processor. Returns 1 for success zero otherwise (bad file name).

int SaveCsv(void *tab,char *fname);

This will output a CSV (Comma Separated Values) to the given file name. Returns 1 for success zero otherwise.

int SaveToClipboard(void *tab,int fSaveAll);

This will copy the selected row or column to the clipboard. If the flag fSaveAll is true, or nothing is selected, all data will be copied. Returns 1 for success zero otherwise.

int SaveXml(void *tab,int fSaveAll);

This will output an XML file with the following layout :


<TABLE>
Number-of-rows="68"
Number-of-columns="10"
<COLUMN-TITLES>
Column0Title="name"
Column1Title="name"
...
</COLUMN-TITLES>
<ROW-TITLES>
Similar layout to column titles: RowX="NAME"
</ROW-TITLES>
<ROW0>
Cell0 "AdjustColumnWidth"
Cell1 "gridresize.c"
Cell2 "37"
...
</ROW0>
Follows the other rows
</TABLE>


Note that a grid opened on a database does not need to be explicitely saved to set the data in the database. Each time the value of a cell is set, the database is updated, and then the data is reloaded because another user may have inserted, deleted or modified several rows.
However, like all grids, it is still possible to save the data in a CSV file, for instance.
Top of page lcc-win32 main page Products main page

Special data output.

int GridSubstitutions(HWND hwnd, void *tab);

This function uses a dialog box to define the data substitutions : a field containing the values 0 and 1 may be showed, for instance, as False and True.
For each column of the grid, we can choose couples of character strings : the value in the grid and the replacement string.
Alternately, you can use another function :

int SetDirectSubstitution(void *tab, int col, int NbOfSubstitutions, char **OriginalData, char **SubstitutedData);

The arguments OriginalData and SubstitutedData must have the length given by NbOfSubstitutions. To clear the substitutions, set NbOfSubstitutions to 0 and the 2 pointers to the values to NULL.

If the gris is opened on a database, the function GridSubstitutions gives another way to change the output. All the tables of the database are listed, so you can choose on of them. Then the columns of this table are displayed, and you can select two of them : the one where the original value is found (generally the key corresponding to the data displayed in the grid), and the one that you want to display instead. Click on the Validate button to accept each substitution definition.
For instance, when you display a billing table, the product ID is a column of the billing table, but you want to display the product name. So you substitute the product number with the table Products, choosing the ProductID as the column containing the value, and ProductName as the column to display.

The other way to set this substitution is to use the function :

int SetSQLSubstitution(void *tab, int col, char *SQLselect1, char *SQLselectall);

You have to define two SQL expressions. The first one is executed when the system has to found the value of a specific cell. This expression must be like :
SELECT ProductName FROM Products WHERE ProductID=#value#
Be sure to replace the value we have to found by #value#, and if this value is a character string, put it between quotes, like :
SELECT ProductName FROM Products WHERE ProductID='#value#'

The second SQL expression is executed when the system has to load all the substitution data. For instance, when we double-click in a cell, the user does not have to type in the characters of the substituted data, but the control is a combo box that displays all the values. The SQL expression has to be like :
SELECT DISTINCT ProductID,ProductName FROM Products
The first column is the one that contains the value found in the grid, and the second one the value to display.

To clear the substitution, just call the function with the two SQL expressions set to NULL.

Note that this function does not do anything, and returns 0, if you call it when the grid is not opened on a database.

Take in mind that the original data is still stored in the grid, so the substitution is calculated each time is is needed. This may be a problem when you have to go through a large number of cells, for instance if you are searching for a particular value.
The solution is to fix the substitution, so the second SQL expression is executed, and the result is read to simulate a user-defined substitution. When the loop through the cells ends, you can unfix the substitution, so if you modify the substitution table, the new values are read and can be displayed in the grid.

int FixDatabaseSubstitution(void *tab, int col);
int UnfixDatabaseSubstitution(void *tab, int col);


These functions use the index of the column you want to fix or unfix. You can give the value -1 that means all the columns.
Top of page lcc-win32 main page Products main page

The virtual rows.

int SetGridVirtualScrolling(HWND hwnd, void *, int NbRows,fnFetchCellValue fnFetch, fnSetCellValueBack fnBack,fnGetWidthOfCol fnWidth);

The function sets the grid on a virtual data that your program has to maintain. The virtual rows are used internally when the grid is opened on a database This function allows you to use the grid to see a file that you can read and write, even if this file is very large.
First create a grid with one row, and the number of columns that is convenient :
Table = CreateGrid(hwnd, 1, NbOfCols);
Then tells the grid that you maintain the data :
SetGridVirtualScrolling(hwnd, Table, NbOfRows, MyFetch, MyBack, MyWidth);

NbOfRows is the actual number of rows of your data, you will be able to modify it when necessary. The number of columns corresponds to the total number of columns of the grid, even if they are hidden. All the column numbers are relative to the data, not to the display, so you work with fix numbers, unless you insert or delete a column.

MyFetch is the function called by the grid to get the content of a cell :
char *MyFetch(void *tab, int row, int col)
{...}

You have to return the pointer on the string to be displayed in the specified cell, or NULL if the cell is empty.

MyBack is the function called by the grid to put back the content of a cell into your file :
char *MyBack(void *tab, int row, int col, char *data)
{...}

You use the pointer on the content of the cell to put the data back into your file. If you do not want to modify your file, you can forget this function, and give NULL as the corresponding argument to the SetGridVirtualScrolling function. If you do so, you will not be able to edit any cell, nor add a line within the grid. However, you can still change your values as convenient, even add or delete rows.

MyWidth is the function called by the grid to get the width of the columns, in characters :
int MyWidth(void *tab, int col) {...}
Be sure to give the maximum width of the column. This value is only read when you call SetGridVirtualScrolling, so the width of a column that can grow, like a line number that can change from one digit to two, has to be estimated, for instance the number will certainly be less than 99999999, so the width of 8 will be convenient.

int SetVirtualNbOfRows(void *tab, int NbRows);

This function sets the grid to a new number of rows. This number may be 0. Remember that you maintain the data, so setting the number of rows to 0 cannot delete your data. You can only see, and modify with the grid, the rows that the grid displays, but the data you want to hide is still in your hands.

void *SetUserData(void *tab, int index, void *data);
void *GetUserData(void *tab, int index);


These functions allows you to store and recall up to 10 values in the grid. They can be pointers on your data, like a buffer containing a portion of the larger file that you have to read and decrypt for the grid.
The index value is from 0 to 9. The function SetUserData returns the old value.

If the access to your file is relatively slow, it may be useful to read a few lines from it, and put them into a buffer. It is a good idea to have enough place to put at least 2 pages of data in this buffer : when the grig has to be painted, the cells from the first row to the last one of the screen have to get there data. So, when your function MyFetch is called, the row of the cell is, in this case, one displayed on the screen. Be sure that this function will be called for other rows. If your buffer if larger than 2 times the maximum number of displayed rows, you can read the buffer with the wanted row at the middle of it, so the first cell of the sreen and the last one are in the buffer.
Top of page lcc-win32 main page Products main page

Managing the selection.

The user selects a cell by clicking once in its surface. The selection can be moved with the arrow keys.

You can set the selection to a specific cell with :

int SelectCell(void *tab,int row,int col):

This function returns 0 if it fails (bad index) or 1 if the cell was selected or the selection was set to that cell. The grid will ensure that the selected cell is visible.

You can use

int SelectRow(void *tab,int row);
int SelectColumn(void *tab,int col);


to select a row or a column. Both return 1 if the row/column was already selected or the selection was set, zero otherwise (bad index).

Once the selection is set, you can move it with :

int MoveSelectedCellUp(void *tab);
int MoveSelectedCellDown(void *tab);
int MoveSelectedCellLeft(void *tab);
int MoveSelectedCellRight(void *tab);


This function return 1 if the selected cell was moved, zero if there was no selected cell, and nothing was moved. Moving up and down will wrap around the table.

Normally, the arrows can move the selected cell with their usual meanings. You may cancel any selection with :

int CancelSelection(void *tab);

It returns zero if nothing was selected, 1 otherwise.

You can get the current selected cell with

int GetSelection(void *tab,int *prow,int *pcol);

The results written into the pointers passed in are :

Row Column Meaning
-1 0 ... Number of table columns-1 The column is selected
0 ... Number of table rows-1 -1 The row is selected
0 ... Number of table rows-1 0 ... Number of table columns-1 The cell at row, column is selected
-1 -1 Nothing is selected

If either the pointer to row or to the column is NULL, nothing is written to them of course. This means that you are not interested in that information. The result is always one.

You can get notified when the selection changes by establishing a callback function that is called by the grid when this event occurs.

typedef int (*GridSelChange)(void *tab,int row,int col);

GridSelChange SetGridSelChangeCallback(void * tab, GridSelChange fn);


When the user defined callback returns 1, the selection will not be changed. If the result is zero, the selection change continues. Note too that when the user selects an already selected cell, this callback is not called.

Here is a simple example that updates a status bar when the selection changes.

int SetStatusLine(void *tab,int row,int col)
{
    char buf[128];
                
    if (row == -1)
       wsprintf(buf,"col %d",col+1);
    else if (col == -1)
        wsprintf(buf,"line %d",row+1);
    else
        wsprintf(buf,"line %d col %d",row+1,col+1);
    UpdateStatusBar(buf,0,0);
    return 0; // Accept the change!
}

After the table is created, the code calls :

GridSetSelChange(tab,SetStatusLine);
Top of page lcc-win32 main page Products main page

Getting state information from the grid.

You get the window handle of the grid window with :

HWND GetGridWindow(void *tab);

This window handle allows you to perform all operations you may want with the grid, like moving it, resizing it, closing it, etc.

int GetDimensions(void *tab,int *prows,int *pcols);

Returns the total number of rows and columns in *prows and *pcols and returns 1. If you want only one of these numbers, you can give a NULL pointer to the other one.

int GridGetFirstVisibleRow(void *tab);

Returns the index of the first visible row.

int GridGetFirstVisibleColumn(void *tab);

Returns the index of the first visible column.

int GridGetVisibleColumns(void *tab);
int GridGetVisibleRows(void *tab);


These functions return the number of rows or columns that are displayed currently in the grid window.

You can scroll a cell into view with

int EnsureCellVisible(void *tab,int row,int col);

Returns 1 if the cell could be set to visible, zero if the cell was already visible, -1 if there was an index error.

int IsCellVisible(void *tab,int row,int col);

Returns 1 if the cell is visible, zero otherwise.

You can query the grid for information about a specific point in it. You pass to the grid the coordinates in its window coordinates of course, not in screen coordinates.

int GridHitTest(void *tab,int x,int y,GRID_HITTEST *result);

The structure GRID_HITTEST is defined like this :

typedef struct {
    int AreaCode; 
    int col;      
    int row;
    int x; // point coordinates
} GRID_HITTEST;

The AreaCode member of the structure is one of the following values defined in windows.h :

HTNOWHERE : The point is not in the grid.
HTTOPLEFT : The point is in cell 0,0.
HTLEFT : The point is in a row title. The y member contains the row index.
HTTOP : The point is in a column title. The x member contains the column index.
HTCLIENT : The point is in the cell area surface. The x and y contain the column and row respectively for the cell under the point.

This function returns always the area code. The pointer to the structure can be NULL, if you are just interested in finding out in which region the point is, and do not care about the specific coordinates.
Top of page lcc-win32 main page Products main page

Hiding or moving columns.

You can hide a column with :

int HideColumn(void *tab, int col);

You can move a column to a new position with :

int ShowColumnAt(void *tab, int col, int pos);

As nearly all the functions, the value of col refers to what we can see on the screen, and the value of pos refers to the position where we want to see the column. This means that if we hide a column, we still access to the columns from 0 to the number of columns we can see on the screen.

int ShowColumns(void *tab, int NbOfColumns, int *SortedColumns);

This function needs the original column numbers. You give the number of columns to display, and the indexes of theses columns. You cannot give the same number several times : each column can appear only once. Use this function to show a hidden column, or, if you want to restore all the columns :

int ResetColumns(void *tab);

To get the columns that are displayed, you can use GetDimensions to know the number of displayed columns, and :

int GetDisplayedColumns(void *tab, int size, int *columns);

The size argument is to be sure you give a sufficient area to return the values, and these are the index in the original list of columns. The value returned is 0 is there is an error, or the number of displayed columns, which is always at least 1.

int ColDisplay2Data(void *tab, int col);
int ColData2Display(void *tab, int col);


These functions give the translated index of column between the display and the data. The secund one returns -1 if the column is not displayed.
Top of page lcc-win32 main page Products main page

Adding or deleting columns or rows.

You can add columns or rows with :

int InsertRowBefore(void *tab,int idx);
int InsertColumnBefore(void *tab,int idx);


These functions receive an index to the row or column just before the insertion point. You cannot insert rows in a database grid, but it is possible to add a row by introducing data in the last row of the grid. In a virtual rows grid, the lines are depending on the calling procedure. It can add one blank line at the end of the grid, or insert a blank line anywhere, but this is its responsability.

int DeleteColumn(void *tab,int idx);
int DeleteRow(void *tab,int idx);


These functions delete a row or column at the specified position. The second one is not available with a virtual rows grid.

These functions do not update the window. You should update the window of the table to force a refresh. This is done to optimize a series of row deletions/insertions without producing screen flickering. You can use this function :

int RefreshGrid(void *tab);

It just sends a message to the grid asking for a repaint.

NOTE : As you can imagine, all indexes after the inserted row or column will increase, or decrease.
Top of page lcc-win32 main page Products main page

The undo functionality.

The grid supports one level of undo. This level may be constituted of any number of elementary functions.

For instance, if you create a function that replace the content of all the cells that contain a specific value, it is possible to undo all the cell changes in one call to the undo function.

The first function to call starts the undo memorization :

int ResetUndoAction(void *tab);

The list of undo actions is now cleared, and all the actions before the next call to this function will be included in the list of elementary actions that will be undone if we call :

int UndoLastCommand(void *tab);

This functions execute the undo list of elementary actions, and store the new actions in the list, so that undoing again restore the grid like it was before undo, and so on.
Top of page lcc-win32 main page Products main page

Controlling user input.

The first way to control may sometimes be sufficient.

void StartEditCell(void *tab);
void EndEditCell(void *tab);
void CancelEdit(void *tab);
int GetCellEdit(void *tab, int *prow, int *pcol);


StartEditCell starts an edit session on the selected cell. EndEditCell ends the edit session, reading the data, CancelEditCell ends but does not read the data. GetCellEdit gives the coordinates of the cell being edited.

This first way to control the input allows us only to edit the cells we want, so we have to go further if we want to get the user's input key by key.

When the user double-clicks in a cell, the grid will show a control window to allow the user to modify the cell's contents. If you want another behavior as the default, you can control this process by establishing a callback function and intervening at three points :
Establishing the callback.

The callback function receives a pointer to the following structure :

typedef struct {
    void *table;
    HWND hControl;// Contains Edit or Combo handle when opened
    HWND hControlEdit;
    HWND hControlCombo;
    int row;      // Will be always equal to the selected row
    int col;      // Will be always equal to the selected column
    int character;// Character typed in
    int flags;     // Type of event
} GRID_EDIT_PARAMS;

Note that we find 3 HWND members. hControlEdit contains the handle of the edit control, and hControlCombo that of the combobox control. This one is used when a substitution is defined on a column, it may be a user-defined one or a database defined one. The list of the combobox contains all the possible values for this column.

If a cell is being edited, the member hControl contains the handle of the corresponding control, otherwise it is NULL.

Since the callback can change the edit callback, it is possible to build complex editing schemas by simply having a standard procedure for handling the GRIDEDIT_START event, then changing the character filter according to the cell that is going to be edited, relying on the event GRIDEDIT_END to re-establish to first callback function.

You set the callback using :

typedef int (*GridDrawCallback)(GRID_DRAW_PARAMS *params);

GridDrawCallback SetGridEditCallback(void * tab, GridEditCallback fn);


Of course, setting it to NULL disables any callbacks. The result of this function is the previous filter. It can be NULL if there wasn't any.

A simple filter.

Here is an example of a very simple filter that allows only numeric input :

int numericFilter(GRID_EDIT_PARAMS *params)
{
    if (0 == (params->flags & GRIDEDIT_CHAR))
        // If the event is not a
        //   character input event, just go on.
        return 0;
    // Accept digits
    if (params->character >= '0' &&
        params->character <= '9')
        return 0;
    // accept the point
    if (params->character == '.')
        return 0;
    // discard everything else
    return 1;
}

Top of page lcc-win32 main page Products main page

Customizing the grid.

You can replace many of the functionalities of the grid by defining callback functions that replace the default grid functionality at many steps.

All these functions should return TRUE if they have replaced the functionality, FALSE to indicate the grid that the default functionality is desired.

Of course, mixed strategies are possible. For instance, the grid cell draw callback receives an HDC (handle to the device context), and can change the text color, the background color, or some other parameter in this HDC, and then return FALSE. The grid will use the modified HDC to draw/paint the cell.

Modifying the drawing of a cell.

You can specify a callback for cell drawing. If your callback returns TRUE, the grid will not paint anything in the cell, supposing that the callback has done it already.

The single parameter of your callback is a structure like this :

typedef struct {
     void *Table;// The table you are drawing in
     int row;    // the row
     int col;    // The column
     HDC hDC;    // The device context
     RECT rc;    // The coordinates of the cell's rectangle
     int selected;// Whether the cell is selected or not
} GRID_DRAW_PARAMS;

The callback function has the following signature :

typedef int (*GridDrawCallback)(GRID_DRAW_PARAMS *params);

It should return an integer, and it receives a pointer to a parameters structure.

You set the callback with:

GridDrawCallback SetCellDrawCallback(void *tab, GridDrawCallback fn);

Note that you can pass NULL to this function. This will disable any existing callback. The function returns the value of the old setting. NULL means there wasn't any callback defined.

Since you receive the value of the old function, you can establish a whole chain of functions if you wish, each calling the callback that was in place if it wants to.

Note that the device context that you receive is not clipped. You can draw anywhere in the surface of the table window... Please try to restrain yourself to the given rectangle however.

Modifying the aspect of the grid lines.

You can set your own function for modifying the grid lines with :

GridDrawCallback SetGridLinesCallback(void *tab, GridDrawCallback fn);

You will receive the same structure as the callback to draw the cells. The data in the structure will be as follows : Modifying the grid fonts.

The grid has two types of fonts : a mono-spaced font used for the text in the cells, and a variable pitch font used for column and row titles.

You can set your own fonts with :

int SetGridFixedFont(void *tab,HFONT newfont);
int SetGridVarPitchFont(void *tab,HFONT newfont);


Note that the grid will destroy this object when the grid is destroyed, or when the font is changed. The result of these functions is always one.

HFONT GetGridFixedFont(void *tab);
HFONT GetGridVarPitchFont(void *tab);


These functions return the current fixed/variable pitch font handles.

Modifying the keyboard interface.

You can establish a callback for all keyboard events. The prototype is as follows:

typedef int (*GridKeyboardCallback) (void *tab, unsigned int message, WPARAM wParam, LPARAM lParam);

The callback function receives the table, the windows message, and its corresponding parameters. This callback is called when the grid window receives the message WM_KEYDOWN, WM_KEYUP or WM_CHAR messages. If the callback function returns a non zero result, no further processing will be done.

You set the callback by calling :

GridKeyboardCallback SetGridKeyboardCallback(void *tab, GridKeyboardCallback fn);

The result is the previous callback function, or NULL if there wasn't any. If the "fn" argument is NULL, no callbacks will be called and the default grid keyboard behavior is re-established.

Getting all window messages.

You can receive all messages that windows sends to the grid control by sub classing it, i.e. putting your own procedure to be called by windows, instead of the windows procedure. Here is an example :

LRESULT MyGridProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    if (msg == WM_CLOSE) {
        int r = MessageBox(hwnd,
           "Really quit?" "Grid", MB_OK|MB_CANCEL);
        if (r == MB_OK)
            return 0;
    }
    return CallWindowProc(OriginalFn,msg,wParam,lParam);
}

This procedure will receive all messages, and if it is the WM_CLOSE message, it will put up a confirmation message box. If not it will call the original function of the window.

You setup this function with the following code :

FARPROC OriginalFn;
HWND GridWindow = GetGridWindow(table1);

OriginalFn = (FARPROC)GetWindowLong(GridWindow,GWL_WNDPROC);
SetWindowLong(GridWindow,GWL_WNDPROC,MyGridProc);

You first obtain a pointer to the old window procedure of the grid window, then you set your own.

And that's all. Normal windows programming.
Top of page lcc-win32 main page Products main page

Modifying the mouse interface.

As with the keyboard, you can establish a callback for getting notified when a mouse event occurs. The prototype of the function you should write is the same as with the keyboard, and you receive the same parameters : the table, the windows message and its parameters, lparam and wparam.

You set the callback with :

GridMouseCallback SetGridMouseCallback(void *tab, GridMouseCallback fn);

If your mouse callback returns a non-zero result, no further processing is done and the message is ignored.
Top of page lcc-win32 main page Products main page

Managing the tool-tips.

The grid displays a tool-tip window when the mouse hovers over a cell whose text is longer than the width of the cell.

You can get the window handle of this window with :

HWND GetTooltipWindow(void *tab);

You can set a text into the tool-tip window with :

int ShowTooltipText(void *tab,POINT *pt,char *text);

The POINT structure holds the coordinates of the top left corner of the tool-tip that will resize itself to hold the text. The origin of those coordinates is the top left corner of the grid window. This function returns zero if tool-tips are disabled, 1 otherwise.

The tool-tip window can be distracting. You can hide it with :

int HideTooltips(void *tab);

You can turn tool-tips off completely with :

int DisableTooltips(void *tab);

or enable them with :

int EnableTooltips(void *tab);

Both functions return the old state of the flag.

If you use the tool-tip functionality, it is often usefull to call these last functions to disable it when the window containing the grid does not have the focus, and to enable it again when it gains the focus.
Top of page lcc-win32 main page Products main page

Enumerating cells.

You can sweep through all cells in the table by writing a function that will be called by the grid at each cell. You receive the row index (zero based), the column index (zero based) and a pointer to the cell's data. You can pass an argument to this function.

The function to be called has the following prototype :

int Function(int row,int col, char *data, LPARAM lParam);

You start the enumeration by calling

typedef int (*GridEnumCallback)(int row, int col, char *data, LPARAM lParam);

int EnumGrid(void *tab, GridEnumCallback fn, int Reverse, LPARAM lParam);


Set Reverse to 1 to look from bottom to the top and from right to left.

The enumeration begins with the first cell of the grid, or the last one if Reverse is set, if no cell is selected.

If a cell is selected, the enumeration starts on the next cell.

If the callback function returns zero, the enumeration stops, and the EnumTable function returns the number of calls it has made. Any other value different than zero continues the process until all cells have been visited.

If the callback function adds or deletes cells during the callback the results are undefined (at best !).

To enumerate a table within a certain range of cells you can use :

int EnumGridWithBounds(void *tab, RECT *rc, GridEnumCallback fn, LPARAM lParam);

The rc parameter is a rectangle describing the range of cells. rc.left and rc.top are the coordinates (row,column) of the top left cell, the rc.right and rc.bottom members are the coordinates of the lower right cell in the rectangle within the grid.

If the coordinates of the lower left corner are -1 and -1, this means that the enumeration goes on until the end of the table.
Top of page lcc-win32 main page Products main page

Using EnumGrid to implement a search function.

To implement a search function, we can use EnumGrid to compare a given character string to the data stored in the cells.

First, we write a callback function that will be called by EnumGrid.

Since we will need that this function returns the coordinates of the found cell (if any), we define a simple structure where the callback will store its results.

typedef struct {
   char *str; // String to search
   int flags; // case sensitive, regular expression, etc
   int row; // Here the callback stores its result
   int col;
} FINDPARAMETERS;

This structure holds the string to be searched, some flags (whether case sensitive, etc), and a couple of integers to hold the result.

Then we will pass a pointer to this structure to EnumGrid, which in turn, will pass it to the enumeration function using the LPARAM argument to it.

Our function would look like this :

int findstr(int row,int col,char *data,LPARAM lParam)
{
   FINDPARAMETERS *p = (FINDPARAMETERS *)lParam;
         
   // Note that data can be NULL if the cell has never been assigned
   if (data && strstr(p->str,data)) {
      p->row = row;
      p->col = col;
      // We stop enumerating at the first cell that matches
      return 0;
   }
   // Continue the enumeration.
   return 1;
}

Note that this simple function ignores the flags.

We can now implement our search function like this :

int SearchGrid(void *tab,char *str,int flags)
{
   FINDPARAMETERS fp;
         
   fp.str = str;
   fp.flags = flags;
   fp.row = fp.col = -1;
   EnumGrid(tab,findstr,(LPARAM)&fp);
   if (fp.row != -1 && fp.col != -1) {
      SelectCell(tab,fp.row,fp.col);
      return 1;
   }
   return 0; // Nothing was found
}

Well, that is it.

To begin the search on the first cell, we have to cancel the cell selection before the first call to EnumGrid. We select the cell where we have found the desired string, and we are ready to search from the next cell.
Top of page lcc-win32 main page Products main page

Properties.

int SetReadOnly(void *tab);

This will disable any editing of the given table.

int SetReadWrite(void *tab);

This will enable editing for this table.

char *SetGridTitle(void *tab,char *title);

Sets the table title to the given argument. Returns the old title or NULL if there wasn't any.

char *GetGridTitle(void *tab);

Returns the table title or NULL if the title wasn't set.

int IsGridVirtual(void *tab);

Returns 1 if the grid has user-driven virtual rows.

int IsGridOnDatabase(void *tab);

Return 1 if the grid has been opened on a database.

int GetDatabaseTableName(void *tab, char *output, int dim);

If the grid is on a database, this function returns 1 and output receives the name of the opened table. The dim value is the size of the buffer you give.
Top of page lcc-win32 main page Products main page

Sorting Columns.

int GridColumnSort(void *tab,int col,SortFn fn);

This function will call the sorting function "fn" for each cell in the given column. The "fn" function should return an integer that represents the order of the data in the cell, which can be NULL if the cell is empty.

Here is an example of a function that sorts the data in the cells handling the contents as floating point numbers :

#include <math.h>
static int invertSortFlipFlop;
static int comparenumbers(const void *n1,const void *n2)
{
     double delta,d1,d2;
     // Note this casts !
     char *p1 = *(char **)n1;
     char *p2 = *(char **)n2;
                 
     //Handle the case where the cell is empty and the
     // pointer to its data is NULL.
     if (p1 == NULL)
          d1 = 0.0;
     else
          d1 = atof(p1);
     if (p2 == NULL)
          d2 = 0.0;
     else
          d2 = atof(p2);
     delta = d1 - d2;
     if (invertSortFlipFlop) {
          delta = -delta;
     }
     if (delta < 0.0)
          return -1;
     else if (delta > 0.0)
          return 1;
     return 0;
}

In this example we have a flip-flop that oscillates between an ascending and descending sort. To sort a column, we would call :

GridColumnSort(tab,2,comparenumbers);

NOTE : the grid on virtual rows does not accept the sort function. It's the responsability of the calling procedure. A database grid accept the sort of the columns, but we use an SQL expression to read the database sorted by one column. You call the same function :

GridColumnSort(tab,col,SORTASCENDING);
GridColumnSort(tab,col,SORTDESCENDING);


As you can imagine, the first call sorts the table with the ascending sort of the column, and the second one with the descending sort.
Top of page lcc-win32 main page Products main page

The programming examples.

To give you a "hands on" approach for using the grid, an example code for an MDI application is distributed with the software. There you will find examples of creating tables, setting callbacks, database grids, virtual rows, etc.

To build the example, compile "gridExample.c" with grid.lib
Top of page lcc-win32 main page Products main page

Function index

CancelEdit
CancelSelection
ColData2Display
ColDisplay2Data
CreateGrid
DeleteColumn
DeleteRow
DestroyTable
DisableTooltips
EnableTooltips
EndEditCell
EnsureCellVisible
EnumGrid
EnumGridWithBounds
FixDatabaseSubstitution
FreeBlockData
GetCellData
GetCellEdit
GetCellNumericData
GetColumnData
GetCsvSeparator
GetDatabaseTableName
GetDimensions
GetDisplayedColumns
GetFromClipboard
GetGridFixedFont
GetGridTitle
GetGridVarPitchFont
GetGridWindow
GetRowData
GetSelection
GetSubstitution
GetTablesOfDatabase
GetTooltipWindow
GetUserData
GridCloseDatabase
GridColumnSort
GridGetFirstVisibleColumn
GridGetFirstVisibleRow
GridGetVisibleColumns
GridGetVisibleRows
GridHitTest
GridOpenDatabase
GridSelectTable
GridSelectTableOrView
GridSubstitutions
HideColumn
HideTooltips
InsertColumnBefore
InsertRowBefore
IsCellVisible
IsGridOnDatabase
IsGridVirtual
LoadCSV
LoadODBC
MoveSelectedCellDown
MoveSelectedCellLeft
MoveSelectedCellRight
MoveSelectedCellUp
RefreshGrid
ResetColumns
ResetUndoAction
SaveCsv
SaveRtf
SaveToClipboard
SaveXml
SelectCell
SelectColumn
SelectRow
SetCell
SetCellDrawCallback
SetColumnTitle
SetCsvSeparator
SetDirectSubstitution
SetGridEditCallback
SetGridFixedFont
SetGridKeyboardCallback
SetGridLinesCallback
SetGridMouseCallback
SetGridSelChangeCallback
SetGridTitle
SetGridVarPitchFont
SetGridVirtualScrolling
SetReadOnly
SetReadWrite
SetSelectedTable
SetSQLSubstitution
SetUserData
SetVirtualNbOfRows
ShowColumnAt
ShowColumns
ShowTooltipText
StartEditCell
UndoLastCommand
UnfixDatabaseSubstitution

Top of page lcc-win32 main page Products main page