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

Posted

技术标签:

【中文标题】Kivy:获取小部件 ID 并通过唯一属性访问小部件【英文标题】:Kivy: Get widgets ids and accessing widgets by unique property 【发布时间】:2016-06-17 23:39:35 【问题描述】:

我是 Kivy 的新手,我有一个小演示 sn-p 来演示我的问题:

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()

所以当你运行代码时会有两个按钮:

首先遍历所有小部件并打印它们的名称(按预期工作 - 为每个小部件返回“名称”), 第二个按钮也可以遍历所有小部件,但不是名称而是打印它们的 id(这不起作用 - 每个 id 都返回 None)。

我的问题是:

    'id' 不是和'name' 一样的属性吗? 如何从 python 端访问每个小部件的 id?

额外问题:

    我可以通过它的 id “全局”访问一个小部件(假设所有 id 都是唯一的)吗? “全局”是指例如从“MyBox:”小部件访问(在上面的代码中)id,而不引用父子,而只是通过 id(或者可能是每个小部件唯一的任何其他属性)。我正在考虑为所有小部件创建一个字典 id : widget object 以便于访问,除非有另一种我不知道的方式? 强调一下 - 我试图避免通过子父方式引用(当您以后想要更改小部件树时,这会很混乱)并且我想用 .kv 语言生成小部件。 那么最好的方法是什么?

编辑:

所以这是我能想到的通过唯一“全局”ID 引用小部件的最简单方法。

首先我创建了一个类,它将被我的 App 类继承:

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 对于这些事情是可靠的)。

【问题讨论】:

【参考方案1】:

实际上,没有。 name 在您的小部件中是一个变量,id 只是一个小部件引用,weakref 根据文档。也许python docs 会帮助您了解它是如何工作的。您所做的是打印id,而不是小部件内的变量“id”。

kivy docs 中解释了kv 被解析后,id 被收集到 ObservableDict 中。 id 像 python dict 键 id:Widget 一样工作,但只有通过字典 (ids) 访问。我认为kv parser 只是将所有 id 放入 dict 并且仅适用于它创建的 dict。

Button:
    id: test
    text: 'self.id'
#or
Button:
    id: 'test'
    text: 'self.id'

即使它写成字符串,也没有任何变化。所以我希望解析器的行为是这样的:抓取id:之后的任何整个单词,变成一个字符串,附加到ids字典&lt;id_string&gt;:Widget_weakref,忘记id在你的.kv中,或者只是忽略它,如果它再次与.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 字典

【讨论】:

谢谢,这终于为我清除了一切并回答了我的问题。因此,我创建了自己的类,其中包含自定义变量的 dict(用作 dict 键中的 id)和对象(小部件)作为值。我现在可以通过简单的参考从我想要的任何地方轻松访问任何小部件,而不必担心 gui 结构,当然假设键(自定义 ID)是唯一的。它可以工作并达到目的,但问题是 - 以这种方式保留这样的 dict 和访问数据是否可以?还是设计不好? 好吧,是好是坏由你说,最好说..如果你真的需要这样的东西,或者它只是需要更多的空间。此外,这取决于它为性能而编码的好坏。但是,您肯定不会让.apk 变得更大。也许在您的问题下提供简单且 short 的示例会很好,因为这种存储可以在将来帮助某人。也许它会成为一个坏习惯(存储所有小部件)或者可能会出现一些安全问题。 我担心它会占用太多资源,或者有一个更好的方法可以通过单个呼叫来做到这一点,而且人们似乎也不会因为缺乏这样的功能而烦恼,所以它让我想到我的做事方式可能不实用(即使在我看来这是在 kivy 中设计 gui 的最简单/最快的方式)。所以我想我确实会用我的解决方案编辑我的帖子,以便人们指出它的任何问题。 我认为人们可能对不同的小部件具有相同的 id,如果将其存储在一个地方可能会成为问题。 Python 人通常不喜欢globals,尽管有时喜欢。这对我来说就像global。此外,现在它没有限制用户使用相同的名称,对每个小部件进行不同的操作等等。优点是小部件内的 ids dict 仅包含其子 id,如果您只有单个 dict,则不会有这样的东西。但是,现在您可以单独使用 id,也可以像以前那样将它们组合在一起,这可能是最好的方法。 我的想法正是如此——在两个不同的小部件树中重复的 id 可能比只有唯一的 id 更令人困惑。无论如何,我用我的解决方案编辑了这篇文章,它实际上加入了我认为最好的两个世界。正如帖子中指出的那样,这也存在一些问题。【参考方案2】:

'id' 不是和'name' 一样的属性吗?

不,id 是一种特殊语法,仅存在于 kv 语言中,或者可以通过 python (self.ids.idname) 中规则的根小部件访问。 is 有一个 id 属性,但我不确定它实际上是否在任何地方使用,它似乎主要是出于遗留原因而存在。我想我们正在考虑删除它。

如何从 python 端访问每个小部件的 id?

通过根小部件的ids 属性。例如,在 MyBox 方法中,您可以编写 self.ids.simple_layout 来获取 SimpleLayout。

我正在考虑为所有小部件创建一个字典 id : widget object 以便于访问

这通常是不可能的,因为多个小部件可以具有相同的 id。这就是为什么它们是规则本地的并通过规则的根小部件访问。

如果您愿意,您可以自己构建这样的列表,或者考虑重复,或者知道您不会创建它们。

【讨论】:

谢谢,我最终得到了可以轻松访问的 dict,因此这解决了我的问题。您对在 dict 中保留引用有何看法?是不是设计不好,会推荐更好的吗?【参考方案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,而my_widget id 必须在根小部件中,而不是在&lt;My_widget&gt; 定义中。

我不确定我是否完全了解自己,但据我了解,当用另一个类定义另一个小部件时,它会创建另一个树,这意味着:

id 必须在主树中分配 两棵树没有链接,访问属性或函数时,一棵树必须使用两次ids

如有错误请指正

【讨论】:

以上是关于Kivy:获取小部件 ID 并通过唯一属性访问小部件的主要内容,如果未能解决你的问题,请参考以下文章

用python读/写kivy小部件属性

在 TabbedPanel 中获取小部件/项目 ID [关闭]

如何获取在kivy中定义在屏幕小部件内的滑块的值

Kivy:在 python 代码中设置小部件禁用属性

引用 Kivy 中动态创建的小部件的 id

在子小部件中,如何在 kivy 中获取父小部件的实例