By dave | November 11, 2020

Runtime lists support the displaying of list based data, where each row can have a name and values. When an item of this type is created, you provide a callback function that can render each item in the list. Each time the name and value for a row is required, it will be requested from the callback.

On most devices lists work somewhat like sub menu’s when displayed on the renderer, and when presented through the API or on a remote UI, they show up as regular lists.

Lists are highly memory efficient, there’s one MenuItem that handles every row, the back item, and the “parent item” when it’s not the active menu. In your callback the request for the “title text” name comes through as row LIST_PARENT_ITEM_POS. Every other row comes through zero based up to the current size.

Class types for ListRuntimeMenuItem

Adding a list item from the designer

To add a list item in the designer select add item and then choose “List”. The properties area will switch to look similar to below:

image showing the list editor

List Item Editor UI

When you choose list, the generator will create a list callback in your sketch / main file as described below. The only parameter to be filled in at this point is the initial number of items. The number of items can change at runtime.

Creating a list menu item from the CLI

To create a list menu item from the CLI here is a template command (options in square brackets are optional):

tcmenu create-item --parent 0 --type list --name ListName [--localonly --readonly --hide]

The structure of a list menu item in the EMF file is:

{
  "parentId": 0,
  "type": "runtimeList",
  "item": {
    "initialRows": 0,
    "name": "ListName",
    "variableName": "ListName",
    "id": 11,
    "eepromAddress": 0,
    "readOnly": false,
    "localOnly": false,
    "visible": true
  }
}

Accessing List Runtime items in code

To set the number of items (your callback must be immediately able to provide data for this many rows):

setNumberOfRows(uint8_t rows)

As discussed earlier, we use one list item for everything. We discuss these methods here for completeness, but most of the time they’re used only by renderers. These methods make the list item switch into different modes - after use always call asParent() to reset the state back to the default:

// to act as a back menu
RuntimeMenuItem* asBackMenu();

// to act as a sub menu
RuntimeMenuItem* asParent();

// to act as a particular child for a given index
RuntimeMenuItem* getChildItem(int requiredIndex);

Now let us look at this in terms of how lists show up on the renderer. Below, we see an image showing an example of how lists render onto displays. Remember from above that there is only one list item, when we call one of the above three methods the list prepares itself to act as it should for that item. Notice that there is a number in brackets next to each list item, this is the row number sent to the name and value callbacks.

List item presentation on most renderers

Left, list in parent menu. Right, list drawn similar to submenu

Now we will take a look at how to send a list via the API conceptually.

// Psuedocode for Remote Message:
Message.ID = ListItem.ID
Message.Name = [call RENDERFN_NAME row =  LIST_PARENT_ITEM_POS]
For N in 0..(ListItem.NumberOfRows-1)
    Message.ListItem[N] = [call RENDERFN_NAME row = N] + [RENDERFN_VALUE row = N]
End For
Send Message

Then in the API lists we use a List to represent them.

Implementing the list rendering callback

Before reading this please ensure you’ve read items based on RuntimeMenuItem, it provides the core knowledge needed to understand the following code.

Here we present a custom rendering function for a runtime list menu item. The title will read Name255 because row is set to LIST_PARENT_ITEM_POS for the title and back function. Row 0 will be displayed as “Name0 Val0”, Row 1 will be displayed as “Name1 Val1”, and so on up to the list size defined by setNumberOfRows.

Function fastltoa is one of a number of helper functions in tcUtil.h

int CALLBACK_FUNCTION fnAdditionalCountListRtCall(RuntimeMenuItem * item, uint8_t row, 
                                                  RenderFnMode mode, 
                                                  char * buffer, int bufferSize) {
    switch(mode) {
        case RENDERFN_INVOKE:
            // when the user selects an item in the list, we get this callback.
            // row represents the item number that was selected.
            return true;
        case RENDERFN_NAME:
            // Called whenever the name of a given row is needed, if you are drawing the 
            // title then row will be set to LIST_PARENT_ITEM_POS.
            strcpy(buffer, "Name");
            fastltoa(buffer, row, 3, NOT_PADDED, bufferSize);
            return true;
        case RENDERFN_VALUE:
            // Called whenever the value of a given row is needed, if you are drawing the 
            // title then row will be set to LIST_PARENT_ITEM_POS.
            strcpy(buffer, "Val");
            fastltoa(buffer, row, 3, NOT_PADDED, bufferSize);
            return true;
        case RENDERFN_EEPROM_POS: return 0xFFFF; // lists are generally not saved to EEPROM
        default: return false;
    }
}

Notes around performance and sizing of lists

Although lists are efficient on the device, we need to be able to send them remotely, and to do that we send the whole list every time. The absolute maximum number of items a list can handle is 254, but there are limits you’ll hit before that in the protocol because it uses a single ASCII character to store the index, so the remote limit is probably around 100 items, and if it updates very frequently, possibly less.

In the future we may provide a configurable higher limit, that attempts to improve matters so that lists could hold far more items, and change the remote API to do delta publication, but right now follow the rules of thumb above.

Other pages within this category

comments powered by Disqus

This site uses cookies to analyse traffic, serve ads by Google AdSense (non-personalized in EEA/UK), and to record consent. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Our privacy policy applies to all pages on our site

Should you need further guidance on how to proceed: External link for information about cookie management.