Dr. Thomas Tensi Paganinistraße 60
D-81247 München

List Viewer OPM Module for EPOC
(Version 0.8)

Introduction, Features/Misfeatures

This OPM module implements vertical list viewers for use in OPL on the EPOC machines. A list viewer is a scrollable window which displays entries of some underlying list of data records. Each record is displayed in a single row and those rows are stacked up vertically.

To identify records each record has some integer number associated with it from an interval [1..n]. Normally the list viewer displays only a subrange [i..i+k-1] with the constraint (i = 1 OR (i > 1 AND i+k-1 <= n)).

list viewer with associated scrollbar
Fig. 1: Example of a list viewer with associated vertical scrollbar

The list viewer module offers the following services:

My implementation has the following features and misfeatures:


The following steps have to be performed to make the list viewer module available:

  1. Download the files from here.

  2. Unpack the ZIP-archive on your PC.

  3. Connect your EPOC device to the PC.

  4. Install the listviewer.sis file on your EPOC machine by double clicking on it.

    • The files "ListViewer.omh" will be copied into "C:\System\OPL", "ListViewer.opm" and its source "ListViewer" will be copied into "C:\System\OPM", and finally the files "ListViewer_Test" and "ListViewer_Doc" into "C:\OPMDemo\ListViewer\".

  5. If you do not have the scrollbar.opm installed, it will be done automatically.

    • The files "Scrollbar.omh" will be copied into "C:\System\OPL", "Scrollbar.opm" and its source "Scrollbar" will be copied into "C:\System\OPM", and finally the files "Scrollbar_Test" and "Scrollbar_Doc" into "C:\OPMDemo\Scrollbar\".


