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)).

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.
  5. If you do not have the scrollbar.opm installed, it will be done automatically.


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

    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:

  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 don't 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:

    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 doesn't the list viewer 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:

  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 don't 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 don't 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
   HOME    UP