Kivy属性错误-对象没有属性-尝试以kv语言连接小部件 [英] Kivy Attribute Error - object has no attribute - Trying to connect widgets in kv language

查看:104
本文介绍了Kivy属性错误-对象没有属性-尝试以kv语言连接小部件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在尝试连接Kivy中的小部件时,我似乎遇到了不停的问题.我已阅读此有用指南,但我的情况未直接涉及

I seem to be having nonstop problems with trying to connect widgets in Kivy. I've read this useful guide but my situation isn't directly covered.

我有2个并排的选择器",例如:

I have 2 different "choosers" side by side like this:

每个选择器都是其自己的类,由KeySigChooserContainer持有.我想根据KeySigChooserContainer的大小来调整按钮的大小,以便按钮具有一致的大小.这是通过

Each chooser will be its own class, held by the KeySigChooserContainer. I want to size the buttons based on the size of the KeySigChooserContainer, so that the buttons will have consistent sizes. This is accomplished with

ChooserButton:
    ...
    width: root.parent.width * (3/32)

但我不喜欢使用parent引用;随着应用程序复杂程度的提高,我宁愿直接使用灵活性作为参考.但是当我尝试使用

but I don't like using the parent reference; I'd much rather use a direct reference for flexibility as the app grows in complexity. But when I try doing that with

<RootNoteChooser>:
    ...
    BoxLayout:
        ...
        ChooserButton:
            ...
            width: root.box.width * (3/32)

<ModeChooser>:
    ...
    BoxLayout:
        ...
        ChooserButton:
            ...
            width: root.box.width * (3/32)

<KeySigChooserContainer>:
    BoxLayout:
        id: box
        RootNoteChooser:
            box: box
        ModeChooser:
            box: box

我收到属性错误:AttributeError: 'RootNoteChooser' object has no attribute 'box'

我在项目的其他地方使用了类似的技术,所以我不知道为什么它不起作用.我还尝试过在RootNoteChooser和ModeChooser类中将box设置为ObjectProperty,但这是行不通的.

I've used a similar technique elsewhere in my project so I have no idea why this isn't working. I have also tried making box an ObjectProperty within the RootNoteChooser and ModeChooser classes but that doesn't work.

# keysigchooser.py
from kivy.app import App
from kivy.properties import NumericProperty, ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout

chrom_scale = ['C', 'C#/Db', 'D', 'D#/Eb', 'E', 'F', 'F#/Gb', 'G', 'G#/Ab', 'A', 'A#/Bb', 'B']
chrom_scale2 = ['C', 'C/D', 'D', 'D/E', 'E', 'F', 'F/G', 'G', 'G/A', 'A', 'A/B', 'B']


class ModeChooser(FloatLayout):
    pass


class RootNoteChooser(FloatLayout):
    note_idx = NumericProperty(0)

    def increment_note_idx(self):
        self.note_idx = (self.note_idx + 1) % 12

    def decrement_note_idx(self):
        self.note_idx = (self.note_idx - 1) % 12

    def on_note_idx(self, instance, value):
        self.note_text.text = chrom_scale[self.note_idx]


class KeySigChooserContainer(FloatLayout):
    def on_size(self, instance, value):
        target_ratio = 60/20
        width, height = self.size
        # check which size is the limiting factor
        if width / height > target_ratio:
            # window is "wider" than targeted, so the limitation is the height.
            self.ids.box.height = height
            self.ids.box.width = height * target_ratio
        else:
            self.ids.box.width = width
            self.ids.box.height = width / target_ratio


class KeySigChooserApp(App):
    def build(self):
        return KeySigChooserContainer()


if __name__ == "__main__":
    KeySigChooserApp().run()

# keysigchooser.kv
<ChooserButton@Button>:
    font_name: "Arial"
    font_size: self.width
    border: [2, 2, 2, 2]


<RootNoteChooser>:
    note_text: note_text
    BoxLayout:
        pos_hint: {"center": [0.5, 0.5]}
        orientation: "horizontal"
        ChooserButton:
            text: u'\u25C4'
            size_hint: [None, 1]
            width: root.box.width * (3/32)
            on_press: root.increment_note_idx()
        Label:
            id: note_text
            text: "C"
        ChooserButton:
            text: u'\u25BA'
            size_hint: [None, 1]
            width: root.box.width * (3/32)
            on_press: root.decrement_note_idx()


<ModeChooser>:
    BoxLayout:
        pos_hint: {"center": [0.5, 0.5]}
        orientation: "horizontal"
        ChooserButton:
            text: u'\u25C4'
            size_hint: [None, 1]
            width: root.box.width * (3/32)
        Label:
            text: "Major"
        ChooserButton:
            text: u'\u25BA'
            size_hint: [None, 1]
            width: root.box.width * (3/32)


