Mobile native

Genlist: Managing Large Element Sets

This tutorial deals with genlists, a list component for large sets of elements. It uses callbacks to populate entries. The same UI component handles both flat lists and trees.

This feature is supported in mobile applications only.

Warm-up

Become familiar with the Elementary and Evas API basics by learning about:

Initializing the Application

Figure: Example of a genlist

Example of a genlist

The code below shows a typical elementary application that creates a window entitled "Genlist Basic Tutorial". It is consisted of a conformant component that contains a naviframe component. The genlist goes inside the naviframe.

static bool
_app_create(void *data)
{
   appdata_s *app = data;

   app->win = elm_win_util_standard_add("main", "Genlist Basic Tutorial");
   elm_win_conformant_set(app->win, EINA_TRUE);
   evas_object_show(app->win);
   evas_object_resize(app->win, 480, 800);
   elm_win_autodel_set(app->win, EINA_TRUE);

   app->conformant = elm_conformant_add(app->win);
   evas_object_size_hint_weight_set(app->conformant, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   elm_win_resize_object_add(app->win, app->conformant);
   evas_object_show(app->conformant);

   app->naviframe = elm_naviframe_add(app->win);
   evas_object_size_hint_weight_set(app->naviframe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   elm_win_resize_object_add(app->win, app->naviframe);
   evas_object_show(app->naviframe);
   elm_object_content_set(app->conformant, app->naviframe);

   _create_list(app);
   elm_naviframe_item_push(app->naviframe, NULL, NULL, NULL, app->list, NULL);

   return true;
}

int
main(int argc, char **argv)
{
   // Declare a few structures and zero-initialize (C99 feature)
   struct app_data app = { 0 };
   ui_app_lifecycle_callback_s event_callback = { 0 };

   event_callback.create = _app_create;

   // Run the mainloop
   return ui_app_main(&argc, &argv, &event_callback, &ad);
}

The declaration of the struct app_data is shown below:

struct app_data
{
   Evas_Object *win;
   Evas_Object *naviframe;
   Evas_Object *conformant;
   Evas_Object *list;
   Elm_Genlist_Item_Class *itc;
   Elm_Genlist_Item_Class *itc2;
};

Creating a Genlist

Call elm_genlist_add() to create a genlist. Then new entries can be added. In this example, first the basic windows is created, then a genlist is added to it, and then 10000 elements with text and a colored block on each side of it.

Evas_Object *list = elm_genlist_add(parent);

Adding New Entries

Use elm_genlist_item_append() to add new elements. Its second parameter is a structure which describes how to populate entries. Typically this structure is built once and re-used across calls to elm_genlist_item_append().

Building a Basic Item Class

The code for the minimal genlist item class is below:

Elm_Genlist_Item_Class *itc = elm_genlist_item_class_new();
itc->item_style = "default";
itc->func.text_get = NULL;
itc->func.content_get = NULL;
itc->func.state_get = NULL;
itc->func.del = NULL;

It creates a simple item class, sets the item_style to "default" and every other field to NULL. However, this leaves out the text_get and content_get fields which are used to add text and an icon to the list entry. This is explained in another section.

Adding the Element

Once the genlist item class object is created, a new element is added to the list by calling elm_genlist_item_append().

elm_genlist_item_append(list,
   itc,
   NULL,                    // Item data
   NULL,                    // Parent item for trees, NULL if none
   ELM_GENLIST_ITEM_NONE,   // Item type; this is the common one
   NULL,                    // Callback on selection of the item
   NULL                     // Data for that callback function
);

With most parameters as NULL and itc having most of its members NULL, too, the elements of that list are blank and will not trigger anything when selected. This shows the APIs that are used.

Text in the List Elements

Use text_get callback to add text in the elements in the Elm_Genlist_Item_Class structure. These callbacks must have a prototype matching to the following:

char * text_get(void *data, Evas_Object *obj, const char *part);

This callbacks returns a C string that is displayed in the part named after the part parameter. This callback is called for each user-settable text part according to the current theme.

If you are not familiar with the concept of parts in the EFLs, read the Write a Simple EDC File section.

Note
The value returned is freed by the EFLs: the value must be freshly-allocated, do not free it yourself and do not re-use it across list elements.

For the default theme there is one part named elm.text. A possible implementation of the text_get callback is therefore:

static char *
_genlist_text_get(void *data, Evas_Object *obj, const char *part)
{
   // Check this is text for the part we're expecting
   if (strcmp(part, "elm.text") == 0) 
   {
      return strdup("Some text");
   }
   else {
      return NULL;
   }
}
Note
The names and positions of parts depends on the item_style chosen when adding new items to the genlist. Setting a custom theme makes it possible to completely change genlists by adding and moving parts. The Edje guide explains how to do that.

The data parameter makes it possible to behave differently according to data that is given to the EFLs during the call to elm_genlist_item_append() in the data parameter. For example, given an integer in that field through casting with (void *)(uintptr_t) i, it is possible to get its value back using (int)(uintptr_t)data:

static char *
_genlist_text_get(void *data, Evas_Object *obj__UNUSED__, const char *part)
{
   if (strcmp(part, "elm.text") == 0) 
   {
      char *buf = malloc(16);
      snprintf(buf, 16, "Entry %d.", (int)(uintptr_t)data);

      return buf;
   }
   else 
   {
      return NULL;
   }
}

Evas_Objects in the List Elements

Icons are added in a similar fashion: there is a callback named content_get which returns a pointer to an Evas_Object and is called for each part which contents can be set.

The prototype of the callback must match this one:

Evas_Object * content_get(void *data, Evas_Object *obj, const char *part);

The only difference with the text_get callback is that it returns an Evas_Object* rather than a char *.

This leads to a fairly simple dummy implementation with colored rectangles in the parts that are to be set:

static Evas_Object *
_genlist_content_get(void *data, Evas_Object *obj, const char *part)
{
   int i = (int) (uintptr_t) data;

   if (strcmp(part, "elm.swallow.icon") == 0) 
   {
      Evas_Object *bg = elm_bg_add(obj);
      elm_bg_color_set(bg, 255 * cos(i / (double) 10), 0, i % 255);

      return bg;
   }
   else if (strcmp(part, "elm.swallow.end") == 0) 
   {
      Evas_Object *bg = elm_bg_add(obj);
      elm_bg_color_set(bg, 0, 255 * sin(i / (double) 10), i % 255);

      return bg;
   }
   else 
   {
      return NULL;
   }
}

For the default theme, this displays a red rectangle on the left of each list item and a green one on their right.

Events on Genlist Items

Genlist items triggers a callback when clicked. This callback is chosen when adding the new item (for example, when calling elm_genlist_item_append()):

elm_genlist_item_append(list,
   itc,
   NULL,                    // item data
   NULL,                    // parent item for trees, NULL if none
   ELM_GENLIST_ITEM_NONE,   // item type, other values are used for trees
   _genlist_selected_cb,      // callback on selection of the item
   NULL                     // data for that callback function
);

This callback adheres to the following prototype:

void _contact_selected_cb(void *data, Evas_Object *obj, void *event_info)

The implementation below changes the item style of items when they are selected:

static void
_genlist_selected_cb(void *data, Evas_Object *obj, void *event_info)
{
   struct app_data *app = data;

   Elm_Object_Item *it = (Elm_Object_Item*) event_info;

   elm_genlist_item_item_class_update(it, app->itc2);
}

Choosing Another Item Style to Add or Remove Parts

As mentioned above, the number of parts to fill depends on the item style that is chosen when adding a new item. This is simply a matter of setting the right value when filling the Elm_Genlist_Item_Class struct:

app->itc->item_style = "default";

The Genlist component lists all available item styles.

Further customization is achieved by modifying the theme as explained in the Edje guide.

In case the customization is only visual, it is good practice to keep the same item style names for new themes. This makes it possible to change theme and keep the code the same while also retaining the same overall item placement.

Using Item Modes

So far the genlist examples have all featured bare lists while the genlist component is able to display trees or even a "group" mode where scrolling keeps the item at the top of the UI component until another group comes and replaces it.

Group Mode

The group mode makes it possible to keep an element visible as long as one of its children is visible. This is most useful for "title" items.

Mark some elements as ELM_GENLIST_ITEM_GROUP and use the returned Elm_Object_Item to establish the parent-children relationship when adding the children items.

Since there are two kind of items, create two item classes. Give them different styles and callback functions. The callback functions are visible in the example, they have no functionalities:

app->itc = elm_genlist_item_class_new();
app->itc->item_style = "default_style";
app->itc->func.text_get = _genlist_text_get_size;
app->itc->func.content_get = _genlist_content_get_bg;
app->itc->func.state_get = NULL;
app->itc->func.del = NULL; 

app->itc2 = elm_genlist_item_class_new();
app->itc2->item_style = "icon_top_text_bottom";
app->itc2->func.text_get = _genlist_text_get_nosize;
app->itc2->func.content_get = _genlist_content_get_icon;
app->itc2->func.state_get = NULL;
app->itc2->func.del = NULL; 

Then add a group header and follow it with 10 children. This is repeated 1000 times.

The parent has type ELM_GENLIST_ITEM_GROUP while the children have type ELM_GENLIST_ITEM_NONE.

The other important point is that the value returned by lm_genlist_item_append() is stored in it and then sent to the elm_genlist_item_append() call that adds the children. This creates the parent-children relationship.

for (i = 0; i < 1000; i++) 
{
   it = elm_genlist_item_append(app->list, app->itc2,
      (void *)(uintptr_t) (10 * i),
      NULL,
      ELM_GENLIST_ITEM_GROUP,
      NULL,
      NULL
   );
   for (j = 0; j < 10; j++)
   {
      elm_genlist_item_append(app->list, app->itc,
         (void *)(uintptr_t) (10 * i + j),
         it,
         ELM_GENLIST_ITEM_NONE,
         NULL,
         NULL
      );
   }
}

Tree Mode

Like group mode, tree mode uses the parenting relationship with other items. Unlike group mode, the child elements are created on-demand when their parent is expanded and deleted when it is contracted. This is done by using smart callbacks: expand,request, expanded, contract,request, and contracted. Like any smart callback, they are registered through evas_object_smart_callback_add on the genlist object:

evas_object_smart_callback_add(app->list, "expand,request",
   _tree_item_expand_request, NULL);
evas_object_smart_callback_add(app->list, "expanded",
   _tree_item_expanded, NULL);

evas_object_smart_callback_add(app->list, "contract,request",
   _tree_item_contract_request, NULL);
evas_object_smart_callback_add(app->list, "contracted",
   _tree_item_contracted, NULL);

The callbacks expand,request and contract,request do only one thing: decide whether the element is expanded or contracted. This is done by using elm_genlist_item_expanded_set() function; if it changes the expansion status of the item, the next callback is called (either expanded or contracted, depending on whether it was an expand,request or contract,request event). A minimal implementation of these callbacks is therefore:

static void
_tree_item_expand_request(void *data, Evas_Object *o, void *event_info)
{
   Elm_Object_Item *it = (Elm_Object_Item*) event_info;

   elm_genlist_item_item_class_update(it, app->itc2);

   elm_genlist_item_expanded_set(it, EINA_TRUE);
}
Note
The example above has an extra line: the call to elm_genlist_item_item_class_update(). It changes the item style and is explained in the Changing the item class of an item after its creation section.
static void
_tree_item_contract_request(void *data, Evas_Object *o, void *event_info)
{
   Elm_Object_Item *it = (Elm_Object_Item*) event_info;

   elm_genlist_item_item_class_update(it, app->itc);

   elm_genlist_item_expanded_set(it, EINA_FALSE);
}

As said above, once the genlist item status is set to expanded, the expanded event is triggered and it is the duty of a callback for that event to populate the list with the item's children. This relies on the parent parameter of functions like elm_genlist_item_append(), like for the group mode.

The function below is a callback implementation for the expanded event. It adds items that are built similarly to previous items, the only change is the parent parameter which is not NULL. Conveniently, the parent Elm_Object_Item pointer that is passes to the elm_genlist_item_append() function is given in the event_info callback and needs to be cast.

static void
_tree_item_expanded(void *data, Evas_Object *o, void *event_info)
{
   Elm_Object_Item *it_parent = (Elm_Object_Item*) event_info;
   int i_parent = (int)(uintptr_t) data;
   int i;

   for (i = 0; i < 10; i++) 
   {
      elm_genlist_item_append(app->list, app->itc,
            (void *)(uintptr_t) (i + i_parent),
            it_parent,
            ELM_GENLIST_ITEM_NONE,
            NULL,
            NULL
      );
   }
}

The following code has the callback function for the contracted event. It imply calls elm_genlist_item_subitems_clear() to clear all children (including their own children if they have any) of the given item. Again, the item that is being contracted is available through the event_info parameter to the callback.

static void
_tree_item_contracted(void *data, Evas_Object *o, void *event_info)
{
   Elm_Object_Item *it_parent = (Elm_Object_Item*) event_info;

   elm_genlist_item_subitems_clear(it_parent);
}

Mixing Group and Tree Modes

A common UI design is to mix group and tree modes. It allows for a tree behavior while also keeping the group header item. The EFLs do not do any magic here and the way to get such a behavior is to create an item of type group, an item of type tree which parent is the group item. Then add the callbacks to populate the children of the tree item in the regular way.

Using Other APIs

Homogeneous Item Size

Because of the scroller, the actual height and/or width of the genlist must be computed. This means summing the sizes of all the items, the sizes must be computed. This obviously has a cost and slows down adding items to the genlist.

The elm_genlist_homogeneous_set() function alleviates this issue by assuming all the items are the same size as the first one of the list. It speeds up large insertions. However, it may lead to serious graphical issues if the items are not actually the same size. Use with care.

Changing the Item Class of an Item After Its Creation

Changing the item class of a UI component is an easy way to change its appearance upon selection or other actions of the user. This is done by calling elm_genlist_item_class_update():

static void
_tree_item_expand_request(void *data, Evas_Object *o, void *event_info)
{
   Elm_Object_Item *it = (Elm_Object_Item*) event_info;

   // Change the appearance and possibly content of the item being expanded.
   elm_genlist_item_item_class_update(it, app->itc2);

   elm_genlist_item_expanded_set(it, EINA_TRUE);
}
Go to top