Set User Data Before Theme Callbacks are Done

Description

When I call any of the create functions such as obj = lv_label_create(parent), the next thing I do is set the user_data. However, the theme apply callback have already been complete with the user_data as NULL;

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

Windows/AMD

What LVGL version are you using?

8.3.6

What do you want to achieve?

obj = lv_label_create(parent,&my_user_data), so the callbacks are complete with my user_data correctly loaded in the obj.user_data field. I am writing a C++ library for lvgl and the user_data field normally holds the address of the object that owns the C lvgl object pointer.

What have you tried so far?

I have looked at the code and have seen that user_data is NULL when theme callbacks are done.

Hi,

I don’t think it’s possible as the themes is applied in the create function, before you can call any other functions.

To be honest I don’t like how themes are working in LVGL now. :sweat_smile: I think we need something better and more flexible. In the practice I usually create components, like ui_my_button.c/h and ui_my_button_create(parent). In this function any styles can be added with a higher level of freedom.

Could it work in your case?

I have it working, I just have to us lv_obj_t * in my theme callback and not LVObj * (my C++ class that wraps the lv_obj_t) as the callback is done before my user_data is set.

If I wanted to I can change the 8.3.6 code to suit myself and my project (I know that’s not the best solution) and add the lv_obj_create functions (such as lv_label_create) with the user parameter like lv_label_create_ex( lv_obj_t * parent, void * user_data ). Or maybe add a a define like DONT_ZERO_USER_DATA_ON_OBJ_CREATION inside the lv_obj_create funcs and then I could just set the user data before calling lv_obj_create and it wouldn’t zero that field.

I think it’s a good idea for the library in general as I saw that one of the required tasks was to do a C++ wrapper for LVGL. Without the above it makes themes a bit hard to wrap properly.

BTW, Thanks for an awesome library! It is by far the easiest and best GUI package I have used. I’ve been programming since 1980, so I’ve used a few GUI packages.

Do you need the extra user_data parameter only for themes, or is it useful for the C++ wrapper in general too?

Wow, that’s awesome! Thank you! :blush:

 class LVTheme {
 private:
 //	virtual void SetApply( LVObj *pObj ){};
 	virtual void SetApply( lv_obj_t * obj ){};
 protected:
     static void ApplyCB( struct _lv_theme_t * pTH, lv_obj_t * obj ){
         if( obj != NULL ){
 //          static_cast< LVTheme *>( pTH->user_data )->SetApply( 
 //                                   static_cast< LVObj *>( obj->user_data ));
 			static_cast< LVTheme *>( pTH->user_data )->SetApply( obj );
 		}
 	}

   ....

 };

ApplyCB is the callback that is pass to the lvgl code. As the user data has not been set when ApplyCB is called, I cannot use the commented out functions and must use raw lv_obj * in my C++ code. In my wrapper code, the user_data in lv_obj is used to hold the address of the class that “owns” the lv_obj. It works really well everywhere except this one situation as I don’t get a chance to set it.

The core function is this:

void LVObj::Create(LV_OBJ_TYPE_E type, LVObj* parent, const lv_obj_class_t* objClass )
{
    this->parent = parent;
    switch (type) {
    case LVOT_SCREEN:
        assert(parent == NULL);
        obj = lv_obj_create(NULL);
        break;
    case LVOT_BUTTON:
        assert(parent != NULL);
        obj = lv_btn_create(parent->obj);
        break;

       ....

    default:
          assert(0);
    }
    lv_obj_set_user_data(obj, this);   **// user_data is set here but the theme callback has already been done**
}

and then classes like this wrap all the lv_obj’s:

 LVObj::LVObj(LV_OBJ_TYPE_E type, LVObj* parent, const lv_obj_class_t* objClass, uint16_t szx, uint16_t szy, uint16_t align, int16_t posx, int16_t posy)
 {
     Create(type, parent, objClass);
     SetWidth(szx);
     SetHeight(szy);
     SetAlign(align);
     SetX(posx);
     SetY(posy);
     SetStyleTextColor( LVOBJ_DISABLED_COLOUR, LV_PART_MAIN | LV_STATE_DISABLED );
 }
 
 LVButton::LVButton(LVObj* parent, uint16_t szx, uint16_t szy, uint16_t align, int16_t posx, int16_t posy)
     : LVObj(LVOT_BUTTON, parent, szx, szy, align, posx, posy)
 {
     AddFlag(LV_OBJ_FLAG_SCROLL_ON_FOCUS);
     ClearFlag(LV_OBJ_FLAG_SCROLLABLE);
     AddFlag(LV_OBJ_FLAG_EVENT_BUBBLE);
 }

What would be super handy for wrapper code is:

