- 跨平台
- UI组件
- 桌面应用
【免费下载链接】toga
A Python native, OS native GUI toolkit.
导读
本文以 Toga 官方示例 examples/date_and_time 为蓝本,系统讲解 Toga 中日期输入控件DateInput与时间输入控件TimeInput的完整用法:包括参数语义、min/max边界约束、value的类型转换与自动裁剪、on_change回调的挂载方式,以及如何在桌面端(Windows/macOS)与 Android 端一键运行。读完本文,你将能够独立写出带日期/时间范围限制、实时回调校验的表单控件代码,并理解其底层实现原理。
1. 示例概览:这个应用在演示什么
date_and_time是 Toga 仓库中用于测试日期与时间输入控件的示例应用。它在同一个窗口中展示了 8 行输入控件,覆盖了两种控件与三种参数组合的全部排列:
| 控件 | 场景 | 演示要点 |
|---|---|---|
DateInput | 无限制(Any date) | 默认取当天日期 |
DateInput | 仅设min | 限制可选最早日期 |
DateInput | 仅设max | 限制可选最晚日期 |
DateInput | 同时设min+max | 完整区间约束 |
TimeInput | 无限制(Any time) | 默认取当前时间 |
TimeInput | 仅设min | 限制可选最早时间 |
TimeInput | 仅设max | 限制可选最晚时间 |
TimeInput | 同时设min+max | 完整区间约束 |
示例源码位于 examples/date_and_time/date_and_time/app.py,共约 140 行,逻辑非常紧凑:每行输入控件都配有右侧固定宽度的标签,用户在控件中修改日期/时间时,应用会实时把当前值以格式化文本打印到控制台。
平台可用性说明:官方 README 明确指出,日期/时间输入控件仅在Android、macOS 和 Windows平台上可用。不过从仓库源码结构看,
gtk、qt、iOS、web等后端也各自提供了dateinput.py/timeinput.py实现(见 gtk/src/toga_gtk/widgets/dateinput.py、qt/src/toga_qt/widgets/timeinput.py 等),具体能力以各平台文档为准。
2. 快速上手:从克隆到运行
官方 README 给出了两种启动方式,下面逐一展开。
2.1 在 Windows / macOS 桌面端运行
$ python -m pip install toga $ python -m date_and_time两条命令分别完成:安装 Toga 核心包;以模块方式启动示例。示例包内 examples/date_and_time/date_and_time/main.py 提供了模块入口:
from date_and_time.app import main if __name__ == "__main__": main().main_loop()main()返回一个DateAndTimeApp实例,随后调用其main_loop()进入事件循环。
2.2 在 Android 上运行
$ python -m pip install briefcase $ briefcase run androidAndroid 端需要借助 Briefcase 工具链打包运行。示例的 examples/date_and_time/pyproject.toml 已经预先配置好了 Briefcase 构建描述:
- 应用标识为
org.beeware.toga.examples,包名date_and_time,版本0.0.1,许可证 BSD-3-Clause; [tool.briefcase.app.date_and_time]段声明了源码目录date_and_time及核心依赖../../travertino、../../core;- 各平台段分别挂载对应后端:macOS 用
cocoa、Linux 用gtk、Windows 用winforms、iOS 用iOS、Android 用android、Web 用web; - Android 段额外指定了 Material 主题
Theme.MaterialComponents.Light.DarkActionBar和com.google.android.material:material:1.12.0依赖,说明 Android 上的日期/时间控件基于 Material 组件实现。
3. 控件构造:一个窗口里的 8 行输入
示例的startup()方法用toga.Box组合所有控件。以日期行为例,每个输入控件与一个宽度 150px、右对齐的标签排成一行:
any_date_box = toga.Box( children=[ toga.Label("Any date:", width=150, text_align=RIGHT), toga.DateInput( id="Any", value=None, on_change=self.changed_date, ), ], direction=ROW, )随后将所有行放入direction=COLUMN的垂直容器中,作为主窗口内容:
self.main_window.content = toga.Box( children=[ any_date_box, min_date_box, max_date_box, min_max_date_box, any_time_box, min_time_box, max_time_box, min_max_time_box, ], direction=COLUMN, ) self.main_window.show()3.1 控件的四个核心参数
从接口定义(core/src/toga/widgets/dateinput.py、core/src/toga/widgets/timeinput.py)可以看出,两个控件的构造参数完全对称:
| 参数 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
id | str \| None | None | 控件唯一标识,回调中可通过widget.id区分是哪个控件 |
style | StyleT \| None | 默认样式 | 可传入 Pack 样式对象 |
value | date/time \| None | None时取当天/当前时间 | 初始显示值 |
min | date/time \| None | None时取全局最小值 | 可选的最早日期/时间(含) |
max | date/time \| None | None时取全局最大值 | 可选的最晚日期/时间(含) |
on_change | 回调函数 | None | 值变化时触发;另支持通过**kwargs传入width等样式属性 |
其中width=150这类额外关键字会直接应用到控件的 Pack 样式中。
3.2 回调:拿到变化后的值
示例为日期和时间各注册了一个回调,用strftime做本地化格式化输出:
def changed_date(self, widget): print(f"{widget.id} is {widget.value.strftime('%A %d %B %Y')}") def changed_time(self, widget): print(f"{widget.id} is {widget.value.strftime('%I:%M:%S %p')}")回调签名统一为handler(widget):Toga 通过 core/src/toga/handlers.py 的wrapped_handler包装用户函数,因此回调只会收到被触发的控件对象,你可以直接读取widget.value并借助widget.id判断是哪个输入框发生了变化。运行示例后,在任一输入框修改值,终端就会打印类似Min is Friday 02 April 2021或Max time is 10:42:00 AM的信息。
4. 边界约束:min / max 的底层行为
这是示例中最有实践价值的部分:min与max不仅限制用户可选范围,还会对value进行自动裁剪(clipping)。
4.1 DateInput 的取值规则
示例展示了四种写法:直接传date对象、传 ISO 格式字符串、或留空。其内部转换逻辑在 core/src/toga/widgets/dateinput.py:
None→date.today()(当天);datetime.datetime→ 提取.date()部分;str→datetime.date.fromisoformat()解析(如"2021-01-01");- 其他类型 → 抛
TypeError("Not a valid date value")。
同时模块顶部定义了全局支持范围(dateinput.py):
MIN_DATE = datetime.date(1800, 1, 1) MAX_DATE = datetime.date(8999, 12, 31)min未指定时默认为1800-01-01,max未指定时默认为8999-12-31(含);min/max超出该范围会抛ValueError(如 "The lowest supported date is 1800-01-01");- 但
value超出范围不会报错,而是被裁剪到最近的合法边界。
4.2 裁剪的三种联动场景
设置value、min、max时存在联动裁剪(见 dateinput.py 与 timeinput.py):
value越界:设置value时,若小于min则被抬升为min,若大于max则被压低为max;- 调大
min:若当前value早于新min,value被裁剪为新min;若max早于新min,max也被一并抬升; - 调小
max:若当前value晚于新max,value被裁剪为新max;若min晚于新max,min也被一并压低。
这些行为均有测试覆盖。例如 core/tests/widgets/test_dateinput.py 的test_value_clipping验证了2010-01-01~2020-01-01区间内,把value设为2005-06-12会被裁剪为2010-01-01;test_timeinput.py 的test_min_clip则验证了当min抬高越过当前value时,value会被同步裁剪并触发一次on_change。
4.3 TimeInput 的特有规则
时间控件的范围边界为00:00:00(min 默认值)到23:59:59(max 默认值),见 timeinput.py)。它的值转换(timeinput.py)与日期控件类似,但有两处差异:
None→datetime.datetime.now().time()(当前时刻);- 返回前统一
replace(microsecond=0),微秒永远归零。
平台差异提示:API 文档(docs/en/reference/api/widgets/timeinput.md)注明,在 Android 和 iOS 上秒也会归零,且
min/max中设置的秒数会被忽略——移动端时间选择器通常只精确到分钟。另外 docs/en/reference/api/widgets/dateinput.md 提到 iOS 上无法定制日期控件的颜色/背景样式(Apple 未提供相关 API)。
5. 示例的四种边界组合写法
直接给出示例中可复制的完整写法(节选自 app.py):
仅设最小值:
toga.DateInput( id="Min", value=None, on_change=self.changed_date, min="2021-01-01", # ISO 字符串也可直接传入 )仅设最大值:
toga.DateInput( id="Max", value=date(2021, 4, 2), on_change=self.changed_date, max=date(2022, 2, 1), )同时设置区间:
toga.DateInput( id="Min-max", value=date(2021, 4, 2), on_change=self.changed_date, min=date(2021, 1, 1), max=date(2022, 2, 1), )时间控件对应写法(时/分/秒):
toga.TimeInput( id="Min-max time", value=time(10, 42), on_change=self.changed_time, min=time(8, 15), max=time(21, 30), )注意示例中min使用了字符串"06:35:00"、max使用了time对象,两种形式等价;value=time(10, 42)表示10:42:00,缺失的秒自动补零。
6. 事件触发的时机验证
一个容易被忽略的细节:构造时即使传入了on_change,回调也不会在创建控件时被调用。核心测试对此有明确断言(test_dateinput.py):
# The change handler isn't invoked at construction. on_change_handler.assert_not_called()而每次通过属性赋值改变value时,回调会且只会被调用一次(即使赋值前后值相同)。wrapped_handler会在值实际写入底层实现后触发事件,测试用例 test_value 中assert_called_once_with(widget)即验证了这一点。
非法值则会在赋值阶段直接抛出异常,测试覆盖了两类情况(test_dateinput.py):
| 非法输入 | 异常类型 | 消息 |
|---|---|---|
123、object()、time对象 | TypeError | Not a valid date value |
"not a date" | ValueError | Invalid isoformat string: 'not a date' |
7. 小结与扩展建议
date_and_time示例虽然只有 140 行,却完整演示了 Toga 日期/时间控件的核心能力:value的类型宽容(date/time/datetime/ISO 字符串/None)、min/max的自动裁剪联动、以及on_change回调的挂载与触发时机。基于它你可以快速扩展出实际业务场景,例如:
- 将回调中的
print替换为表单校验,实时判断所选日期是否在工作日或未来; - 结合
value的字符串解析能力,从后端接口返回的 ISO 字符串直接初始化控件; - 利用
widget.id区分多个输入框,复用同一个回调处理一整组日期/时间字段。
若需深入了解每个参数的完整语义,可继续阅读官方 API 文档 DateInput 参考 与 TimeInput 参考;想验证控件在各种边界条件下的行为,可查看核心测试 test_dateinput.py 与 test_timeinput.py。
- 跨平台
- UI组件
- 桌面应用
【免费下载链接】toga
A Python native, OS native GUI toolkit.
相关推荐
Toga TimeInput 时间选择控件:属性、事件与跨平台实现解析
Toga TimeInput 时间选择控件:属性、事件与跨平台实现解析 TimeInput 是 Toga 中用于让用户选择一天中某一时刻(时、分、秒)的本地化原
跨平台UI组件桌面应用Toga DateInput 日期选择控件:从基础用法到跨平台实现原理
Toga DateInput 日期选择控件:从基础用法到跨平台实现原理 本文是 Toga(A Python native, OS native GUI tool
跨平台UI组件桌面应用ESP32 温控 PID 闭环实战:从原始 ADC 读数到稳定的加热功率输出
ESP32 温控 PID 闭环实战:从原始 ADC 读数到稳定的加热功率输出 设温 25°C,实际冲到 31°C 才慢慢回落到 24°C——这是第一次跑温控闭环
嵌入式物联网驱动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考