To use the list viewer module in your own program you have to do the following:

  1. Put include lines at the beginning of your program for "Scrollbar.oph" and "ListViewer.oph" (and possibly for the OPM loader)

                INCLUDE "OPM.omh"
                INCLUDE "Scrollbar.omh"
                INCLUDE "ListViewer.omh"
  2. Load both modules and call their initialisation procedures similarly to other OPM modules:

                PROC init1:
                  LOADM OPM_loader$
                  UNLOADM OPM_loader$
                PROC init2:

    The string parameter for ListViewer_initMODULE: is the name of the first procedure in your program.

  3. Define procedures in your program to be called from the list viewer when

    • some record must be redrawn in the list viewer (this procedure must have one long integer parameter, one integer parameter and must not return a value),

    • a record is selected in the list viewer (this procedure must also have a long integer parameter, an integer parameter and must not return a value), and

    • the list viewer needs to know the total number of records (this procedure must have no parameters and must return a long integer value).

    So whenever the list viewer must be redrawn (either by event or by call) your application model is notified and queried.

    Note that the names of the procedure and the names of the parameters can be chosen freely.

  4. Any list viewer you want to use is created by ListViewer_create%: with the following signature:


    This routine has a single parameter and returns a list viewer object identifier (which must be stored in some integer variable for later use).

    The only parameter is colourMode% which defines the depth of the window bitmap (typically it is kGCreate4ColourMode%). A list viewer has this property fixed over its lifetime.

    The routine returns a list viewer id because it is necessary to identify a list viewer in later routines (as you may have several viewers open at a time).

    ListViewer_create%: does not display a list viewer yet! Additional steps are still necessary: the callbacks and the outer and row geometry have to be defined and possibly a scrollbar can be associated.

  5. A list viewer has to be linked to a client program which takes messages about redrawal or selection and queries about the number of records. This is done via

                ListViewer_linkToClient:(listViewer%, recordCountProc$,
                                         redrawRecordProc$, selectRecordProc$)

    This routine has (besides the mandatory list viewer as the first parameter) several others as follows:

    • recordCountProc$ is the name of the procedure to yield the last record number (the first is by convention always 1).

    • redrawRecordProc$ is called by the list viewer to redraw a row showing a specific record. This routine has two parameters: the first (a long integer) is the record number to be redrawn, the second (an integer) is the number of a row bitmap into which the drawing shall be done. The client program may draw anything into the row bitmap which is later on copied into the list viewer.

    • selectRecordProc$ is called by the list viewer module when some row for a record has been clicked. This routine has two parameters: the first (a long integer) is the record number clicked, the second (an integer) is the x-offset of the click. Normally the second parameter is not relevant but it could be when you have some column structure in your rows.

      Also note that your client is responsible for handling the selections. The list viewer has no idea whether a selection may cancel another one or whether multiple selections are o.k. and so on.

  6. The list viewer geometry is specified by calling the routine ListViewer_setGeometry: which has the following signature:

                ListViewer_setGeometry%:(listViewer%, x%, y%, width%, height%)

    The first parameter is the list viewer affected. x% and y% give the position of the upper left corner in pixels. width% is the list viewer"s horizontal extent and height% its vertical extent (both dimensions in pixels). Note that the extents include the border which goes inward, i.e. that the outer size is not affected by the border width.

    Hence to set up a list viewer at (200,0) with a width of 300 pixels and a height of 240 pixels you say:

                LOCAL listViewer%
                listViewer% = ListViewer_create%:(kGCreate4ColourMode%)
                ListViewer_linkToClient:(listViewer%, "countRecords", "redraw", "select")
                ListViewer_setGeometry:(listViewer%, 200, 0, 300, 240)

    Note that ListViewer_setGeometry: does still not display a list viewer! This happens as late as possible to prevent unnecessary list viewer updates.

  7. The list viewer so far has no idea how many vertical pixels each row will take, how far those rows are apart and how wide the horizontal lines between the rows are. This can be specified with ListViewer_setVerticalSizes: with the following signature:

                ListViewer_setVerticalSizes:(listViewer%, borderThickness%, 
                                             rowHeight%, rowSkip%, separationLineHeight%)

    The first parameter is the list viewer affected. borderThickness% tells how wide the border is, rowHeight% is the vertical size of the drawing area for a row, rowSkip% is the vertical size of the separation area between two rows and finally separationLineHeight% is the vertical size of the horizontal line between two rows. All dimensions are in pixels.

    You may draw anything you want in the rows, but normally you will display text. Then the height of a row must be at least the font height plus the font descent, otherwise some glyphs might become clipped. It does not look to good when you directly abutt those text rows (you also would not in an editor), hence you have to define some space between the rows: this is the row skip. It is typically about 30% of the row height. Centered in this area lies the separation line. Its thickness may be 0 (then you do not see it) up to rowSkip (then the complete separation area is black). A value of one or two pixels is typically o.k.

    Based on the above parameters the vertical list viewer layout is as follows:

    • First comes the top border with borderThickness as its height.

    • Then comes an empty space of height rowSkip/2.

    • Now we have (k-1) rows where k denotes the number of completely visible rows. Each row has height rowHeight and below a space of height rowSkip which may be filled with the separation line centered in this space.

    • The last row has also height rowHeight, but ideally only a space of height rowSkip/2 below.

    • Finally comes the bottom border with borderThickness as its height.

    Hence ideally a list viewer must have a height of k×(rowHeight+rowSkip)+2×borderHeight. Practically you will not always achieve this. Then some row is partially visible to prevent annoying space at the bottom border.

    As always when you set those parameters to new values, this does not take effect immediately! You have to do a redraw first (see below).

  8. A list viewer offers no way to scroll by itself. This can easily be fixed because you can define a scrollbar whereever you want and associate it with the list viewer via ListViewer_setScrollbar:. This routine has the following signature:

                ListViewer_setScrollbar:(listViewer%, scrollbar%)

    The first parameter is as always the list viewer affected. scrollbar% is the id of a previously created scrollbar. The list viewer automatically links itself to the scrollbar as client, hence any other client linkage of the scrollbar will be lost.

    Why does the list viewer not contain a scrollbar automatically? At first I had this solution but in the end it was too inflexible. Do I want the scrollbar to the left of the viewer or to the right or at the bottom? What happens when the list viewer gets resized? What happens when the scrollbar is invisible (because there is nothing to scroll)?

    Hence you have to handle the scrollbar by yourself.

  9. Whenever you fiddle around with geometry the internal data in the list viewer is changed but the visual representation is not updated! To do that you must issue a call to


    which visually updates the list viewer to reflect the internal data.

    The reason for this design is that you may have many updates in geometry and internal data and do a redraw only when done. Also draw is optimized to redraw only those parts which are invalid.

  10. Feeding pointer events into list viewers is similar to feeding them into a toolbar: a centralized procedure is called in your event loop:

                ListViewer_offer%:(windowID%, pointerType&, x%, y%)

    The first parameter tells in which window the event has happened. The second parameter tells whether the event was a pen down, pen up or pen drag. Finally x% and y% give the position of the pen event. You can take all the information from the result of a call to GETEVENT32. It is identical to the information you would give to a call of TBarOffer.

    Some further remarks:

    • This routine will tell you as its result whether the event has been consumed by the list viewer or not.

    • It is not a function for a specific list viewer. The dispatch to the specific list viewer is done within the function.

    • The list viewer is automatically redrawn for the pointer events.

  11. If you want to set the record at the top of the viewer by yourself (and not by receiving the correct events from the user) you may do that via

                ListViewer_setTopRecordNumber:(listViewer%, topRecordNumber&)

    As always the first parameter is the list viewer and the second is the new top record number. Note that this routine does no update! You have to explicitely call draw or postpone that until you are done with the settings.

  12. To check whether some record is currently on screen, you may call the routine visibleRecords which tells you about all completely visible records.

                                          topRecordNumberPtr&, bottomRecordNumberPtr&)

    Note that the second and third parameters are pointers to long integers. Hence you have to call this with addresses of variables like e.g.

                LOCAL low&, high&
                ListViewer_visibleRecords(listViewer%, ADDR(low&), ADDR(high&))

    The bottom value returned sometimes seems to be off by one. This comes from the layout rules of the viewer (see above at the setGeometry routine). The bottom row is only considered unclipped if and only if its contents and at least a white space of rowSkip/2 is displayed.

  13. Finally to get rid of a list viewer, you have to close it as follows:


    The only parameter is the list viewer identifier (just in case you have more than one list viewer open).

Sample program

A sample program is included which shows how to use the list viewer module. This program is called ListViewerDemo and simulates a clent of a list viewer. It consists of three dialogs:

After completion of that dialog you can click into the scrollbar or the list viewer window and check the effect. It is also possible to see how zooming works and to see the current visible interval by pressing the tabulator key.

Pressing Esc anytime returns you to the previous dialog or - when you are in the outer dialog- exits the program.

Conditions of use

The list viewer module is put into the public domain and is unsupported. If you think there is some bug in the program you can contact me by electronic mail. Currently I cannot promise an immediate answer or even any answer at all.

You may modify the source code but you are not allowed to publish the list viewer code as your own work when the changes are marginal. I do not like being confronted with the new OPL 5 list viewer module by Fredi Klabuster which is nearly 100% my code.

Nevertheless you may use this code freely and of course you do not have to mention me when the list viewer is part of your program.

Have fun,

Thomas Tensi, August 2001


You can download the list viewer module for OPL (with source code!) for use in your own programs. A detailed instruction for installation and use is available in the SIS-file as "ListViewer_Doc". Also a tiny demo test driver is included where you can see how to practically use a list viewer.

Appendix: Change History

2001-08-14 (Version 0.8)
First public version