Kivy:获取小部件ID并通过唯一属性访问小部件

19

我是Kivy的新手,这里有一小段演示代码,展示了我的问题:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder


class KivyGuiApp(App):
    def build(self):
        return root_widget


class MyBox(BoxLayout):
    def print_ids(self, *args):
        print("\nids:")
        for widget in self.walk():
            print("{} -> {}".format(widget, widget.id))
    def print_names(self, *args):
        print("\nnames:")
        for widget in self.walk():
            print("{} -> {}".format(widget, widget.name))



root_widget = Builder.load_string("""
MyBox:
    id: screen_manager
    name: 'screen_manager'

    SimpleLayout:
        id: simple_layout
        name: 'simple_layout'


<SimpleLayout@BoxLayout>:
    id: simple_layout_rule
    name: 'simple_layout_rule'
    Button:
        id: button_ids
        name: 'button_ids'
        text: 'print ids to console'
        on_release: app.root.print_ids()
    Button:
        id: button_names
        name: 'button_names'
        text: 'print names to console'
        on_release: app.root.print_names()
""")


if __name__ == '__main__':
    KivyGuiApp().run()
当您运行代码时,将会有两个按钮:
  • 第一个按钮用于遍历所有小部件并打印它们的名称(按预期工作 - 对于每个小部件返回“name”),
  • 第二个按钮也会遍历所有小部件,但不是打印名称,而是打印其id(它不起作用 - 对于每个id都返回None)。
我的问题是:
  1. 难道'id'不是像'name'一样的属性吗?
  2. 我如何从Python侧访问每个小部件的id?
奖励问题:
  1. 我能否通过其id“全局”访问小部件(假设所有id均唯一)?通过“全局”,我的意思是例如(在上面的代码中)从'MyBox:'小部件的id访问而无需引用父子关系,而只是通过ids(或者可能是任何其他对于每个小部件都是唯一的属性)。我正在考虑创建一个字典{id:widget object},其中包含所有窗口小部件以供轻松访问,除非我不知道其他方法? 强调一下 - 我试图避免参考子父方式(当您想要稍后更改小部件树时这种方式相当混乱),我想在.kv语言中生成小部件。 那么最好的方法是什么?
编辑:
这是我能想到的通过唯一“全局”id引用小部件的最简单方法。
首先,我创建了一个将由我的应用程序类继承的类:
class KivyWidgetInterface():
    ''' interface for  global widget access '''
    global_widgets = {}
    def register_widget(self, widget_object):
        ''' registers widget only if it has unique gid '''
        if widget_object.gid not in self.global_widgets:
            self.global_widgets[widget_object.gid] = widget_object

    def get_widget(self, widget_gid):
        ''' returns widget if it is registered '''
        if widget_gid in self.global_widgets:
            return self.global_widgets[widget_gid]
        else:
            return None

因此,只有当小部件具有gid(小部件类变量)并且是唯一的时,才会注册该小部件。这样,我可以仅在此字典中存储关键小部件。而且,从.kv和python两侧都很容易访问。

现在我创建gid变量并在.kv中将它们注册到字典中:

<PickDirectory>:
    gid: 'pick_directory'
    on_pos: app.register_widget(self)
    on_selection: app.get_widget('filelist').some_func()
<FileListView>:
    gid: 'filelist'
    on_pos: app.register_widget(self)   
    Button:
        name: 'not important'
    Button:
        gid: 'tab_browse_button1'
        on_pos: app.register_widget(self)
我困扰的是,我将我的部件通过'on_pos'事件注册到这个“全局”字典中...我真的不喜欢这样做,但我找不到任何可靠的方法在部件初始化后调用注册方法(当部件被定位并且后来很少调用时,on_pos在init阶段之后立即调用,因此...似乎是以我对kivy api、用.kv语言初始化部件的顺序等知识最不麻烦的方式。如果有更好的方法,我将非常感激任何指针)。
无论如何,通过这种方式,我可以轻松地将任何事件绑定到任何类中的任何方法中,直接从.kv文件中进行。
需要记住的一件事是gid(全局id)需要在全局范围内唯一,但我并不觉得这比在本地保持id唯一更令人不安(对我来说可能同样或甚至更加困惑)。正如我所说-我希望以不同的方式注册部件,但我找不到任何其他可靠的方法来做到这一点(而我不认为Clock在这方面是可靠的)。
3个回答

8
实际上并不是这样的。你的widgets中的name是一个变量,而id只是一个widget的引用,根据文档是一个weakref。也许Python文档可以帮助你理解它的工作原理。你所做的是打印id,而不是widget内部的一个名为"id"的变量。
Kivy文档中解释了,在kv被解析后,ids会被收集到ObservableDict中。id就像Python字典中的一个键id:Widget,但只有通过字典(ids)访问时才有效。我认为kv解析器只是将所有的id都放入字典中,并且只与它创建的字典一起工作。
Button:
    id: test
    text: 'self.id'
#or
Button:
    id: 'test'
    text: 'self.id'

即使它像字符串一样编写,也不会改变任何内容。因此,我期望解析器的行为如下:抓取在id:之后的任何完整单词,转换为字符串,附加到ids字典<id_string>:Widget_weakref,在您的.kv文件中忘记id或者如果再次使用.kv工作,则忽略它。因此,当直接调用id(而不是类似于d[key]的字典)时,它的行为就像一个空/ None变量。我希望我是正确的。


