Message box with tabview


cant get the messagebox to work properly, not created on first call but there on second
1- how do i create the message box on the current selected tab on a tabview?
2- why is the box not showing up when called

What MCU/Processor/Board and compiler are you using?

esp32 arduino ide

What LVGL version are you using?


What do you want to achieve?

i have an app with several tabs on a tabview, when a long operation is in progrss (moving a stepper motor is ongoing) i need to display a message on the current tab

What have you tried so far?

the examples on the website
below is a simple routine i call, tabzero is the current tab for this test
it does not show when called

Code to reproduce

void showmsg(void) {

mbox1 = lv_msgbox_create(tabzero, NULL);
lv_msgbox_set_text(mbox1, “Test message”);
lv_obj_align(mbox1, NULL, LV_ALIGN_CENTER, 0, 0);


in the calling code behind a button



The code block(s) should be formatted like:

/*You code here*/

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

It should not be necessary to manually invalidate the screen. You also want to be calling lv_task_handler repeatedly in a loop, not calling it one time manually.

reason was to make sure screen is refreshed, even if i put it in a loop,. the msgbox does not show up

Interesting. It sounds like the refresh task is not being triggered. Are you calling lv_tick_inc?

yes, all other code works, 1500 lines, several tab screens, only thing not working is the msgbox
i just need to display a message that the router lift is moving, all other actions are not active during it, even if i put a long looping on the task handler, nothing is shown, if i call the routine a second time the mesaage is on the screen, not just on first call

/* Interrupt driven periodic handler */
static void lv_tick_handler(void)

really stuck now, i pushed this issue forward, now the project is nearing completion but this is unresolved,

this is the code to setup the messagebox and clear it, these routines are called at the start of a routine that takes a number of steps on a stepper motor and again at the end, resulting in a model message that the stepper is moving
it works on one tab of the tabview but not the others, don’t know why

void busy_mbox_create(void)
{ /* Create a base object for the modal background */
lv_obj_t obj = lv_obj_create(lv_scr_act(), NULL);
lv_obj_reset_style_list(obj, LV_OBJ_PART_MAIN);
lv_obj_add_style(obj, LV_OBJ_PART_MAIN, &style_modal);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_HOR_RES, LV_VER_RES);
Create the message box as a child of the modal background */
mbox = lv_msgbox_create(obj, NULL);
// lv_msgbox_add_btns(mbox, btns2);
lv_msgbox_set_text(mbox, “Positioning…”);
lv_obj_add_style(mbox, LV_OBJ_PART_MAIN, &label_stylemsg1);
lv_obj_set_width(mbox, 400);
lv_obj_align(mbox, NULL, LV_ALIGN_CENTER, 0, 0);
// lv_obj_invalidate(lv_scr_act());

void busy_mbox_delete(void)
/* Delete the parent modal background /
mbox = NULL; /
happens before object is actually deleted! */

the code in fromt

// messagebox
static lv_obj_t *mbox;
static lv_style_t style_modal;

owkey, one step closer…
i know now when but not yet why
the msgbox routine is called by a function that moves a stepper, in turn this function is called by several other functions,
if the top caller is an event handler , as is the case with all button clicks, it shows no messagebox, no matter how many functions are in between, however if the top caller is not an event handler it shows the messagebox
in both cases the msgbox routine is entered and exited in the proper way, no errors just no msgbox is shown when the top caller is an event handler
any clues to WHY this is?

Are you using multiple threads? The official modal message box example creates a message box inside a button event handler, and that works fine on many other platforms, so this must be something specific to your setup.

indeed, the example works
i tried to narrow it down, reduced my 4200 lines code to a minimum to demonstrate the issue.
It seems that only when a task handler is called within the main loop() function the screen is generated with the message box
here is some code, it creates a button, when clicked a messagebox is created, a delay of 2 seconds shows that even when i call the task handler in the function it does not work, after 2 seconds i go back to the main loop which basically calles task handler every 10 msec and the messagebox shows up, ofter 5 seconds more it is closed, that works

im lost as to why i cant get the messagebox to appear outside the main loop

#include “FS.h”
#include <SPI.h>
#include <lvgl.h>
#include <Ticker.h>
#include <TFT_eSPI.h>


// touch screen calibration file
#define CALIBRATION_FILE “/calibData”

#define CS_LCD 15
#define CS_TOUCH 5

Ticker tick; /* timer for interrupt handler /
TFT_eSPI tft = TFT_eSPI(); /
TFT instance */

static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * LV_VER_RES_MAX / 10];

// touch screen calib
uint16_t calibrationData[5];
uint8_t calDataOK = 0;

