Implement radio button with checkbox

Hello Dear friends,
I was trying to implement a radio button by the means of the checkbox widget. I know that this can be done with the btnmatrix widget but the checkbox is something else.
My first method is to use the evil Global variable. The code is as follows.

struct Object
{
    lv_obj_t * Selection;
    unsigned char num;
};
struct Object ViewContent[4];
static void VIEW_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    struct Object *ViewTemp = lv_event_get_user_data(e);
    unsigned char userdata = ViewTemp->num;

    if(code == LV_EVENT_RELEASED)
    {
        lv_obj_add_state (ViewTemp.Selection, LV_STATE_CHECKED);
        for (unsigned char i=0;i<4;i++)
            if (i!=userdata)
                lv_obj_clear_state(ViewContent[i].Selection, LV_STATE_CHECKED);
    }
}

lv_obj_t* CreateScreenMenuView(void)
{
    ScreenMenu = lv_obj_create(NULL);
    lv_scr_load_anim(ScreenMenu,LV_SCR_LOAD_ANIM_FADE_ON, 200, 0, true);

    for (unsigned char i = 0;i<4;i++)
        {
            ViewContent[i].Selection = lv_checkbox_create(ScreenMenu);
            ViewContent[i].num = i+1;
            lv_obj_add_event_cb(ViewContent[i].Selection,VIEW_event_handler,LV_EVENT_RELEASED,&ViewContent[i]);
        }

    lv_checkbox_set_text(ViewContent[0].Selection,"E1");
    lv_checkbox_set_text(ViewContent[1].Selection,"E2");
    lv_checkbox_set_text(ViewContent[2].Selection,"E3");
    lv_checkbox_set_text(ViewContent[3].Selection,"E4");
    lv_obj_add_state(ViewContent[0].Selection,LV_STATE_CHECKED);

    lv_obj_set_pos(ViewContent[0].Selection,100,100);
    lv_obj_align_to(ViewContent[1].Selection,ViewContent[0].Selection,LV_ALIGN_OUT_BOTTOM_LEFT,0,30);
    lv_obj_align_to(ViewContent[2].Selection,ViewContent[1].Selection,LV_ALIGN_OUT_BOTTOM_LEFT,0,30);
    lv_obj_align_to(ViewContent[3].Selection,ViewContent[2].Selection,LV_ALIGN_OUT_BOTTOM_LEFT,0,30);

    CreateReturnButton(ScreenMenu);
    return ScreenMenu;
}

The code above works, but there are some problems. First and foremost, the problem is with static and non-dynamic definitions. By which I mean via the above method, the number of buttons should be determined in advance. Second, the variable containing is global.
Then, my next try was as follows

typedef struct
{
    unsigned char index;
    lv_obj_t * radiobtnVIEW[4];
}AssetVIEW
static void VIEW_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_current_target(e);
    AssetVIEW * ViewTemp = lv_event_get_user_data(e);

    if(code == LV_EVENT_RELEASED)
    {
        for (unsigned char i=0;i<4;i++)
                lv_obj_clear_state(ViewTemp->radiobtnVIEW[i], LV_STATE_CHECKED);
        lv_obj_add_state (target, LV_STATE_CHECKED);
        for (unsigned char i=0;i<4;i++)
            if(lv_obj_has_state (ViewTemp->radiobtnVIEW[i], LV_STATE_CHECKED))
            {
                ViewTemp->index = i;
                break;
            }
    }
    ViewTemp->index;
}

