Subclassing List Class

From MorphOS Library

Revision as of 22:33, 5 February 2012 by Krashan (talk | contribs) (List Reading)

Grzegorz Kraszewski

List class is one of the most complex MUI classes. Its purpose is to display data as a list or table. Objects of List can be found in almost any MUI application. An example shown below is a List object from the Media Logger application, which shows Reggae event log. The event log list is the main item of the program window. The most important visible elements of a List object are marked with digits.

Listclass1.png


  1. A bar of column titles. This is an optional element, used usually when a list has multiple columns.
  2. Data rows. Usually contain textual information. Simple formatting like italicizing, emboldening, color change (shown on the example) may be applied. List class does not allow for more advanced formatting, for example font cannot be changed. There is a possibility for adding images however.
  3. Selected data rows. Selection can be done with mouse, keyboard, or from inside application using object attributes. There is a possibility of optional multiselection.
  4. The active row. It is selected as well usually, however in therory an item can be active, but not selected. There is even a separate setting in MUI preferences for that case.
  5. Horizontal scroller. Used when data do not fit horizontally. Horizontal scroller can be disabled, set to automatic (appears when needed), or always visible.
  6. Vertical scroller. Used when data do not fit vertically. The srcroller may be disabled.

Basic Usage

The simplest case of List class usage is a single column list containing plain text strings. Then, the simplest way to define these texts is a static array. Such a List object is a static one. An array of strings is passed to the object with MUIA_List_SourceArray attribute. The attribute may be used only as an argument for a constructor. The array may be for example declared as a global variable:

STRPTR ListItems[] = { "first", "second", "third", "fourth", NULL };

The array must be terminated with a NULL pointer. The object is created as follows:

ListObj = MUI_NewObject(MUIC_List,
  MUIA_List_SourceArray, (ULONG)ListItems,
  MUIA_Frame, MUIV_Frame_ReadList,
  MUIA_Background, MUII_ReadListBack,
  MUIA_Font, MUIV_Font_List, 
TAG_END);

Except of MUIA_List_SourceArray attribute mentioned above, there are three attributes related to the object's appearance. They should be never omitted. MUIA_Frame defines the object frame. If not specified, the object will be frameless, which does not look good. There are two kinds of frame defined in the user preferences. MUIV_Frame_ReadList should be used for read-only lists (ones, which cannot be edited by user). Editable lists should have MUIV_Frame_InputList frame. In most of legacy Amiga GUI toolkits, read-only lists had recessed frames, while editable lists had embossed ones. Of course in MUI it is up to user, but on the application side the difference between two kinds of list frames should be maintained. The picture below shows an example appearance of a List object defined with the above code:


Listclass.1.png


Setting the MUIA_Frame attribute of a List object to one of special values listed above, has an unexpected and undocumented sideeffect. The background for the object is set automatically according to the frame type. What is strange, this automatic setting cannot be overridden by passing MUIA_Background explicitly, the attribute is ignored.

A List object can change its both dimensions freely when a containing window is resized. For static lists, where the contents is known in advance, one can request that the object width is locked to the width of the longest item. It is also possible to lock the object height to the total height of all the items. To do this MUIA_List_AdjustWidth and MUIA_List_AdjustHeight attributes may be specified for the object construction. Both of them are boolean attributes, with the default value FALSE. Let's add them to our List object:

  MUIA_List_AdjustWidth, TRUE,
  MUIA_List_AdjustHeight, TRUE,


Listclass2.png


Now the list object dimensions are fixed. As the list is the only object inside the window, the window is no more resizable (there is no bottom bar and sizing gadget). As the list now always show all its items, the vertical scroller is superfluous. In theory it can be removed, specifying MUIA_List_ScrollerPos attribute as MUIV_List_ScrollerPos_None, but MorphOS 2.7 MUI seems to ignore it.

Dynamic Adding and Removing Items

It is obvious that a static list is useful only in rare cases. Usually items are added and removed with user actions or because of other external events. The simplest method for adding a list item dynamically is MUIM_List_InsertSingle. It inserts a single item in a specified position:

DoMethod(ListObj, MUIM_List_InsertSingle, (IPTR)"fifth", MUIV_List_Insert_Bottom);

When multiple items are to be inserted, it can be done in one go with MUIM_List_Insert. Items should be grouped in an array. Number of inserted items may be either given explicitly, or the array may be terminated with NULL item (similarly as for MUIA_List_SourceArray), then −1 is passed as quantity. These two ways are shown in the example below, both calls are equivalent:

DoMethod(ListObj, MUIM_List_Insert, (IPTR)ListElements, 4, MUIV_List_Insert_Bottom);
DoMethod(ListObj, MUIM_List_Insert, (IPTR)ListElements, -1, MUIV_List_Insert_Bottom);

For the first call, ListItems table does not need to have a NULL element at the end. Then, the first form may be used to insert any continuous fragment of a source array. Let's insert only the second and the third element of the array:

DoMethod(ListObj, MUIM_List_Insert, (IPTR)&ListElements[1], 2, MUIV_List_Insert_Bottom);