// timers
unsigned long lvgl_timer;
unsigned long timer1;

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{ uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors(&color_p->full, w * h, true);

/Read the touchpad/
bool my_touchpad_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
uint16_t touchX, touchY;
bool touched = tft.getTouch(&touchX, &touchY, 600);
if (!touched)
data->state = LV_INDEV_STATE_REL;
return false;
data->state = LV_INDEV_STATE_PR;
if (touchX > 480 || touchY > 320)
/Set the coordinates/
data->point.x = touchX;
data->point.y = touchY;
return false; /Return false because we are not buffering and no more data to read/

/* Interrupt driven periodic handler */
static void lv_tick_handler(void)

static lv_obj_t *tabview;
static lv_obj_t *tabmain;

static lv_obj_t *mbox;
static lv_style_t style_modal;

static lv_obj_t *obj;

boolean destroy = false;

void setup() {


pinMode(CS_LCD, OUTPUT);

digitalWrite(CS_LCD, HIGH);
digitalWrite(CS_TOUCH, HIGH);


tft.setRotation(3); /* Landscape orientation */

// check file system
if (!SPIFFS.begin()) {
// delay to settle pins

// check if calibration file exists
File f =, “r”);
if (f) {
if (f.readBytes((char *)calibrationData, 14) == 14)
calDataOK = 1;

if (calDataOK) {
// calibration data valid
} else {
// data not valid. recalibrate

lv_disp_buf_init(&disp_buf, buf, NULL, (LV_HOR_RES_MAX * LV_VER_RES_MAX / 10));

/Initialize the display/
lv_disp_drv_t disp_drv;
disp_drv.hor_res = 480;
disp_drv.ver_res = 320;
disp_drv.flush_cb = my_disp_flush;
disp_drv.buffer = &disp_buf;

/Initialize the (dummy) input device driver/
lv_indev_drv_t indev_drv;
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;

/Initialize the graphics library’s tick/
tick.attach_ms(LVGL_TICK_PERIOD, lv_tick_handler);


lv_style_set_bg_color(&style_modal, LV_STATE_DEFAULT, LV_COLOR_BLACK);

lvgl_timer = millis() - 200;
timer1 = millis();


void loop() {

if (destroy) {
if (millis() >= timer1 + 5000) {
destroy = false;

if (millis() >= lvgl_timer + 10) {
lvgl_timer = millis();


void setupscreens(void) {
// tabs
tabview = lv_tabview_create(lv_scr_act(), NULL);
tabmain = lv_tabview_add_tab(tabview, “”);

lv_tabview_set_btns_pos(tabview, LV_TABVIEW_TAB_POS_NONE);
// main screen
lv_obj_t * btn10 = lv_btn_create(tabmain, NULL);
lv_obj_set_event_cb(btn10, event_test1);
lv_obj_t * label = lv_label_create(btn10, NULL);
lv_label_set_text(label, “test 1”);
lv_obj_set_width(btn10, 140);
lv_obj_set_height(btn10, 55);
lv_obj_align(btn10, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10);


static void event_test1(lv_obj_t * obj, lv_event_t event) {
if (event == LV_EVENT_CLICKED ) {
timer1 = millis();

static void event_calibrate(lv_obj_t * obj, lv_event_t event)
if (event == LV_EVENT_CLICKED) {



void calibratescreen(void)
// clear screen
tft.calibrateTouch(calibrationData, TFT_WHITE, TFT_RED, 15);
// store data
File f =, “w”);
if (f) {
f.write((const unsigned char *)calibrationData, 14);

static void create_box(void) {
obj = lv_obj_create(lv_scr_act(), NULL);
lv_obj_reset_style_list(obj, LV_OBJ_PART_MAIN);
lv_obj_add_style(obj, LV_OBJ_PART_MAIN, &style_modal);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_HOR_RES, LV_VER_RES);

mbox = lv_msgbox_create(obj, NULL);
lv_msgbox_set_text(mbox, “Busy…”);
lv_obj_align(mbox, NULL, LV_ALIGN_CENTER, 0, 0);
destroy = true;


timer1 = millis();

static void delete_box(void) {
mbox = NULL; /* happens before object is actually deleted! */

The screen is only re-rendered at a fixed rate, so you need to call lv_task_handler consistently (e.g. every 10ms) in order for it to get shown. Just calling it once like what you are doing won’t be reliable. That’s why it works when loop() is called but not when you use a delay.

not sure i agree, even if i put the task handler in the create_box function in an infinite loop like below, the screen is never refreshed, the main loop() function does nothing more than the same with some added delay

while (1) {

so,if i look at the return parameter from the task handler, outside of the main loop it is 1, in the main loop it is the tick time remaining, looking at the source i see that it is probably detecting another instance of the task handler already running therefor simply quitting
is there any other way to refresh the screen outside the main loop() function?

LV_ATTRIBUTE_TASK_HANDLER uint32_t lv_task_handler(void)

LV_LOG_TRACE("lv_task_handler started");

/*Avoid concurrent running of the task handler*/
static bool already_running = false;
if(already_running) return 1;
already_running = true;

Not that I know of, as that’s not how LVGL (or most event loops, for that matter) are designed to work. The expectation is that your program does not block and returns control to the main loop whenever anything needs to be rendered.

i usually do put the code in the main loop but this one is a bit complex to do …
i implemented plan B as below, the box disappears when the screen is refreshed when i go back to the main loop

void busy_msg(void) {
tft.fillRoundRect(139, 109, 200, 100, 5, TFT_RED);
tft.fillRoundRect(143, 113, 192, 92, 5, TFT_WHITE);
tft.drawString(“Busy”, 187, 133, 1);