lv_obj_t* CreateScreenMenuView(void)
{
    ScreenMenu = lv_obj_create(NULL);
    lv_scr_load_anim(ScreenMenu,LV_SCR_LOAD_ANIM_FADE_ON, 200, 0, true);
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_radius(&style,10);
    static AssetVIEW ViewContent;
    for (unsigned char i = 0;i<4;i++)
    {
        ViewContent.radiobtnVIEW[i] = lv_checkbox_create(ScreenMenu);
        lv_obj_add_style(ViewContent.radiobtnVIEW[i],&style,LV_PART_INDICATOR);
        lv_obj_add_event_cb(ViewContent.radiobtnVIEW[i],VIEW_event_handler,LV_EVENT_RELEASED,&ViewContent);
    }

    lv_checkbox_set_text(ViewContent.radiobtnVIEW[0],"E1");
    lv_checkbox_set_text(ViewContent.radiobtnVIEW[1],"E2");
    lv_checkbox_set_text(ViewContent.radiobtnVIEW[2],"E3");
    lv_checkbox_set_text(ViewContent.radiobtnVIEW[3],"E4");
    lv_obj_add_state(ViewContent.radiobtnVIEW[0],LV_STATE_CHECKED);

    lv_obj_set_pos(ViewContent.radiobtnVIEW[0],100,100);
    lv_obj_align_to(ViewContent.radiobtnVIEW[1],ViewContent.radiobtnVIEW[0],LV_ALIGN_OUT_BOTTOM_LEFT,0,30);
    lv_obj_align_to(ViewContent.radiobtnVIEW[2],ViewContent.radiobtnVIEW[1],LV_ALIGN_OUT_BOTTOM_LEFT,0,30);
    lv_obj_align_to(ViewContent.radiobtnVIEW[3],ViewContent.radiobtnVIEW[2],LV_ALIGN_OUT_BOTTOM_LEFT,0,30);

    CreateReturnButton(ScreenMenu);
    return ScreenMenu;
}

The last code problem is non-dynamic initialization. By which I mean the number of the radio buttons should be determined in advance.
Then I looked for the lv_group functionality. But I couldn’t get deep into it. Your help is really appreciated.

As for the non-dynamic initialization, world wide friend had lead me to FAM. Ref1,Ref2
The solution got so far is as follows

typedef struct
{
    unsigned char index;
    lv_obj_t * radiobtnVIEW[];
}AssetVIEW
static void VIEW_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_current_target(e);
    unsigned char n = 4;

    AssetVIEW * ViewTemp = malloc(sizeof(*ViewTemp) + n * sizeof(*ViewTemp->radiobtnVIEW));
    ViewTemp = lv_event_get_user_data(e);

    if(code == LV_EVENT_RELEASED)
    {
        for (unsigned char i=0;i<n;i++)
                lv_obj_clear_state(ViewTemp->radiobtnVIEW[i], LV_STATE_CHECKED);
        lv_obj_add_state (target, LV_STATE_CHECKED);
        for (unsigned char i=0;i<n;i++)
            if(lv_obj_has_state (ViewTemp->radiobtnVIEW[i], LV_STATE_CHECKED))
            {
                ViewTemp->index = i;
                break;
            }
    }
    ViewTemp->index;
}

lv_obj_t* CreateScreenMenuView(void)
{
    ScreenMenu = lv_obj_create(NULL);
    lv_scr_load_anim(ScreenMenu,LV_SCR_LOAD_ANIM_FADE_ON, 200, 0, true);
    static lv_style_t style;
    lv_style_init(&style);
    lv_style_set_radius(&style,10);
    unsigned char n = 4;
    AssetVIEW * ViewContent = malloc(sizeof(*ViewContent) + n * sizeof(*ViewContent->radiobtnVIEW));
    for (unsigned char i = 0;i<n;i++)
    {
        ViewContent->radiobtnVIEW[i] = lv_checkbox_create(ScreenMenu);
        lv_obj_add_style(ViewContent->radiobtnVIEW[i],&style,LV_PART_INDICATOR);
        lv_obj_add_event_cb(ViewContent->radiobtnVIEW[i],VIEW_event_handler,LV_EVENT_RELEASED,ViewContent);
    }

    lv_checkbox_set_text(ViewContent->radiobtnVIEW[0],"E1");
    lv_checkbox_set_text(ViewContent->radiobtnVIEW[1],"E2");
    lv_checkbox_set_text(ViewContent->radiobtnVIEW[2],"E3");
    lv_checkbox_set_text(ViewContent->radiobtnVIEW[3],"E4");

    lv_obj_add_state(ViewContent->radiobtnVIEWViewContent->index],LV_STATE_CHECKED);

    lv_obj_set_pos(ViewContent->radiobtnVIEW[0],100,100);
    lv_obj_align_to(ViewContent->radiobtnVIEW[1],ViewContent->radiobtnVIEW[0],LV_ALIGN_OUT_BOTTOM_LEFT,0,30);
    lv_obj_align_to(ViewContent->radiobtnVIEW[2],ViewContent->radiobtnVIEW[1],LV_ALIGN_OUT_BOTTOM_LEFT,0,30);
    lv_obj_align_to(ViewContent->radiobtnVIEW[3],ViewContent->radiobtnVIEW[2],LV_ALIGN_OUT_BOTTOM_LEFT,0,30);

    CreateReturnButton(ScreenMenu);
    return ScreenMenu;
}

