V8.1 msgbox... activated by multiple buttons

using v8.1
There must be a better way of accomplishing this
without globals and callbacks for each button.

2022-02-02 15_51_57-LVGL_MicroPython Simulator

Simulator link…
https://sim.lvgl.io/v8.1/micropython/ports/javascript/index.html?script_direct=9e6d6c8361a01ae912a6235a641aed57047b6475

Some code…

def mm_cb(event):
    global mbox1
    mbox = event.get_current_target()
    txt = mbox.get_active_btn_text()
    if txt == 'YES':
        print('yes reset')
    lv.msgbox.close(mbox1)
    
def fw_cb(event):
    global mbox1
    mbox = event.get_current_target()
    txt = mbox.get_active_btn_text()
    if txt == 'YES':
        print('yes install')
    lv.msgbox.close(mbox1)

def pwr_cb(event):
    global mbox1
    mbox = event.get_current_target()
    txt = mbox.get_active_btn_text()
    if txt == 'YES':
        print('yes power down')
    lv.msgbox.close(mbox1)


def action_cb(event):
    global mbox1
    btn = event.get_current_target()

    if btn == mute_btn:
        None

    if btn == mm_btn:
        mbox1 = lv.msgbox(None, "Continue Reset of\nMinimum and Maximum\nTemperature Values ?", btns, True)
        mbox1.add_event_cb(mm_cb, lv.EVENT.VALUE_CHANGED, None)
        mbox1.center()

    if btn == fw_btn:
        mbox1 = lv.msgbox(None, "Update Found...\n Install ?", btns, True)
        mbox1.add_event_cb(fw_cb, lv.EVENT.VALUE_CHANGED, None)
        mbox1.center()        

    if btn == pwr_btn:
        mbox1 = lv.msgbox(None, "Confirm", "Are you shure you want to power down ?", btns, True)
        mbox1.add_event_cb(pwr_cb, lv.EVENT.VALUE_CHANGED, None)
        mbox1.center()

    btn.set_style_bg_color(lv.palette_main(lv.PALETTE.RED), 0)

I think you can take advantage of user data on event callbacks to make them a lot more data-driven. Here’s an example from a couple minutes of playing around with it. Since the dictionary is handled at Python level, not by LVGL, you can put anything in there, including other Python functions, so you have a lot of flexibility in terms of how you structure everything.

https://sim.lvgl.io/v8.1/micropython/ports/javascript/index.html?script_direct=79ae937e30cf9c04c559dfa3b208f89a8da22042

Before using this, I would double-check with @amirgon that this is the correct way of handling user_data in MicroPython. I noticed some very strange behavior when calling set_user_data on the object itself (event.get_current_target() started returning the user data), so I had to put it on the event callback. I think this is allowed but I’m not 100% sure.

I strongly advise against using user_data argument in Micropython for that purpose.
user_data is used internally by the Micropython Bindings.
You should keep the user_data argument as None in 99% of the cases.
It should only be used in cases where the caller on the C side needs to examine the user_data and extract some information from it, which is never the case on LVGL callbacks.

You can easily achieve what you want very nicely in other ways.
Remember that in Python, as opposed to C, you can pass any Callable Object as a callback and not just plain functions. For example, you can pass an object’s member function or a lambda function.

So here is your example restrctured, trying to make it more Object Oriented.
No globals, with member functions and lambdas as callbacks:

Online simulator


# Initialize 

import display_driver
import lvgl as lv

# --------------------------------------------------------------------------------

class MyMsgbox(lv.msgbox):
    btns = ["YES", "NO", ""]

    def __init__(self, yes_cb, txt):        
        super().__init__(None, "Confirm", txt, MyMsgbox.btns, True)
        self.yes_cb = yes_cb
        if self.yes_cb:
            self.add_event_cb(self.msgbox_cb, lv.EVENT.VALUE_CHANGED, None)

    def msgbox_cb(self, event):
        txt = self.get_active_btn_text()
        if txt == 'YES':
            self.yes_cb()
        self.close()

class MyBtn(lv.btn):
    def __init__(self, parent, *msg_box_args):
        super().__init__(parent)        
        self.msg_box_args = msg_box_args
        if self.msg_box_args :
            self.add_event_cb(self.btn_cb, lv.EVENT.CLICKED, None)

    def btn_cb(self, event):
        self.mbox = MyMsgbox(*self.msg_box_args)        
        self.mbox.center()
        self.set_style_bg_color(lv.palette_main(lv.PALETTE.RED), 0)

