Python Kivy属性错误-对象没有属性-尝试用kv语言连接小部件
在尝试连接Kivy中的小部件时,我似乎遇到了不停的问题。我读过,但我的情况没有直接报道 我有两个不同的“选择器”并排排列,如下所示: 每个选择器都是自己的类,由KeySigChooserContainer持有。我想根据KeySigChooserContainer的大小调整按钮的大小,以便按钮具有一致的大小。这是通过以下方式实现的:Python Kivy属性错误-对象没有属性-尝试用kv语言连接小部件,python,python-3.x,kivy,Python,Python 3.x,Kivy,在尝试连接Kivy中的小部件时,我似乎遇到了不停的问题。我读过,但我的情况没有直接报道 我有两个不同的“选择器”并排排列,如下所示: 每个选择器都是自己的类,由KeySigChooserContainer持有。我想根据KeySigChooserContainer的大小调整按钮的大小,以便按钮具有一致的大小。这是通过以下方式实现的: ChooserButton: ... width: root.parent.width * (3/32) 但是我不喜欢使用parentreferen
ChooserButton:
...
width: root.parent.width * (3/32)
但是我不喜欢使用parent
reference;随着应用程序的复杂性不断增加,我更愿意使用直接引用来提高灵活性。但是当我试着和你一起做的时候
<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
如果希望
BoxLayout
的子项是BoxLayout
宽度的某一部分,则可以使用size\u hint
(这就是它的预期用途)。例如,在rootnotechoser
中:
<RootNoteChooser>:
note_text: note_text
BoxLayout:
pos_hint: {"center": [0.5, 0.5]}
orientation: "horizontal"
ChooserButton:
text: u'\u25C4'
size_hint: [3/32, 1]
#width: root.box.width * (3/32)
on_press: root.increment_note_idx()
Label:
id: note_text
text: "C"
size_hint: [26/32, 1]
ChooserButton:
text: u'\u25BA'
size_hint: [3/32, 1]
#width: root.box.width * (3/32)
on_press: root.decrement_note_idx()
:
注释文字:注释文字
盒子布局:
位置提示:{“中心”:[0.5,0.5]}
方向:“水平”
选择按钮:
文本:u'\u25C4'
尺寸提示:[3/32,1]
#宽度:root.box.width*(3/32)
按:root.increment\u note\u idx()
标签:
id:注\文本
案文:“C”
尺寸提示:[26/32,1]
选择按钮:
文本:u'\u25BA'
尺寸提示:[3/32,1]
#宽度:root.box.width*(3/32)
按:root.decreation\u note\u idx()
在
BoxLayout
中,分配了size\u hint
的子项将在减去其他子项的大小后占据剩余空间的那一部分。因此,在上面的示例中,从父BoxLayout
中减去标签
空间,剩余空间在选择器按钮
之间分配。为标签添加类似的size\u提示
,使其更加清晰。在kivy中设置属性引用时,必须注意的一点是这些属性何时可用。您的原始代码似乎合理,但问题是rootnotechoser
和ModeChooser
的box
属性在设置之前被访问。您可以通过定义一个在实际设置其值之前可以使用的属性来解决这个问题。在这种情况下,使用NumericProperty(0)
将允许代码使用初始值零,即使该值不正确。然后,当(Kivy)指定了正确的值时,它将按照您的预期工作。下面是使用该方法修改的代码版本:
# 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()
还有一种方法是从kv
文件中删除
和
规则,并将这些规则的内容直接放在
规则的模式选择器
和根注释选择器
部分下。这将允许您使用以下方法设置ChooserButton
宽度:
width: box.width * (3/32)
与您的原始代码类似。对-但我希望RootNoteChooser和ModeChooser的宽度不同,ChooserButtons的宽度相同(如果不清楚,请抱歉)。无论我是直接使用size\u hint
还是width
,按钮的宽度都需要基于KeySigChooser的宽度,这是所有ChooserButtons的共同父项。我知道这是可行的,但我觉得这更像是一种解决方法,而不是答案。这样做对吗?创建已经存在的属性(box.width
)似乎是一种不好的做法。使用root.parent.width
是可行的,因此保存这些“选择器”的BoxLayout显然存在。但看起来和规则在创建属性的各自实例之前执行(box:box
)。有没有更好的方法来构造kv文件以避免此问题?(感谢您花时间编写这些答案!)在我上面的回答中,我添加了两种可选方法来实现您的目标。您对第四个选项有何看法:在和规则中,设置box:self.parent
以使box
存在。然后,稍后实例化时,框
将被附加到它的id
所覆盖(我认为)。这允许我们仍然使用box.width
(而不是box\u width
),并且kv文件仍然可以为每个类划分为规则。@marcus\u afailius,我认为这会起作用,但我认为您需要使用root.box.width
,而不是box.width
。有许多不同的方法可以达到相同的结果!!谢谢使用root.box.width
是我目前的做法。在我的主应用程序中,box
在小部件树中的位置实际上比我在这里解释的要高,所以这似乎是最干净的选择。不过,我并不特别喜欢这些解决方案,因为它们似乎都有点老套。。但也许它就是这样。我会让这篇文章停留一段时间,看看是否有其他人有意见,否则我会更新我的OP并将其标记为答案。再次感谢您的洞察力!
<RootNoteChooser>:
note_text: note_text
BoxLayout:
pos_hint: {"center": [0.5, 0.5]}
orientation: "horizontal"
ChooserButton:
text: u'\u25C4'
size_hint: [3/32, 1]
#width: root.box.width * (3/32)
on_press: root.increment_note_idx()
Label:
id: note_text
text: "C"
size_hint: [26/32, 1]
ChooserButton:
text: u'\u25BA'
size_hint: [3/32, 1]
#width: root.box.width * (3/32)
on_press: root.decrement_note_idx()
# 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()
# 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
width: box.width * (3/32)