把数据响应机制引入python,所有事件驱动的界面库都有了新玩法

我是数据外星人 2024-02-20 02:05:08

前言

python 中的各种界面库,大部分都是基于事件驱动。做界面一般困扰大部分人的,无非几个关键点:

布局关联状态处理

正如上一节关于 nicegui 的上手介绍,如同大家的感受,我也觉得写起来麻烦。实际上所有基于事件驱动的界面库都差不多。

但今天,我们将尝试引入目前 web 前端流行的数据响应式机制,解决 "关联状态处理" 的难题。

为了证明数据响应式与具体界面框架无关,我们直接对同一个需求,同时使用三种界面框架解决(tkinter、flet、nicegui)。

之所以选用它们,只是因为它们安装容易。本节内容同样适合其他事件驱动的界面库,比如 pyqt(pyside)

本节围绕 flet 做核心思路讲解,但最终你会发现,它们最终的代码如此相似!

先看需求效果:

非常简单的需求,输入框输入文字,点击"添加"按钮,把输入文字添加到下方列表框中。点击"撤销"按钮。把列表框最后一项填回去输入框。

但是,还有几个附加状态需求:

为了避免记录空输入,输入框没有内容时,"添加"按钮不可用同理,列表框没有记录时,"撤销"按钮不可用为了避免多次记录重复记录,当列表框最后一项与当前输入框内容一样时,"添加" 与 "撤销" 按钮都不可用

今天需要安装这些库 shell pip install flet nicegui signe

事件驱动的弊端

现在,先使用普通的方式尝试解决需求。

创建文件 flet_normal.py :

为了避免代码的干扰,我们忽略布局行6:输入框行8-9:"添加记录" 与 "撤销" 两个按钮行11:记录列表

今天重点不是 flet 框架的教学,不必要的细节不深入讲解。

为了可以修改代码后,画面自动刷新(所谓的热更新),在当前目录位置打开命令行,执行: shell flet run flet_normal.py -d

注意,此时命令行处于占用状态

此时会出来画面:

实现"添加"按钮点击,把输入框内容加入下方的列表框:

行12:为按钮的点击事件绑定我们自定义的函数由于函数里面的代码是点击时才被执行,所以里面可以用上外部定义的控件的变量

现在的问题:

输入框没有内容,就不要让用户点击"添加"按钮当下方列表框最后一笔记录与当前输入框内容一样,也不能点击"添加"按钮

这些都与输入框内容有关系,自然就想要输入框的内容改变事件:

行22:绑定输入框事件行6-20:里面的代码不是重点,看注释应该能明白,逻辑是可以的

现在你打开界面,发现的第一个问题是,按钮一开始就可以点击了。 哦,对了,因为上面写的一大段逻辑,只有在输入框内容改变的时候才会触发。

没办法,只能一开始就设置按钮不可用:

这次你信心满满,现实却打脸:

现在输入内容后,或把输入框内容清空,按钮状态都能正确转换但是,当点击按钮,内容被记录到下方历史列表框后,按钮怎么仍然可以用?

你反复查看之前的逻辑,完全正确!为什么就行不通? 其实还是之前的问题,那段逻辑只有文本框内容改变,才会触发。

我知道肯定有"大神"会说:"你应该把那段逻辑抽出来,分别在输入框事件和按钮事件中调用"

如果此时加上一些需求:

新增一个勾选框,控制按钮的可用状态?"撤销" 按钮点击后,不允许立刻点击 "新增" 按钮?

此时你会发现,越来越多的组件事件中调用各种状态函数,逻辑乱窜。

到这里,我们可以看出来,基于组件事件驱动的弊端。这里的关键原因是,组件事件与所控制的状态,颗粒度不一致。

按钮是否可用状态,只是一个组件上的一个属性值,但我们却要用多个组件的事件影响它。

接下来,我就直接尝试基于数据的响应式(事件),看看效果如何。

数据事件

今天我们说的数据响应式,是基于 signe 包实现。但如果我直接使用它的函数,会显得代码繁琐。所以我特意为 flet 包装了一层。

文件 flet_utils.py 你需要找我拿。

今天介绍的只是其中一种玩法,后续我会弄出来其他风格的编码方式。比如类似 streamlit 或 pysimaplegui 的流程风格

一开始,我们把之前代码中不需要的部分去掉:

是的,不再需要输入框事件

首先定义基本响应式数据:

行5:使用 ref 函数,里面填一个空字符串。返回的就是一个响应式数据对象行6:需求中,有一个历史输入记录的列表,同样道理,创建响应式数据行9-10:是演示的用法,使用 响应式对象 .value 获取值,用普通复制的方式赋值给 value属性

此时,我们可以定义"添加"按钮的禁用状态的响应式数据:

行11:定义函数,逻辑照搬之前写好的逻辑即可行10:为自定义函数打上装饰器 ref_computed行13-15:注意,里面我们只需要使用之前定义的响应式数据即可

函数 get_add_btn_disabled 也是响应式对象,也就是一样可以 get_add_btn_disabled.value 获取它的值

这里好像与之前没什么特别。 神奇的是,由于 get_add_btn_disabled 函数里面使用了 ref_input 与 ref_historys 这两个响应式对象的值。所以,函数会自动绑定它们,每当两个响应式对象的值被修改,函数也会自动触发。

也就是说,它能够自动捕获使用到的响应式数据,并自动让它们产生关联

如果你用过前端的 vue ,那么应该很熟悉这种套路

现在只是定义了数据,接下来可以给这些响应式对象绑定到具体的组件里面。

行39:绑定输入框行40:绑定按钮的禁用状态行41:绑定历史记录列表行27-28:现在"添加"按钮的逻辑,是直接对数据做处理,而不是原来那样,写一大堆组件的处理逻辑。行28:这句看起来很奇怪。当响应式数据是对象的时候(比如是列表,字典,自定义对象),就需要明确赋值,通知系统需要更新。(其实有方法可以省掉)为什么绑定组件的代码要放到最下面?大家注意此时的按钮定义代码(行30),我们没有设置按钮禁用(disabled=False)。但程序启动,按钮是禁用的。因为在绑定状态的时候(行40) ,就已经计算并更新了按钮的状态。但 flet 的机制不允许 page 加载之前就改变组件状态,所以只能放到下方

现在运行看看效果:

你会惊喜地发现,不仅仅我们之前做了一半的需求都搞定了,并且下方的历史列表框也能正常工作!

之后的事情就很简单,一样的套路,定义撤销按钮的状态,绑定一下,就可以。详细代码可以看源码。

所有界面库都能用

接下来,我们快速看看,如果用一样的方式,使用 nicegui做一样的需求,代码是怎么样的:

这是响应数据定义地方,你没看错,与之前的 flet 是一模一样。因为这些地方与具体的界面库没有任何关系。

然后就是界面组件定义和绑定的代码:

是不是几乎一模一样

本期源码里面还有 tkinter 的实现,也是一样的流程。

我知道,这代码还不够简单,因为有些小伙伴不需要处理这么复杂的交互状态,只是希望有个简单界面,控制自己写的简单的程序功能。

后续我会继续用响应式机制,打造各种"傻瓜式"界面流程。类似 streamlit 那样"蹩脚"的运行方式,也可以弄出来。下期再见。

如果你喜欢这样的教程,请一键三连,评论区告诉我。不喜欢就直接划走即可

0 阅读:0

我是数据外星人

简介:感谢大家的关注