Now, let's discuss the insertion position, which is the last argument of both the two described methods. The position may be specified explicitly as an index (counting from 0). Then the new element is inserted before the one specified. Except of this, there are four predefined constants:

  • MUIV_List_Insert_Top – inserts element(s) at the top of the list. It gives the same result as inserting at position 0, and indeed this constant has a value of 0.
  • MUIV_List_Insert_Bottom – inserts elements at bottom of the list.
  • MUIV_List_Insert_Active – inserts elements above the active element (the list cursor).
  • MUIV_List_Insert_Sorted – inserts elements according to the list sorting order.

There are two importants things, which should be observed when inserting arrays of items. The first one is that insertion position is not applied to the inserted array as a whole. Instead it is applied to every single item in turn. It leads to surprising results. For example when one inserts an array of items with MUIV_List_Insert_Top, the inserted array will appear in the list in reversed order. The first element of array will be of course inserted at top on the list, then the second element of array will be also inserted at top, so it will appear above the first, and so on. It also works this way when insertion position is given as a number. On the other hand, the order of an original array is preserved, when it is inserted at bottom, or when it is inserted above the list cursor (because the list cursor is moved down after adding each item).

The second important thing is list sorting. If one wants to keep the list sorted, every element must be inserted with MUIV_List_Insert_Sorted. This is because inserting an element as sorted does not sort the current contents of the list. The constant just means that insert position will be determined using current sorting order with assumption that the list is currently sorted. If it is not the case, results may be unpredictable. Then, if for some reason a list cannot be kept sorted, it may be sorted manually with MUIM_List_Sort() method.

The default sorting method for lists of plain text strings is alphabetical order, ascending, case insensitive. It is based on ASCII codes of characters, so works reliably only for English and other languages not using characters above the base ASCII range (up to code 127). For many languages, such sorting does not provide dictionary order. A solution for this will be shown later, when subclassing the List class will be discussed.

Removing items from a list is much easier. MUIM_List_Remove() method is used for this. Similarly as for insert, one can specify item index as an explicit number, or indirectly with a predefined constant. These constants are very similar to ones used for inserting, and allow for removing the first, the last or the active list item. They can be found in the List class autodoc file in the MorphOS SDK. An additional constant is MUIV_List_Remove_Selected, which removes all the selected items, assuming a list allows for multiselection. This is the only case when MUIM_List_Remove() can remove more than one item.

Every insert or remove operation causes the list object redraw, if the object is visible. Redrawing may slow down inserting huge arrays of items. The List class provides MUIA_List_Quiet attribute which temporarily disables object redrawing on inserting, removing or reordering operations. The attribute may be set to TRUE before intensive operations on the list, then set to FALSE at the end, so the list will be refreshed only once, showing the final result. There is also MUIM_List_Clear() method, which removes all the items in one go and is of course much faster than removing items in a loop.

List Reading

The problem of reading a List object consists of two parts: reading items and reading object state. Let's start from reading items. The MUIM_List_GetEntry() method gets a pointer to an item. In case of plain lists it is just a pointer to a string. It is used as follows:

STRPTR item;

DoMethod(ListObj, MUIM_List_GetEntry, index, (IPTR)&item);

The index argument may be just a number, or a predefined constant MUIV_List_GetEntry_Active to read the active item. The index counts from zero. What is important, a pointer to the item is not the method result. This pointer is placed in a variable. Address of this variable is passed as the last method argument. If the list does not contain an item of specified index, NULL is placed in the variable. Then, an example loop reading all the items may be organized as shown:

STRPTR item;
LONG i;

for (i = 0; ; i++)
{
  DoMethod(ListObj, MUIM_List_GetEntry, i, (IPTR)&item);
  if (item) Printf("Item %ld is '%s'.\n", i, item);
  else break;
}
  • MUIA_List_Active – index of the active item.
  • MUIA_List_Entries – total number of items.
  • MUIA_List_First – index of the first visible item.
  • MUIA_List_Visible – number of possibly visible items.
  • MUIA_List_DoubleClick – is set to TRUE when a left mouse button doubleclick is done over the list.

MUIA_List_Active and MUIA_List_First are also settable, so list cursor may be moved and list may be scrolled from inside the application. The other three are read-only for obvious reasons. Notifications may be set on attributes, which change directly after user actions: MUIA_List_Active, MUIA_List_Entries and MUIA_List_DoubleClick. Attributes MUIA_List_First and MUIA_List_Visible are describing object geometry rather than its contents. Values of these attributes may be only changed indirtectly by scrolling or window resizing. Then notifications on these two attributes do not work. As MUIA_List_Visible in fact describes list object height, its value may be higher than the number of items on the list, in case when the list is short. For example if the object is tall enough to display 5 items, but there are only 3 items on the list, the attribute will still be equal to 5, so it will be higher than MUIA_List_Entries.

The MUIA_List_DoubleClick attribute may seem to be useless at the first glance, as it does not provide information which item has been doubleclicked. A doubleclick however moves also the list cursor, so MUIA_List_Active is set to the index of the doubleclicked item. In case of multicolumn lists it is also possible to get an index of clicked column, it will be discussed later.

There are more List attributes handling following features:

  • multicolumn lists,
  • list title bar,
  • multiselection support.

As these are advanced topics, they will be explained later.