<KeySigChooserContainer>:
    BoxLayout:
        id: box
        pos_hint: {"center": [0.5, 0.5]}
        size_hint: [None, None]
        orientation: "horizontal"
        RootNoteChooser:
            id: rootnotechooser
            box: box
            size_hint: [0.4, 1]
            canvas:
                Color:
                    rgba: [1, 0, 0, 0.5]
                Rectangle:
                    pos: self.pos
                    size: self.size
        ModeChooser:
            id: modechooser
            box: box
            size_hint: [0.6, 1]
            canvas:
                Color:
                    rgba: [0, 1, 0, 0.5]
                Rectangle:
                    pos: self.pos
                    size: self.size

很明显,我在这里缺少任何东西.感谢您的帮助.

Clearly I'm missing something here... any help is appreciated.

更新 这似乎是没有伟大方法来解决问题的情况之一.感谢@JohnAnderson,这是我学到的东西:

UPDATE This seems to be one of those situations where there's not a great way to solve the problem. Thanks to @JohnAnderson, here's what I learned:

  1. ID的范围仅限于声明它的规则.
  2. 最外面的小部件将kv规则应用于其所有内部小部件,然后再应用其他任何规则
  3. 规则始终在实例之前应用.
  1. An id is limited in scope to the rule it is declared in.
  2. the outermost widget applies the kv rules to all its inner widgets before any other rules are applied
  3. Rules are always applied before instances.

这里的问题是我在<RootNoteChooser><ModeChooser> 规则中使用了属性(box),但是该属性是在实例中创建的RootNoteChooserModeChooser的值.由于首先应用规则,所以box尚不存在.

The problem here is I am using an attribute (box) in the <RootNoteChooser> and <ModeChooser> rules, but that attribute gets created in the instance of RootNoteChooser and ModeChooser. Since rules are applied first, box does not yet exist.

为此,我要使用的解决方法是同时在两个规则中创建box属性,并将其设置为有意义的值(不会引起错误).然后,在RootNoteChooserModeChooser实例中(在<KeySigChooser>规则中),box将重置为适当的对象.这是要点:

The work-around I'm using for this is to also create the box attribute in both rules, and set it to something that makes sense (and won't cause an error). Then, in the RootNoteChooser and ModeChooser instances (in the <KeySigChooser> rule), box will get reset to the proper object. Here's the gist of it:

<RootNoteChooser>:
    box: self.parent  # Initially we'll set it to something reasonable.
    BoxLayout:
        ...
        ChooserButton:
            ...
            width: root.box.width * (3/32)

<ModeChooser>:
    box: self.parent  # Initially we'll set it to something reasonable.
    BoxLayout:
        ...
        ChooserButton:
            ...
            width: root.box.width * (3/32)

<KeySigChooserContainer>:
    BoxLayout:
        id: box
        RootNoteChooser:
            box: box  # Now box attribute is correct, and can be pointed at any
        ModeChooser:
            box: box  # id that is within this rule.

推荐答案

在kivy中设置对属性的引用时,必须注意的一件事是这些属性何时可用.您的原始代码似乎合理,但是问题在于,在设置之前,必须先访问RootNoteChooserModeChooserbox属性.您可以通过定义一个可以在实际设置其值之前使用的属性来解决此问题.在这种情况下,使用NumericProperty(0)将允许您的代码使用初始值零,即使这不是正确的值.然后,当(由Kivy)分配了正确的值时,它将按您期望的那样工作.这是使用该方法的代码的修改版本:

One thing you must watch when setting up references to properties in kivy is when those properties will be available. Your original code seems reasonable, but the problem is that the box property of RootNoteChooser and ModeChooser is accessed before it is set-up. You can get around that by defining a property that can be used before its value is actually set. In this case, using a NumericProperty(0) will allow your code to use the initial value of zero, even though that is not the correct value. Then when the correct value is assigned (by Kivy), it will work as you expect. Here is a modified version of your code using that approach:

# keysigchooser.py
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.floatlayout import FloatLayout

chrom_scale = ['C', 'C#/Db', 'D', 'D#/Eb', 'E', 'F', 'F#/Gb', 'G', 'G#/Ab', 'A', 'A#/Bb', 'B']
chrom_scale2 = ['C', 'C/D', 'D', 'D/E', 'E', 'F', 'F/G', 'G', 'G/A', 'A', 'A/B', 'B']