The only matter that bothers me is that I couldn’t embed radio button count to the structure.

What about enabling LV_OBJ_FLAG_EVENT_BUBBLE on the check boxes (it delegates the events of the check boxes to the parent too) and adding an LV_EVENT_CLICKED event to the parent.

See this example for event bubbling: Examples — LVGL documentation

For the radio buttons I assume you will store the index of the selected checkbox in an int variable. Let’s call it active_id and the currentnly clicked check box is obj. In the parent’s event all you need to do is

  1. Get the last active checkbox with: lv_obj_t * cb = lv_obj_get_child(parent, active_id)
  2. lv_obj_clear_state(cb, LV_STATE_CHECKED)
  3. Be sure the currently clicked checkbox is checked: lv_obj_add_state(obj, LV_STATE_CHECKED)
  4. Get the ID of the clicked checkbox: active_id = lv_obj-get_child_id(obj)

Dear @kisvegabor
Just one question.
If there is one series of radio buttons, there is no problem with the proposed method. What if there would be multiple groups of radio buttons.

To only global data is active_id which can be stored in the user_data of the parent container.

@kisvegabor Well, I didn’t catch what you said.
To make clear what I meant I will place a minimal code here.

typedef struct
{
    unsigned char Widgetcount;
    unsigned char index;
    lv_obj_t * btn[];
}AssetRADIOBUTTON;
static void L_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_current_target(e);
    unsigned char n = 4;
    AssetRADIOBUTTON * Radio = malloc(sizeof(*Radio) + n * sizeof(*Radio->btn));
    Radio = lv_event_get_user_data(e);
    if(code == LV_EVENT_RELEASED)
    {
        lv_obj_clear_state(Radio->radiobtn[Radio->index], LV_STATE_CHECKED);
        lv_obj_add_state (target, LV_STATE_CHECKED);
        for (unsigned char i=0;i<n;i++)
            if(lv_obj_has_state (Radio->btn[i], LV_STATE_CHECKED))
            {
                Radio->index = i;
                break;
            }
    }
}

static void M_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_current_target(e);
    unsigned char n = 3;
    AssetRADIOBUTTON * Radio = malloc(sizeof(*Radio) + n * sizeof(*Radio->btn));
    Radio = lv_event_get_user_data(e);
    if(code == LV_EVENT_RELEASED)
    {
        lv_obj_clear_state(Radio->radiobtn[Radio->index], LV_STATE_CHECKED);
        lv_obj_add_state (target, LV_STATE_CHECKED);
        for (unsigned char i=0;i<n;i++)
            if(lv_obj_has_state (Radio->btn[i], LV_STATE_CHECKED))
            {
                Radio->index = i;
                break;
            }
    }
}

