在之前的很长一段时间,一只有小伙伴和开发者问我,“TAPython是否能够动态添加控件”。在之前的TAPython中,我们可以在打开工具时重新创建界面,或者使用ExternalJSON
功能实现“一定程度”的动态创建。因此我对“为TAPython通过代码添加控件”这件事,并没有那么积极,其实在内心我对这个功能是有一些担忧的。
担忧¶
首先,TAPython的目标用户,不仅有资深程序员,也有美术和策划。
对于程序员来说,通过Python代码实现界面和逻辑是非常自然的事情,单独分离的JSON文件对于他们来说,甚至可以是多余的。但对于非程序用户来说,用分离的JSON文件来定义节目会极大程度的简化工具的复杂程度,界面JSON中的每一行和界面一一对应,界面外观和点击逻辑在JSON文件中就如同直接写在纸面上一样直接;而“通过代码来创建界面”就又给他们添加了一个“界面创建”的技术负担。 在我看来,如果TAPython的目标是将做工具这件事变得尽可能的简单,那么将“走出第一步”所需要知道的上下文降低到最小程度,无疑是最重要的。
现在,TAPython已经相对比较成熟,使用JSON来描述界面已经成为一个底层习惯。那么是时候为TAPython添加动态添加,移除控件的功能了。
Examples¶
下图的Gif示例和下面的API,都可以在 https://github.com/cgerchenhp/TAPython_ButtonDivision_Example 中找到。
Dynamic Slate Creation¶
下面API中使用的JSON内容设置控件或Slot中的内容。
TIP
JSON中的内容为以'{'
,'}'
开始和结束,内容为一个完整的Slate控件组件的内容
下面提到的API和例子,在DynamicSlateExamples中都有对应的控件内容。
设置单个控件的内容¶
Slate中有一些控件有且只能有一个子控件,在这些控件的JSON文件中,我们会通过Content
字段来设置其中的子空间内容。
同样,通过chameleon_data_instance.set_content_from_json
, 我们可以动态的为这些控件添加子控件。
支持的控件有:¶
- SBox
- SBorder
- SCheckBox
- SBox
- SButton
例如,我们有一个SBorder
控件:
DynamicSlateExamples.json
"SBorder": {
"Aka": "a_named_sborder",
"BorderImage": {
"Style": "FEditorStyle",
"Brush": "ToolPanel.DarkGroupBorder"
}
}
通过self.data.set_content_from_json(aka_name='a_named_sborder', json_content='{ "SButton": { "Text": "A New Button" } }')
可以为这个SBorder添加一个按钮作为子控件。其中变量json_content
中的内容为描述子控件内容的字符串内容。其格式与界面JSON文件中的相同。
除了之间编写字符串内容以外,我们也可以json.dumps
将对象dump为字符串格式,通过这种方法,设置控件的属性会更为方便。下面的json_content
中
import json
new_widget = {"SButton": {}}
new_widget["SButton"]["Text"] = "A New Button"
json_content = json.dumps(new_widget)
设置Slots中的内容¶
和上面说的set_content_from_json
类似,我们可以通过append_slot_from_json
和insert_slot_from_json
来动态的添加Slot中的控件。
append_slot_from_json 支持的控件有:¶
- SHorizontalBox
- SVerticalBox
- SScrollBox
- SGridPanel
- SUniformGridPanel
- SUniformWrapPanel
- SOverlay
- SCanvas
- SSplitter
TIP
为SUniformGridPanel
和SUniformGridPanel
添加控件时,需要指定该函数中第三和第四个参数Column
和Row
,(默认均为0)。多次添加会使得控件重叠。
例如,我们通常会通过类似下面的代码在控件的Slot中添加子控件:
#append a new slot to the vertical box
self.data.append_slot_from_json(self.ui_verticalbox, json_content)
# add a new slot to the grid at column 1, row 2
self.data.append_slot_from_json(self.ui_grid, json_content, column=1, row=2)
insert_slot_from_json 支持的控件有:¶
- SHorizontalBox
- SVerticalBox
其中最常用的是SHorizontalBox
和SVerticalBox
,我们可以通过insert_slot_from_json
在指定的Slot位置添加子控件。例如:
# insert a new widget to the vertical box with specified index
self.data.insert_slot_from_json(self.ui_verticalbox, json.dumps(new_widget), index)
TIP
上述例子中的self.ui_widget_name
,均为控件的Aka名称
移除指定的控件¶
我们可以通过remove_widget_at
来移除指定的控件。上述两个API中提到的所有控件:
- SHorizontalBox
- SVerticalBox
- SScrollBox
- SGridPanel
- SUniformGridPanel
- SUniformWrapPanel
- SOverlay
- SCanvas
- SSplitter
- SBox
- SBorder
- SCheckBox
- SBox
- SButton'
例如我们可以通过下面的代码来移除SVerticalBox
控件中的第一个子控件:
self.data.remove_widget_at(self.ui_verticalbox, 0)
当我们的控件时只能有一个子控件的控件时(SBox, SBorder,SCheckBox,SBox,SButton),参数index
应为0,其余值无效。
获取当前控件中所有已用Aka¶
由于我们可以动态创建和添加控件了,因此我们控件的Aka名的来源也不仅限于界面JSON文件中的内容了,也包括python代码添加的控件的Aka名称。
我们可以通过chameleon_data_instance.get_all_akas
来获取当前控件中所有已用的Aka名称。例如:
all_akas = self.data.get_all_akas()
获取指定Aka名的控件所在路径¶
在获取了控件的Aka名之后,我们可以通过chameleon_data_instance.get_widget_path
来获取该控件所在的实际Slate路径。例如下面的函数中,获取和打印了所有控件的Aka名和其所在的界面路径。
def on_button_log_akas(self):
all_akas = self.data.get_all_akas()
widget_paths = [self.data.get_widget_path(aka) for aka in all_akas]
for aka, widget_path in zip(all_akas, widget_paths):
print(f"Aka: {aka: <30} :{widget_path}")
本文提到的内容的实例代码可以在下面的链接中找到:https://github.com/cgerchenhp/TAPython_ButtonDivision_Example
下面是我对动态创建控件的一些理解和建议:
建议¶
要¶
-
创建数量不可预知的控件
例如,我们的控件数量需要按照某个网络服务的返回值的数量动态修改,那么这些控件的动态创建是非常合理和自然的
-
将需要动态创建的组件合并在一起,并发起一次调用
当我们要创建一个包含了100个子控件的
SHorizontalBox
时,应该把包含所有子控件的JOSN代码先在Python端拼接成一段完整的JSON,然后发起1次调用。合并后的创建速度是单独创建的几十倍以上。我们甚至可以简单的认为两者的速度是:“C++的速度和Python的速度之比”。 - 注意控件中的“Aka”必须在每个工具中唯一由于我们的控件是通过代码创建的,这个时候很容易出现多个控件适用同一个“Aka”的情况。这对于工具逻辑来说是致命的。
TIP
注意Output中的黄色警告,特别是“Aka”相关的
不要¶
- 不要将整个工具的界面代码都用Python来创建,除非你是唯一的工具维护者
- 当由于“没有正确导入某个package”,使得工具的代码无法执行,进而连界面都无法显示的时候,无疑是非常沮丧的
- 当其他开发者需要先理解Python中的界面创建逻辑,才知道界面长什么样的时候,无疑更加沮丧
- 当其他开发者修复了你的工具之后,你发现居然连节目也和以前完全不同了,你会非常沮丧