要回答第二个和第三个问题:

如果您指的是直接通过MyBox中的id访问小部件,例如SimpleLayout,那么是的。

Python类:

self.ids.simple_layout

kv MyBox 规则:

MyBox:
    id: screen_manager
    name: 'screen_manager'
    BoxLayout:
        Label:
            id: my_label
            text: 'test'
        Button:
            text: 'button'
            on_release: self.text = root.ids.my_label.text

然而,想要以类似Python全局变量的方式访问所有小部件的ID是不可能的。你需要先访问类/小部件,然后再访问它的ids字典。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - kilbee
这取决于你是否需要这样做或者它只会占用更多的空间,好坏自有评价。此外,它的性能编码好坏也很重要。但是无论如何,使用这种方式不会显著增加.apk文件大小。如果您提供一个简单且“简短”的示例,可能会帮助未来的某个人。但是存储所有小部件可能会成为一种不良习惯,还可能出现某些安全问题。 - Peter Badida
我担心这样做会占用太多资源,或者有更好的方法可以通过单个调用完成。此外,人们似乎并不在意缺少这样的功能,这让我想到我的做法可能不太实用(即使在我看来,这是在Kivy中设计GUI最简单/最快的方法)。所以我想我确实会编辑我的帖子,并提供我的解决方案,以便人们指出其中的任何问题。 - kilbee
我认为人们可能会为不同的小部件使用相同的ID,这将与将其存储在一个地方一起成为问题。Python-人们通常不喜欢“全局变量”,尽管有时会使用。这对我来说就像是一个“全局变量”。此外,现在它不限制用户拥有相同的名称,以不同的方式操作每个小部件等等。优点是小部件内的ids字典仅包含其子ids,如果您只有单个字典,则不会出现这种情况。但是,现在您可以单独使用ids或像您所做的那样将它们连接在一起,这可能是最好的方法。 - Peter Badida
我的想法正是如此——在两个不同的小部件树中重复使用ID可能比仅使用唯一ID更加混乱。无论如何,我已经用我的解决方案编辑了帖子,我认为它实际上结合了两个世界的优点。正如帖子中指出的那样,这也存在一些问题。 - kilbee

4
“id”不就像“name”一样的属性吗?
不是的,ids是kv语言中特殊的语法,或者通过python规则的根部件(self.ids.idname)访问。确实有一个id属性,但我不确定它是否真正被使用,它似乎主要存在于传统原因。我们正在考虑将其删除。
如何从python端访问每个小部件的id?
通过根部件的ids属性。例如,在MyBox方法中,您可以编写self.ids.simple_layout来获取SimpleLayout。
我想创建一个字典{id:widget object},以便轻松访问所有小部件
这不是通用的,因为多个小部件可以具有相同的id。这就是为什么它们是规则本地的,并通过规则的根部件进行访问的原因。
您可以自己构造这样的列表,无论是考虑重复还是知道您不会创建它们。

谢谢,我最终得到了一个可以轻松访问的字典,这解决了我的问题。您对在字典中保留引用有什么看法?这是不好的设计吗?您会推荐更好的方法吗? - kilbee

3

最近我也在思考一个类似的问题:如何访问另一个小部件中的属性。我已经找到了答案,并在这里发布,因为我认为它不是很直观。

这段代码的思路很简单:当点击按钮时,它会更改另一个类的子小部件中的属性。

通常情况下,在同一小部件树中调用很容易,可以通过简单的self.ids.simple_layout(或任何带有appselfroot的变体)来调用。

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button

kv = """
<Test>:
    Button:
        text: "Access label"
        on_press: root.access_label()
    MyWidget:
        id: my_widget


<MyWidget>
    Label:
        id: my_label
        text: "not yet accessed"
"""

Builder.load_string(kv)
class MyWidget(BoxLayout):
    pass

class Test(BoxLayout):
   def access_label(self):
        self.ids.my_widget.ids.my_label.text = 'Accessed!'

class TestApp(App):
    def build(self):
        return Test()

if __name__ == '__main__':
    TestApp().run()

复杂的部分在于self.ids.my_widget.ids.my_label.text,并且id my_widget必须在根widget中而不是<My_widget>定义中。
我自己也不太确定是否完全理解了,但从我的理解来看,似乎当使用另一个类定义另一个widget时,它会创建另一棵树,这意味着:
  • id必须在主树中分配
  • 两个树没有链接,当访问属性或函数时必须使用两次ids
如果我错了,请纠正我。

build() 方法返回 Test(),它是被 <Test> 规则覆盖的根部件,该规则添加了一个 Button 和一个 MyWidget 部件。MyWidget<MyWidget> 规则覆盖,该规则添加了一个 Label。现在集合的 ID 已经嵌套,因此需要额外的 .ids 来访问修改后的 <MyWidget> ID 树。现在你需要从根部件开始,通过 MyWidget: 和它的 ID 来到达 my_label.text。你只有一个根部件 Test:,但它已经被修改以包含一个简单的 Button: 和一个复杂的 MyWidget: 部件,因此路径变得复杂。 - Android Control
这可能也有助于澄清:https://kivy.org/doc/stable/api-kivy.uix.widget.html#kivy.uix.widget.Widget.ids - Android Control
另外,你可以直接使用Kivy属性,并将其链接到一个id:,如此处所述:https://stackoverflow.com/a/47445226/1506858 - Android Control

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接