And in the main

    ScreenMenu = lv_obj_create(lv_scr_act());
    lv_scr_load_anim(ScreenMenu,LV_SCR_LOAD_ANIM_FADE_ON, 200, 0, true);
    unsigned char n = 4;
    AssetVIEW * Lradio = malloc(sizeof(*Lradio + n * sizeof(*Lradio->radiobtnVIEW));
    for (unsigned char i = 0;i<n;i++)
    {
        Lradio->radiobtnVIEW[i] = lv_checkbox_create(ScreenMenu);
        lv_obj_add_style(Lradio->radiobtnVIEW[i],&style,LV_PART_INDICATOR);
        lv_obj_add_event_cb(Lradio->btn[i],L_event_handler,LV_EVENT_RELEASED,Lradio );
    }

    lv_checkbox_set_text(Lradio->radiobtnVIEW[0],"L1");
    lv_checkbox_set_text(Lradio->radiobtnVIEW[1],"L2");
    lv_checkbox_set_text(Lradio->radiobtnVIEW[2],"L3");
    lv_checkbox_set_text(Lradio->radiobtnVIEW[3],"L4");
    n = 3
    AssetVIEW * Mradio = malloc(sizeof(*Mradio + n * sizeof(*Mradio->radiobtnVIEW));
    for (unsigned char i = 0;i<n;i++)
    {
        Mradio->radiobtnVIEW[i] = lv_checkbox_create(ScreenMenu);
        lv_obj_add_style(Mradio->radiobtnVIEW[i],&style,LV_PART_INDICATOR);
        lv_obj_add_event_cb(Mradio->btn[i],M_event_handler,LV_EVENT_RELEASED,Mradio );
    }

    lv_checkbox_set_text(Mradio->radiobtnVIEW[0],"M1");
    lv_checkbox_set_text(Mradio->radiobtnVIEW[1],"M2");
    lv_checkbox_set_text(Mradio->radiobtnVIEW[2],"M3");

As you see, L_event_handler and M_event_handler are exactly the same, but the variable n is different. I am looking for some way to merge these two functions and use it for both of the groups.
My solution is something like this


static void Radio_event_handler(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_current_target(e);
    unsigned char n = lv_event_get_user_data(e);
    AssetRADIOBUTTON * ViewTemp = malloc(sizeof(*ViewTemp) + n * sizeof(*ViewTemp->btn));
    ViewTemp = lv_event_get_user_data(e);
    if(code == LV_EVENT_RELEASED)
    {
        lv_obj_clear_state(ViewTemp->btn[ViewTemp->index], LV_STATE_CHECKED);
        lv_obj_add_state (target, LV_STATE_CHECKED);
        for (unsigned char i=0;i<n;i++)
            if(lv_obj_has_state (ViewTemp->btn[i], LV_STATE_CHECKED))
            {
                ViewTemp->index = i;
                break;
            }
    }
}

Based your code I’ve created an example:

Global variables to store the active radio button:

static uint32_t radio_1_active_id = 0;
static uint32_t radio_2_active_id = 0;

Create the checkboxes:

     lv_obj_t * cont1 = lv_obj_create(lv_scr_act());
     lv_obj_set_flex_flow(cont1, LV_FLEX_FLOW_COLUMN);
     lv_obj_set_size(cont1, 200, 200);
     lv_obj_add_event_cb(cont1, radio_event_handler, LV_EVENT_RELEASED, &radio_1_active_id);

     for (unsigned char i = 0;i<4;i++)
     {
         lv_obj_t * obj = lv_checkbox_create(cont1);
         char buf[32];
         lv_snprintf(buf, sizeof(buf), "L%d", i+1);
         lv_checkbox_set_text(obj, buf);
         lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE);
     }

     lv_obj_add_state(lv_obj_get_child(cont1, 0), LV_STATE_CHECKED);

     lv_obj_t * cont2 = lv_obj_create(lv_scr_act());
     lv_obj_set_flex_flow(cont2, LV_FLEX_FLOW_COLUMN);
     lv_obj_set_size(cont2, 200, 200);
     lv_obj_set_x(cont2, 220);
     lv_obj_add_event_cb(cont2, radio_event_handler, LV_EVENT_RELEASED, &radio_2_active_id);

     for (unsigned char i = 0;i<3;i++)
     {
         lv_obj_t * obj = lv_checkbox_create(cont2);
         char buf[32];
         lv_snprintf(buf, sizeof(buf), "M%d", i+1);
         lv_checkbox_set_text(obj, buf);
         lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE);
     }

     lv_obj_add_state(lv_obj_get_child(cont2, 0), LV_STATE_CHECKED);

The event:

void radio_event_handler(lv_event_t * e)
{
    uint32_t * active_id = lv_event_get_user_data(e);
    lv_obj_t * cont = lv_event_get_current_target(e);
    lv_obj_t * act_cb = lv_event_get_target(e);
    lv_obj_t * old_cb = lv_obj_get_child(cont, *active_id);

    lv_obj_clear_state(old_cb, LV_STATE_CHECKED);
    lv_obj_add_state(act_cb, LV_STATE_CHECKED);

    *active_id = lv_obj_get_index(act_cb);

    printf("%d, %d\n", radio_1_active_id, radio_2_active_id);
}

I wonder if it is of your concern to add this solution to the examples.

1 Like

Added here Checkbox (lv_checkbox) — LVGL documentation