python 中的各种界面库,大部分都是基于事件驱动。做界面一般困扰大部分人的,无非几个关键点:
正如上一节关于 nicegui 的上手介绍,如同大家的感受,我也觉得写起来麻烦。实际上所有基于事件驱动的界面库都差不多。
但今天,我们将尝试引入目前 web 前端流行的数据响应式机制,解决 "关联状态处理" 的难题。
为了证明数据响应式与具体界面框架无关,我们直接对同一个需求,同时使用三种界面框架解决(tkinter、flet、nicegui)。
之所以选用它们,只是因为它们安装容易。本节内容同样适合其他事件驱动的界面库,比如 pyqt(pyside)
本节围绕 flet 做核心思路讲解,但最终你会发现,它们最终的代码如此相似!
先看需求效果:
非常简单的需求,输入框输入文字,点击"添加"按钮,把输入文字添加到下方列表框中。点击"撤销"按钮。把列表框最后一项填回去输入框。
但是,还有几个附加状态需求:
今天需要安装这些库 shell pip install flet nicegui signe
现在,先使用普通的方式尝试解决需求。
创建文件 flet_normal.py
:
今天重点不是 flet 框架的教学,不必要的细节不深入讲解。
为了可以修改代码后,画面自动刷新(所谓的热更新),在当前目录位置打开命令行,执行: shell flet run flet_normal.py -d
此时会出来画面:
实现"添加"按钮点击,把输入框内容加入下方的列表框:
现在的问题:
这些都与输入框内容有关系,自然就想要输入框的内容改变事件:
现在你打开界面,发现的第一个问题是,按钮一开始就可以点击了。哦,对了,因为上面写的一大段逻辑,只有在输入框内容改变的时候才会触发。
没办法,只能一开始就设置按钮不可用:
这次你信心满满,现实却打脸:
你反复查看之前的逻辑,完全正确!为什么就行不通?其实还是之前的问题,那段逻辑只有文本框内容改变,才会触发。
我知道肯定有"大神"会说:"你应该把那段逻辑抽出来,分别在输入框事件和按钮事件中调用"
如果此时加上一些需求:
此时你会发现,越来越多的组件事件中调用各种状态函数,逻辑乱窜。
到这里,我们可以看出来,基于组件事件驱动的弊端。这里的关键原因是,组件事件与所控制的状态,颗粒度不一致。
按钮是否可用状态,只是一个组件上的一个属性值,但我们却要用多个组件的事件影响它。
接下来,我就直接尝试基于数据的响应式(事件),看看效果如何。
今天我们说的数据响应式,是基于 signe
包实现。但如果我直接使用它的函数,会显得代码繁琐。所以我特意为 flet 包装了一层。
文件 flet_utils.py
你需要找我拿。
今天介绍的只是其中一种玩法,后续我会弄出来其他风格的编码方式。比如类似 streamlit 或 pysimaplegui 的流程风格
一开始,我们把之前代码中不需要的部分去掉:
首先定义基本响应式数据:
ref
函数,里面填一个空字符串。返回的就是一个响应式数据对象响应式对象
.value 获取值,用普通复制的方式赋值给 value属性此时,我们可以定义"添加"按钮的禁用状态的响应式数据:
ref_computed
函数 get_add_btn_disabled
也是响应式对象,也就是一样可以 get_add_btn_disabled.value
获取它的值
这里好像与之前没什么特别。 神奇的是,由于 get_add_btn_disabled
函数里面使用了 ref_input
与 ref_historys
这两个响应式对象的值。所以,函数会自动绑定它们,每当两个响应式对象的值被修改,函数也会自动触发。
也就是说,它能够自动捕获使用到的响应式数据,并自动让它们产生关联
如果你用过前端的 vue ,那么应该很熟悉这种套路
现在只是定义了数据,接下来可以给这些响应式对象绑定到具体的组件里面。
现在运行看看效果:
你会惊喜地发现,不仅仅我们之前做了一半的需求都搞定了,并且下方的历史列表框也能正常工作!
之后的事情就很简单,一样的套路,定义撤销按钮的状态,绑定一下,就可以。详细代码可以看源码。
接下来,我们快速看看,如果用一样的方式,使用 nicegui做一样的需求,代码是怎么样的:
这是响应数据定义地方,你没看错,与之前的 flet 是一模一样。因为这些地方与具体的界面库没有任何关系。
然后就是界面组件定义和绑定的代码:
是不是几乎一模一样
本期源码里面还有 tkinter 的实现,也是一样的流程。
我知道,这代码还不够简单,因为有些小伙伴不需要处理这么复杂的交互状态,只是希望有个简单界面,控制自己写的简单的程序功能。
后续我会继续用响应式机制,打造各种"傻瓜式"界面流程。类似 streamlit 那样"蹩脚"的运行方式,也可以弄出来。下期再见。