Widget Tree

Parents and Children

LVGL stores Widgets in a parent-child structure (tree). The root element is the Screen, which has no parent.

When a Widget is created, a pointer to its parent is passed to the create function.

LVGL provides many useful functions to modify the Widget tree and get information about it:

  • lv_obj_get_screen(widget): Get a Widget's screen ("root" parent).

  • lv_obj_get_parent(widget): Get the Widget's current parent.

  • lv_obj_set_parent(widget, new_parent): Move the Widget to a new parent. The Widget will become the top-most (last/youngest) child of the new parent.

  • lv_obj_get_child(parent, idx): Return a specific child of a parent. Some examples for idx:

    • 0: get the first created child

    • 1: get the second created child

    • -1: get the last created child

  • lv_obj_get_child_by_type(parent, idx, &lv_slider_class): Similar to lv_obj_get_child but filters to children with a given type.

  • lv_obj_find_by_name(parent, "text"): Find a Widget with a given name under a parent. It does not have to be a direct child.

  • lv_obj_get_child_by_name(parent, "container/button/text"): Get a Widget by navigating a path using parent names (separated by /).

  • lv_obj_get_index(widget): Return the index of the Widget in its parent. It is equivalent to the number of older siblings in the parent.

  • lv_obj_move_to_index(widget, idx): Move the Widget to a specified index. 0 is the oldest child's position, -1 is the youngest.

  • lv_obj_swap(widget1, widget2): Swap the positions of two Widgets.

To iterate through a parent widget's children:

uint32_t i;
uint32_t count = lv_obj_get_child_count(parent);
for(i = 0; i < count; i++) {
    lv_obj_t * child = lv_obj_get_child(parent, i);
    /* Do something with `child`. */
}

Names

When a Widget is created, its reference can be stored in an lv_obj_t* pointer variable. To use this Widget in multiple places in the code, the variable can be passed as a function parameter or made global. However, this approach has some drawbacks:

  • Using global variables adds names to the global namespace and is thus generally not recommended.

  • It's not scalable. Passing references to 20 Widgets as function parameters is not ideal.

  • Tracking whether a Widget still exists or has been deleted requires extra logic and can be tricky.

Setting Names

To address these issues, LVGL introduces a powerful Widget naming system that can be enabled by setting LV_USE_OBJ_NAME in lv_conf.h.

A custom name can be assigned using lv_obj_set_name(obj, "name") or lv_obj_set_name_static(obj, "name"). The "static" variant requires that the passed name remains valid while the Widget exists, since only the pointer is stored. Otherwise, LVGL will allocate memory to store a copy of the name.

If a name ends with #, LVGL will automatically replace it with an index based on the number of siblings with the same base name. If no name is provided, the default is <widget_type>_#.

Below is an example showing how manually and automatically assigned names are resolved:

  • Main lv_obj container named "cont": "cont"

    • lv_obj container named "header": "header"

      • lv_label with no name: "lv_label_0"

      • lv_label named "title": "title"

      • lv_label with no name: "lv_label_1" (It's the third label, but custom-named Widgets are not counted)

    • lv_obj container named "buttons":

      • lv_button with no name: "lv_button_0"

      • lv_button named "second_button": "second_button"

      • lv_button with no name: "lv_button_1"

      • lv_button named lv_button_#: "lv_button_2"

      • lv_button named mybtn_#: "mybtn_0"

      • lv_button with no name: "lv_button_2"

      • lv_button named mybtn_#: "mybtn_1"

      • lv_button named mybtn_#: "mybtn_2"

      • lv_button named mybtn_#: "mybtn_3"

Note that these indices are "fluid", meaning if "mybtn_2" above is deleted, the button previously known as "mybtn_3" will thereafter be known as "mybtn_2", since the indices are resolved when names are being found by name, not when they are set. Keep this in mind when Finding Widgets as described below.

Finding Widgets

Widgets can be found by name in two ways:

  1. Get a direct child by name using lv_obj_get_child_by_name(parent, "child_name"). Example: lv_obj_get_child_by_name(header, "title"). You can also use a path to find nested children: lv_obj_get_child_by_name(cont, "buttons/mybtn_2").

  2. Find a descendant at any level using lv_obj_find_by_name(parent, "child_name"). Example: lv_obj_find_by_name(cont, "mybtn_1") Note that "mybtn_1" is a child of cont at any level, not necessarily a direct child. This is useful when you want to ignore the hierarchy and search by name alone.

Since both functions start searching from a specific parent, it's possible to have multiple Widget subtrees with identical names under different parents.

For example, if my_listitem_create(parent) creates a Widget named "list_item_#" with direct children "icon", "title", "ok_button", and "lv_label_0", and it is called 10 times, a specific "ok_button" can be found like this:

lv_obj_t * item = lv_obj_find_by_name(lv_screen_active(), "list_item_5");
lv_obj_t * ok_btn = lv_obj_find_by_name(item, "ok_button");

// Or
lv_obj_t * ok_btn = lv_obj_get_child_by_name(some_list_container, "list_item_5/ok_button");