ceccopierangiolieugenio / pyTermTk

Python Terminal Toolkit - a Spiced Up Cross Compatible TUI Library 🌶️
https://ceccopierangiolieugenio.github.io/pyTermTk-Docs/
MIT License
598 stars 23 forks source link

Question: How can assign 2 actions to a single CheckBox.stateChanged event? #145

Closed castben closed 1 year ago

castben commented 1 year ago

Hi, I'm trying to "activate" textfield entries depending on a stateChanged from a checkbox. if state is true they should be visible, if state is false, they should be disabled or hidden.

This "forms" are being build programmatically. Here is bit of code that build this form, and create a "dependency" for fields that need to be hidden/disabled:

        for each_node_rq in lab_requirement.get_additional_requirements(node_type):
            if each_node_rq in self.lab_user_setup:
                for each_setup in lab_requirement.get_additional_requirements(
                        node_type, each_node_rq, self.lab_user_setup[each_node_rq]):

                    label, default_setting = LabInfra.setup_required_settings(each_setup, defaults)
                    var_action = LabInfra.check_variable_rules(label)
                    if var_action:
                        if var_action['rule-action'] == 'hide':
                            var_action['widget'].stateChanged.connect(
                                lambda x: default_setting.setVisible(x == ttk.TTkK.Checked))
                    tui_node_form.add_widget(f"{label}", default_setting, group=group_name)

Actual code that "creates" this dependency is:

                    if var_action:
                        if var_action['rule-action'] == 'hide':
                            var_action['widget'].stateChanged.connect(
                                lambda x: default_setting.setVisible(x == ttk.TTkK.Checked))

I know this may look bit complicated; this is in a for-loop which create a form on the fly(reading configurations setup) it looks like this:

image

last 2 fields:

USER_RPC
PASSWORD_RPC

Will be enabled only if ACTIVATE_RPC is checked-on. var_action['widget'] contains actual checkbox widget default_setting hold actual widget that will be disabled/hiden.

At the moment the code that generates this form is only "hiding" one of the fields when I uncheck ACTIVATE_RPC so it looks like this: image

It is like is taking only last event (I mean second event overwrite first one that was created)

I know I can do this diferently, like put all widgets I want to hide in a function and then call that funtion... but this will mean I need to modify the actual code that creates this form on the fly.

If I can't assign more than one action to a single event, then I will approach using other method, but just wanted to ask you first if I'm doing it in wrong way.

Thank you a lot for your time!

Regards, Larry

ceccopierangiolieugenio commented 1 year ago

I think that the issue is in the lambda. once executed it use the last "default_setting"

you should be able to workaround it forcing the variable scope assigning it to a default "y" variable with:

                            var_action['widget'].stateChanged.connect(
                                lambda x,  y=default_setting: y.setVisible(x == ttk.TTkK.Checked))

I made an example here where I reproduce the issue and 2 proposed solutions; https://gist.github.com/ceccopierangiolieugenio/a46530db30cd728105046842f0011111#file-issue_145_loop_callbacks-py

you can try it online here

anyway, I just realised that there is a signal missing from the checkbox, I still have to implement the "toggled(bool)" signal that would have been extremely handy in this situation without the need of lambda workarounds you could have just used:

                            var_action['widget'].toggled.connect(default_setting.setVisible)

I will include it in the next release

castben commented 1 year ago

Thank you I will check this out now and will report back to you shortly!

castben commented 1 year ago

Well, I'm now reporting back,

I've used your 1st proposed workaround/fix:

                            var_action['widget'].stateChanged.connect(
                                lambda x,  y=default_setting: y.setVisible(x == ttk.TTkK.Checked))

And it worked like a charm!,

This fixed my issue. I think you can close this, unless you want it to track and add the additional feature(to avoid lambda usage in this specific case)

Again thank you very much for your always welcome help!

ceccopierangiolieugenio commented 1 year ago

I just noticed that the clicked event return a bool as it is suposed to be used by the toggled event:

you can find the updated solution here

https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?fileUri=https://gist.githubusercontent.com/ceccopierangiolieugenio/a46530db30cd728105046842f0011111/raw/3b2657e8c702deeb5ce0a20931a3b476e9bd7a7c/issue_145_loop_callbacks.py

so this should work as well;

                            var_action['widget'].clicked.connect(default_setting.setVisible)