void LVObj::Create(LV_OBJ_TYPE_E type, LVObj* parent, const lv_obj_class_t* objClass )
 {
     this->parent = parent;
     switch (type) {
     case LVOT_SCREEN:
         assert(parent == NULL);
         obj = lv_obj_create(NULL,this);    // <-----------pass in this for user_data here
         break;
     case LVOT_BUTTON:
         assert(parent != NULL);
         obj = lv_btn_create(parent->obj,this);        // <-----------pass in this for user_data here
         break;
        ...
 
     default:
         assert(0);
     }
 }

So I don’t really require any extra user_data pointers in lv_obj, just to be able to set it before any callbacks are done. In general, the lv_obj user_data is super useful for any wrapper code.

For example lv_button_create() is implemented like this now:

lv_obj_t * lv_button_create(lv_obj_t * parent)
{
    LV_LOG_INFO("begin");
    lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
    lv_obj_class_init_obj(obj);
    return obj;
}

So what if you do this in the binding:

    lv_obj_t * obj = lv_obj_class_create_obj(&lv_button_class, parent);

    //To object is allocated, so you can set the user_data here
    obj->user_data = binding_data;

   //Call the constructors and lv_theme_apply
    lv_obj_class_init_obj(obj);

Yes, that’s exactly what I need to do. I can modify it here to do that. I’m stuck on 8.3.6 (or maybe I can go to 8.4.0)

I would do:

lv_obj_t * lv_btn_create_ex(lv_obj_t * parent, void * user_data)
{
    LV_LOG_INFO("begin");
    lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
    obj->user_data = user_data;
    lv_obj_class_init_obj(obj);
    return obj;
}


lv_obj_t * lv_btn_create(lv_obj_t * parent)
{
    return lv_btn_create_ex(parent, NULL);
}

I can change all the widget create files here so it works for my wrappers. If you are interested in me changing the repo, let me know and I’ll check it out and do it. Whatever you want.

Do you really need the _ex functions to exist in lvgl, or can you use these two functions in the constructor of the widgets?

Ah ok, I see what you’re saying. Yes I didn’t look properly and I thought I couldn’t get to the lv_btn_class from outside lv_btn.c. Yes, I can do that.

Awesome! :+1:

That’s mostly working well. The only snag I’ve hit is dropdown lists. The dropdown list constructor gets called from inside the dropdown and I don’t get a chance to intercept that one.

static lv_obj_t * lv_dropdown_list_create(lv_obj_t * parent)
{
    LV_LOG_INFO("begin");
    lv_obj_t * obj = lv_obj_class_create_obj(&lv_dropdownlist_class, parent);
    lv_obj_class_init_obj(obj);
    return obj;
}

static void lv_dropdown_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
    LV_UNUSED(class_p);
    LV_TRACE_OBJ_CREATE("begin");

    lv_dropdown_t * dropdown = (lv_dropdown_t *)obj;

    /*Initialize the allocated 'ext'*/
    dropdown->list          = NULL;
    dropdown->options     = NULL;
    
    ...

    dropdown->list = lv_dropdown_list_create(lv_obj_get_screen(obj));

I can ignore the NULL pointer I get passed in my theme apply callback (user_data is NULL for the dropdown list) and I can even apply styles to the dropdown list if it is NULL, but perhaps the _ex function solution is cleaner (at least in the specific case of the dropdown?) I guess ideally I could create the dropdown list and then pass it into the dropdown myself, but that’s not easy without code mods.

I’m getting past it by ignoring the NULL pointer I’m getting in my theme apply callback and applying the same theme from the dropdown to the list in the constructor.

LVDropdownList::LVDropdownList(LVObj* parent)
    : LVObj(LVOT_DROPDOWN_LIST, parent)
{}

LVDropdown::LVDropdown( LVObj* parent, uint16_t szx, uint16_t szy, uint16_t align, int16_t posx, int16_t posy, LVFont* font )
    : LVObj(LVOT_DROPDOWN, parent, szx, szy, align, posx, posy),
    list(this)
{
	list.SetStyleBGColor( LVObj::GetStyleBGGradColor( LV_PART_MAIN ), LV_PART_MAIN );
	list.SetStyleTextColor( LVObj::GetStyleTextColor( LV_PART_MAIN ), LV_PART_MAIN );

In my LVObj() constructor I have:

    case LVOT_DROPDOWN_LIST:
        assert(parent != NULL);
		obj = lv_dropdown_get_list(parent->obj);
//		lvc = &lv_dropdownlist_class;

Is there any easy way to get a style from an object and apply the same style to another object part for part (by part I mean such as LV_PART_MAIN). There used to be an lv_obj_get_style() func?

It seems there is no API like lv_obj_get_style() API now, but you can use

    scale_line->styles[i].
    scale_line->style_cnt

Ok thanks, I’ll use that