class ModeChooser(FloatLayout):
    box_width = NumericProperty(0)   # starts off as zero, just so there is  number available


class RootNoteChooser(FloatLayout):
    box_width = NumericProperty(0)   # starts off as zero, just so there is  number available
    note_idx = NumericProperty(0)

    def increment_note_idx(self):
        self.note_idx = (self.note_idx + 1) % 12

    def decrement_note_idx(self):
        self.note_idx = (self.note_idx - 1) % 12

    def on_note_idx(self, instance, value):
        self.note_text.text = chrom_scale[self.note_idx]


class KeySigChooserContainer(FloatLayout):
    def on_size(self, instance, value):
        target_ratio = 60/20
        width, height = self.size
        # check which size is the limiting factor
        if width / height > target_ratio:
            # window is "wider" than targeted, so the limitation is the height.
            self.ids.box.height = height
            self.ids.box.width = height * target_ratio
        else:
            self.ids.box.width = width
            self.ids.box.height = width / target_ratio

Builder.load_string('''
# keysigchooser.kv
<ChooserButton@Button>:
    font_name: "Arial"
    font_size: self.width
    border: [2, 2, 2, 2]


<RootNoteChooser>:
    note_text: note_text
    BoxLayout:
        pos_hint: {"center": [0.5, 0.5]}
        orientation: "horizontal"
        ChooserButton:
            text: u'\u25C4'
            size_hint: [None, 1]
            width: root.box_width * (3/32)
            on_press: root.increment_note_idx()
        Label:
            id: note_text
            text: "C"
        ChooserButton:
            text: u'\u25BA'
            size_hint: [None, 1]
            width: root.box_width * (3/32)
            on_press: root.decrement_note_idx()


<ModeChooser>:
    BoxLayout:
        pos_hint: {"center": [0.5, 0.5]}
        orientation: "horizontal"
        ChooserButton:
            text: u'\u25C4'
            size_hint: [None, 1]
            width: root.box_width * (3/32)
        Label:
            text: "Major"
        ChooserButton:
            text: u'\u25BA'
            size_hint: [None, 1]
            width: root.box_width * (3/32)


<KeySigChooserContainer>:
    BoxLayout:
        id: box
        pos_hint: {"center": [0.5, 0.5]}
        size_hint: [None, None]
        orientation: "horizontal"
        RootNoteChooser:
            id: rootnotechooser
            box_width: box.width   # this sets the box_width
            size_hint: [0.4, 1]
            canvas:
                Color:
                    rgba: [1, 0, 0, 0.5]
                Rectangle:
                    pos: self.pos
                    size: self.size
        ModeChooser:
            id: modechooser
            box_width: box.width   # this sets the box_width
            size_hint: [0.6, 1]
            canvas:
                Color:
                    rgba: [0, 1, 0, 0.5]
                Rectangle:
                    pos: self.pos
                    size: self.size
''')
class KeySigChooserApp(App):
    def build(self):
        return KeySigChooserContainer()


if __name__ == "__main__":
    KeySigChooserApp().run()

为方便起见,我将您的keysigchooser.kv放入Builder.load_string()通话中.

I put your keysigchooser.kv into a Builder.load_string() call just for my own convenience.

有很多方法可以实现您想要的.另一种方法是使用KeySigChooserContaineron_size()方法设置ChooserButton大小.为此,将id添加到ChooserButtons并将以下代码添加到该方法的末尾:

There are numerous ways to accomplish what you want. Another way is to set the ChooserButton sizes using your on_size() method of KeySigChooserContainer. To do this, add ids to the ChooserButtons and add the following code to the end of that method:

    # set button sizes
    self.ids.rootnotechooser.ids.butt1.width = self.ids.box.width * 3/32
    self.ids.rootnotechooser.ids.butt2.width = self.ids.box.width * 3/32
    self.ids.modechooser.ids.butt1.width = self.ids.box.width * 3/32
    self.ids.modechooser.ids.butt2.width = self.ids.box.width * 3/32

还有另一种方法是从kv文件中删除<RootNoteChooser><ModeChooser>规则,并将这些规则的内容直接放在<KeySigChooserContainer>规则的ModeChooserRootNoteChooser部分下.这将允许您使用以下方法设置ChooserButton宽度:

And yet another method is to remove the <RootNoteChooser> and <ModeChooser> rules from your kv file and place the contents of those rules directly under the ModeChooser and RootNoteChooser sections of the <KeySigChooserContainer> rule. This would allow you to set the ChooserButton widths using:

width: box.width * (3/32)

类似于您的原始代码.

这篇关于Kivy属性错误-对象没有属性-尝试以kv语言连接小部件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