使用 id 访问动态添加的小部件

access dynamically added widget with id

目标
我想创建一个动态添加按钮的小脚本,但仍然允许我通过 root 对特定按钮执行功能。


我的方法
我做了这个脚本。

它能够在顶部动态添加大按钮。
每个按钮在按下时都会稍微改变自己的颜色。

它的底部有两个小按钮。
第一个按钮沿顶部动态添加新的大按钮。
第二个按钮重置顶部第一个大按钮的颜色。

我的代码

#!/usr/bin/env python3
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout

Builder.load_string('''
<RootWidget>:  
    Button:
        text: 'Add'
        size_hint: (None, None)
        size: (40, 40)
        pos: (40, 40)
        group: 'action'
        on_press: root.createNextTarget()
    Button:
        text: 'res'
        size_hint: (None, None)
        size: (40, 40)
        pos: (100, 40)
        group: 'action'
        on_press: root.resetTarget()
''')

class RootWidget(FloatLayout):
    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)
        #note: self.ids isn't populated yet. I guess we can't use it yet.
        self.createNextTarget()

    def resetTarget(self):
        f_target = self.ids['targetbutton0']
        f_target.background_color = (1.0, 1.0, 1.0, 1.0)
        return True

    def countTargets(self):
        return [str(x.__class__.__name__) for x in self.children if x != None].count('TargetButton')

    def createNextTarget(self):
        f_nextButton = TargetButton(id="targetbutton"+str(self.countTargets()),
                               size_hint=(None, None),
                               pos=(80 + (10 + 60) * self.countTargets(), 100),
                               size=(60, 60),
                               background_normal = '',
                               background_color = (1, 1, 1, 1),
                               group = 'target')
        self.add_widget(f_nextButton)
        f_nextButton.bind(on_press=TargetButton.lowerAllRGB)

class TargetButton(Button):
    def __init__(self, **kwargs):
        super(TargetButton, self).__init__(**kwargs)

    def lowerAllRGB(self):
        f_r, f_g, f_b, f_a = self.background_color
        if f_r >= 0.1: f_r = f_r - 0.1
        if f_g >= 0.1: f_g = f_g - 0.1
        if f_b >= 0.1: f_b = f_b - 0.1
        self.background_color = (f_r, f_g, f_b, f_a)
        return True

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

    def on_stop(self):
        print("TestApp.on_stop: finishing", self.root.ids)

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

问题
如果我尝试点击重置按钮(通过 root.ids 访问小部件),我会收到错误消息:KeyError: 'targetbutton0'

找到 a post about a similar problem 后,我认为 root.idsRootWidget.__init__ 期间无法正常工作。
但是当我在 RootWidget.__init__ 完成后使用按钮添加按钮时, TestApp.on_stop() 仍然打印: TestApp.on_stop:整理{}

所以 root.ids 仍然是空的,并且似乎不包含任何动态添加的小部件,尽管我为每个小部件分配了 id 属性。

我想问你的问题

  1. 鉴于我动态添加小部件的方式,使用 root.ids 是否对我的目的毫无价值?
  2. 有没有合适的方法让我通过 id 访问我的小部件?
    我看到 问类似的问题。但是它没有回答我关于动态添加的小部件的问题。

问题 1 - root.ids / self.ids

Given the way I am dynamically adding widgets, is using root.ids just worthless for my purposes?

回答

id 分配给动态添加的小部件不会存储在 self.idsroot.ids 中。因此,您无法使用 self.ids['targetbutton0']self.ids.targetbutton0 访问动态添加的小部件。如果你这样做,你会得到一个 KeyError 因为它在 self.ids 中找不到,它是一个字典类型 属性.

当你的 kv 文件被解析时,Kivy 会收集所有标有 id 的小部件并将它们放入这个 self.ids 字典类型 属性.

注: 这些类型的 id(即 id 分配给动态创建的小部件)已弃用,并将在未来的 Kivy 版本中删除。

[WARNING] Deprecated property "<StringProperty name=id>" of object "<kivy.uix.button.Button object at 0x7feeec0968d0>" has been set, it will be removed in a future version

问题 2

Is there a decent way for me to access my widgets via id?

解决方案

您可以创建自己的字典类型的 ID 列表 属性。

片段

from kivy.properties import DictProperty

class RootWidget(FloatLayout):
    dynamic_ids = DictProperty({})    # declare class attribute, dynamic_ids

    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)
        self.createNextTarget()

    def resetTarget(self):
        f_target = self.dynamic_ids['targetbutton0']
        f_target.background_color = (0.0, 1.0, 1.0, 1.0)    # cyan colour
        return True

    ...

    def createNextTarget(self):
        id = "targetbutton" + str(self.countTargets())
        f_nextButton = TargetButton(id=id,
                               size_hint=(None, None),
                               pos=(80 + (10 + 60) * self.countTargets(), 100),
                               size=(60, 60),
                               background_normal = '',
                               background_color = (1, 1, 1, 1),    # white colour
                               group = 'target')
        self.add_widget(f_nextButton)
        self.dynamic_ids[id] = f_nextButton
        f_nextButton.bind(on_press=TargetButton.lowerAllRGB)

输出

当您使用 id='value' 从 python 主文件动态创建 kivy 小部件时,Kivy 2.0 会抛出错误。但是通过使用弱引用,您可以获得以下成功。

from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
import weakref


class MyLayout(Widget):
    def use_weakref_id_to_replace_text(self, *args):
        self.ids.AddUserTextBox.text = "shotta"
        print("released and renamed the text to shotta")

    def add_textinpt_with_weak_ref_dyn_id(self, *args):
        print('Pressed and added text input box')

        textinput = TextInput(pos=(380,380),text='123')
        
        self.add_widget(textinput)

        # We'll use a weak ref to add our dynamic id 
        self.ids['AddUserTextBox'] = weakref.ref(textinput)

class MdApp(App):
    def build(self):
        root = MyLayout()
        Btn = Button(size=(250,250), pos=(100,100),text="Dynamic id assign n use id to rename")
        Btn.bind(on_release=root.use_weakref_id_to_replace_text,on_press=root.add_textinpt_with_weak_ref_dyn_id)
        root.add_widget(Btn)
        return root 


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