我似乎在尝试连接 Kivy 中的小部件时遇到了不间断的问题.我已阅读
每个选择器都是它自己的类,由 KeySigChooserContainer 持有.我想根据 KeySigChooserContainer 的大小调整按钮的大小,以便按钮具有一致的大小.这是通过
完成的选择按钮:...宽度:root.parent.width * (3/32)
但我不喜欢使用 parent
引用;随着应用程序复杂性的增加,我更愿意使用直接参考来获得灵活性.但是当我尝试这样做时
<RootNoteChooser>:...盒子布局:...选择器按钮:...宽度:root.box.width * (3/32)<模式选择器>:...盒子布局:...选择器按钮:...宽度:root.box.width * (3/32)<KeySigChooserContainer>:盒子布局:编号:盒子RootNoteChooser:盒子:盒子模式选择器:盒子:盒子
我得到一个属性错误:AttributeError: 'RootNoteChooser' object has no attribute 'box'
我在项目的其他地方使用了类似的技术,所以我不知道为什么这不起作用.我还尝试在 RootNoteChooser 和 ModeChooser 类中将 box
设为 ObjectProperty,但这不起作用.
#keysigchooser.py从 kivy.app 导入应用程序从 kivy.properties 导入 NumericProperty、ObjectProperty从 kivy.uix.floatlayout 导入 FloatLayout从 kivy.uix.relativelayout 导入 RelativeLayoutchrom_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']类模式选择器(浮动布局):经过类 RootNoteChooser(FloatLayout):note_idx = NumericProperty(0)def increment_note_idx(self):self.note_idx = (self.note_idx + 1) % 12def decrement_note_idx(self):self.note_idx = (self.note_idx - 1) % 12def on_note_idx(self, instance, value):self.note_text.text = chrom_scale[self.note_idx]类 KeySigChooserContainer(FloatLayout):def on_size(self, instance, value):目标比率 = 60/20宽度,高度 = self.size# 检查哪个尺寸是限制因素如果宽度/高度 >目标比率:# 窗口比目标宽",所以限制是高度.self.ids.box.height = 高度self.ids.box.width = 高度 * target_ratio别的:self.ids.box.width = 宽度self.ids.box.height = 宽度/target_ratio类 KeySigChooserApp(App):定义构建(自我):返回 KeySigChooserContainer()如果 __name__ == "__main__":KeySigChooserApp().run()
#keysigchooser.kv<ChooserButton@Button>:font_name: "宋体"字体大小:self.width边框:[2, 2, 2, 2]<RootNoteChooser>:注释文本:注释文本盒子布局:pos_hint: {"center": [0.5, 0.5]}方向:水平"选择器按钮:文本:u'u25C4'size_hint:[无,1]宽度:root.box.width * (3/32)on_press:root.increment_note_idx()标签:编号:note_text文字:C"选择器按钮:文本:u'u25BA'size_hint:[无,1]宽度:root.box.width * (3/32)on_press:root.decrement_note_idx()<模式选择器>:盒子布局:pos_hint: {"center": [0.5, 0.5]}方向:水平"选择器按钮:文本:u'u25C4'size_hint:[无,1]宽度:root.box.width * (3/32)标签:文字:主要"选择器按钮:文本:u'u25BA'size_hint:[无,1]宽度:root.box.width * (3/32)<KeySigChooserContainer>:盒子布局:编号:盒子pos_hint: {"center": [0.5, 0.5]}size_hint:[无,无]方向:水平"RootNoteChooser:id: rootnotechooser盒子:盒子size_hint: [0.4, 1]帆布:颜色:rgba: [1, 0, 0, 0.5]长方形:pos: self.pos尺寸:self.size模式选择器:id: 模式选择器盒子:盒子size_hint: [0.6, 1]帆布:颜色:RGBA:[0, 1, 0, 0.5]长方形:pos: self.pos尺寸:self.size
显然我在这里遗漏了一些东西......感谢任何帮助.
更新这似乎是没有解决问题的好方法的情况之一.感谢@JohnAnderson,这就是我学到的东西:
这里的问题是我在 <RootNoteChooser>
和 <ModeChooser>
中使用了一个属性(box
)规则,但该属性是在 RootNoteChooser
和 ModeChooser
的 instance 中创建的.由于首先应用规则,因此 box
尚不存在.
我为此使用的解决方法是同时在两个规则中创建 box
属性,并将其设置为有意义的内容(并且不会导致错误).然后,在 RootNoteChooser
和 ModeChooser
实例中(在 <KeySigChooser>
规则中),box
将被重置到合适的对象.这是它的要点:
<RootNoteChooser>:box: self.parent # 最初我们会将其设置为合理的值.盒子布局:...选择器按钮:...宽度:root.box.width * (3/32)<模式选择器>:box: self.parent # 最初我们会将其设置为合理的值.盒子布局:...选择器按钮:...宽度:root.box.width * (3/32)<KeySigChooserContainer>:盒子布局:编号:盒子RootNoteChooser:box: box # 现在box属性正确,可以指向任意模式选择器:box: box # id 在这个规则内.
在 kivy 中设置对属性的引用时必须注意的一件事是这些属性何时可用.您的原始代码似乎合理,但问题是 RootNoteChooser
和 ModeChooser
的 box
属性在设置之前被访问.您可以通过定义一个可以在实际设置其值之前使用的属性来解决这个问题.在这种情况下,使用 NumericProperty(0)
将允许您的代码使用初始值零,即使这不是正确的值.然后,当(通过 Kivy)分配正确的值时,它将按您的预期工作.这是使用该方法的代码的修改版本:
#keysigchooser.py从 kivy.app 导入应用程序从 kivy.lang 导入生成器从 kivy.properties 导入 NumericProperty从 kivy.uix.floatlayout 导入 FloatLayoutchrom_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']类模式选择器(浮动布局):box_width = NumericProperty(0) # 从零开始,所以有可用的数字类 RootNoteChooser(FloatLayout):box_width = NumericProperty(0) # 从零开始,所以有可用的数字note_idx = NumericProperty(0)def increment_note_idx(self):self.note_idx = (self.note_idx + 1) % 12def decrement_note_idx(self):self.note_idx = (self.note_idx - 1) % 12def on_note_idx(self, instance, value):self.note_text.text = chrom_scale[self.note_idx]类 KeySigChooserContainer(FloatLayout):def on_size(self, instance, value):目标比率 = 60/20宽度,高度 = self.size# 检查哪个尺寸是限制因素如果宽度/高度 >目标比率:# 窗口比目标宽",所以限制是高度.self.ids.box.height = 高度self.ids.box.width = 高度 * target_ratio别的:self.ids.box.width = 宽度self.ids.box.height = 宽度/target_ratiobuilder.load_string('''# keyigchooser.kv<ChooserButton@Button>:font_name: "宋体"字体大小:self.width边框:[2, 2, 2, 2]<RootNoteChooser>:注释文本:注释文本盒子布局:pos_hint: {"center": [0.5, 0.5]}方向:水平"选择器按钮:文本:u'u25C4'size_hint:[无,1]宽度:root.box_width * (3/32)on_press:root.increment_note_idx()标签:编号:note_text文字:C"选择器按钮:文本:u'u25BA'size_hint:[无,1]宽度:root.box_width * (3/32)on_press:root.decrement_note_idx()<模式选择器>:盒子布局:pos_hint: {"center": [0.5, 0.5]}方向:水平"选择器按钮:文本:u'u25C4'size_hint:[无,1]宽度:root.box_width * (3/32)标签:文字:主要"选择器按钮:文本:u'u25BA'size_hint:[无,1]宽度:root.box_width * (3/32)<KeySigChooserContainer>:盒子布局:编号:盒子pos_hint: {"center": [0.5, 0.5]}size_hint:[无,无]方向:水平"RootNoteChooser:id: rootnotechooserbox_width: box.width # 设置 box_widthsize_hint: [0.4, 1]帆布:颜色:rgba: [1, 0, 0, 0.5]长方形:pos: self.pos尺寸:self.size模式选择器:id: 模式选择器box_width: box.width # 设置 box_widthsize_hint: [0.6, 1]帆布:颜色:RGBA:[0, 1, 0, 0.5]长方形:pos: self.pos尺寸:self.size''')类 KeySigChooserApp(App):定义构建(自我):返回 KeySigChooserContainer()如果 __name__ == "__main__":KeySigChooserApp().run()
我将您的 keysigchooser.kv
放入 Builder.load_string()
调用中只是为了我自己的方便.
有很多方法可以实现您想要的.另一种方法是使用 KeySigChooserContainer
的 on_size()
方法设置 ChooserButton
大小.为此,请将 id 添加到 ChooserButtons
并将以下代码添加到该方法的末尾:
# 设置按钮大小self.ids.rootnotechooser.ids.butt1.width = self.ids.box.width * 3/32self.ids.rootnotechooser.ids.butt2.width = self.ids.box.width * 3/32self.ids.modechooser.ids.butt1.width = self.ids.box.width * 3/32self.ids.modechooser.ids.butt2.width = self.ids.box.width * 3/32
还有一种方法是从 kv
文件中删除 <RootNoteChooser>
和 <ModeChooser>
规则并放置内容<KeySigChooserContainer>
规则的 ModeChooser
和 RootNoteChooser
部分下的这些规则.这将允许您使用以下方法设置 ChooserButton
宽度:
宽度:box.width * (3/32)
类似于您的原始代码.
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.
I have 2 different "choosers" side by side like this:
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)
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
I get an attribute error: AttributeError: 'RootNoteChooser' object has no attribute 'box'
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.
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:
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.
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.
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()
I put your keysigchooser.kv
into a Builder.load_string()
call just for my own convenience.
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
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)
similar to your original code.
这篇关于Kivy 属性错误 - 对象没有属性 - 尝试以 kv 语言连接小部件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!