# --------------------------------------------------------------------------------

modal = lv.obj()

# --------------------------------------------------------------------------------

mute_btn = MyBtn(modal)
mute_btn.set_size(200,40) 
mute_btn.align(lv.ALIGN.TOP_RIGHT, -130, 60)

mute_btn_lbl = lv.label(mute_btn)
mute_btn_lbl.set_text("MUTE  ALARM")
mute_btn_lbl.align(lv.ALIGN.CENTER, 0, 0)

# --------------------------------------------------------------------------------

mm_btn = MyBtn(modal, lambda: print('yes reset'), "Continue Reset of\nMinimum and Maximum\nTemperature Values ?")
mm_btn.set_size(200,40)
mm_btn.align(lv.ALIGN.TOP_RIGHT, -130, 120)

mm_btn_lbl = lv.label(mm_btn)
mm_btn_lbl.set_text("RESET  MIN/MAX")
mm_btn_lbl.align(lv.ALIGN.CENTER, 0, 0)

# --------------------------------------------------------------------------------

fw_btn = MyBtn(modal, lambda: print('yes install'), "Update Found...\n Install ?")
fw_btn.set_size(200,40)
fw_btn.align(lv.ALIGN.TOP_RIGHT, -130, 180)

fw_btn_lbl = lv.label(fw_btn)
fw_btn_lbl.set_text("CHECK  FOR  UPDATE")
fw_btn_lbl.align(lv.ALIGN.CENTER, 0, 0)

# --------------------------------------------------------------------------------

pwr_btn = MyBtn(modal, lambda: print('yes power down'), "Are you shure you want to power down ?")
pwr_btn.set_size(200,40)
pwr_btn.align(lv.ALIGN.TOP_RIGHT, -130, 240)

pwr_btn_lbl = lv.label(pwr_btn)
pwr_btn_lbl.set_text("POWER  OFF")
pwr_btn_lbl.align(lv.ALIGN.CENTER, 0, 0)

# --------------------------------------------------------------------------------

close_btn = lv.btn(modal)
close_btn.set_size(20,20)
close_btn.align(lv.ALIGN.CENTER, 96, -140)

close_btn_lblb = lv.label(close_btn)
close_btn_lblb.set_size(9,16)
close_btn_lblb.set_text("X")
close_btn_lblb.align(lv.ALIGN.CENTER, 0, -2)

lv.scr_load(modal)

embeddedt… as usual your help in these call backs is much appreciated !
I struggle mostly with on how to get data in these callbacks and your use
of the ‘.cast’ seems to be the only method.
I wish the documentation would have explanations, samples, pros and cons
on using these cast methods.
Will recreating the lv.msgbox on every literation of the action_cb()
cause any memory issues ?
My original thought was to create lv.msgbox once and pass
lv_msgbox_set_text(msgbox, “My text”), but that is no longer in v8.1.
Then just show/hide the msgbox.
Again thank you for your help and fast response !!

I would recommend using @amirgon’s approach instead of user_data; as I suspected, the latter is used internally. I just wasn’t aware that one could extend an LVGL object with a Python class. That is extremely useful, and much less hacky than using __cast__.

@amirgon The only downside I see to your example is that inside msgbox_cb, self is an instance of MyMsgbox but event.get_current_target() is still just an lv.msgbox. I assume this is a limitation of the bindings.

It’s unlikely, as long as you delete the message box (which you’re doing with lv_msgbox_close). Your only issue could be memory fragmentation from continuous object churn, which I’m pretty sure you would run into just from using Python anyway. As long as your heap is big enough you shouldn’t have problems.

You are right. Luckily we can simply refer to self in that case.
Another option, if you don’t want to extend LVGL objects, is to pass arguments with lambda.

Example: (Online Simulator)

import display_driver
import lvgl as lv

def btn_cb(event, s):
    print(f'btn_cb: {s}')

scr = lv.obj()

btn1 = lv.btn(scr)
btn1.add_event_cb(lambda e: btn_cb(e,'11111'), lv.EVENT.CLICKED, None)
btn1.align(lv.ALIGN.CENTER, 0, -30)
label1 = lv.label(btn1)
label1.set_text('Btn1')

btn2 = lv.btn(scr)
btn2.add_event_cb(lambda e: btn_cb(e,'22222'), lv.EVENT.CLICKED, None)
btn2.align(lv.ALIGN.CENTER, 0, +30)
label1 = lv.label(btn2)
label1.set_text('Btn2')

lv.scr_load(scr)