PyWebIO icon indicating copy to clipboard operation
PyWebIO copied to clipboard

无法在 onchange 中触发另一个 onchange

Open junyilou opened this issue 3 years ago • 1 comments

问题的背景

希望达成的效果是,用户首先选择一种输入的模式,在不同的输入模式下会有不同的输入函数(例如,对应几种不同的 input_group),并最终完成输入。固然,可以先通过一个 radio 选择模式,然后分别进入不同的 input_groups,但是这需要用户首先点击一次「完成」按钮,期望的效果是尽量减少阻塞从而减少输入所花的时间。 最开始的想法是,选择完模式后,保持当前输入页面然后展示不同的输入函数,但是由于各种输入方式并没有类似于 visible 这样的属性,同时 onchange 不能通过 input_update 进行修改,适应几种截然不同的输入模式会比较困难,故调整了实现的方式: 现在的设计是,当用户选择一种输入模式后,触发第一个 onchange,跳转到一个新的输入体,比如一个 input_group,在这里完成每种输入模式的更详细的输入,如下代码是对这个思路的一个简单的实现

from pywebio.input import *

def submain(mode):
    return input(mode)

def main():
    i = input_group("Main", [
        radio(
            label = "Select…",
            name = "mode",
            options = ["Mode 1", "Mode 2"],
            onchange = lambda m: input_update("command", value = submain(m))
        ),
        input(
            label = " ",
            name = "command",
            value = "",
            readonly = True
        )
    ])
    print(i["command"])

main()

实验证明这样操作是可行的,即,在用户选择完 Mode 1、Mode 2 后,分别可以进入两个不同的输入(在上面的代码中没有什么不同,但很容易通过 if 语句 和 submain() 中的 mode 参数对 Mode 1 和 Mode 2 给出两种不同的输入体),然后将两种输入的值返回回第一个输入的 command 当中,接下来可以继续对 command 进行操作。这里的 command 可以设置为一种能够区分 Mode 1 和 Mode 2 但又能够让后续主程序均兼容的数据形式(例如一个字典)。

实际问题

但是问题就出现在,如果 submain() 当中也有 input_group,且也包含一个 onchange 时,测试效果为 submain() 中的 onchange 可以正常运行,并且 submain() 可以正常返回结果,但是回到主函数中的 onchange 后就会出现错误:

Traceback (most recent call last):
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/input.py", line 731, in input_group
    return input_control(spec, preprocess_funcs=preprocess_funcs,
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/session/__init__.py", line 283, in inner
    return run_as_function(gen)
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/utils.py", line 296, in run_as_function
    res = gen.send(res)
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/io_ctrl.py", line 280, in input_control
    data = yield input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs, onchange_funcs)
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/session/__init__.py", line 283, in inner
    return run_as_function(gen)
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/utils.py", line 296, in run_as_function
    res = gen.send(res)
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/io_ctrl.py", line 343, in input_event_handle
    trigger_onchange(event_data, onchange_funcs)
  File "/opt/homebrew/lib/python3.9/site-packages/pywebio/io_ctrl.py", line 319, in trigger_onchange
    del get_current_session().internal_save['onchange_trigger-' + task_id]
KeyError: 'onchange_trigger-None-4368508096'

如果两个 onchange 都是 input_update 的话(不确定是不是这个条件,如有必要可以贴实际的代码),还可以看到警告

WARNING:pywebio.io_ctrl:Get RuntimeError('`input_update()` can only be called in `onchange` callback.') in onchange function for name:"mode"

简化程序后,发现似乎是因为不能在已经存在一个 onchange 时再触发另一个 onchange,阅读 trigger_onchange 中的代码猜测是否是因为两个 onchange 的 task_id 相同从而操作重复删除?

junyilou avatar Apr 30 '22 16:04 junyilou

补充,在 io_ctrl.py

    try:
        onchange_func(event_data['value'])
    except Exception as e:
        logger.warning('Get %r in onchange function for name:"%s"', e, name)
    finally:
        del get_current_session().internal_save['onchange_trigger-' + task_id]

如果将 finally 块中的删除语句注释(改为 pass),不再出现错误,同时也可以实现希望达成的效果

junyilou avatar May 04 '22 12:05 junyilou