問題描述
我似乎在嘗試連接 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,這就是我學到的東西:
- 一個 id 的范圍僅限于聲明它的規則.
- 最外面的小部件將 kv 規則應用于其所有內部應用任何其他規則之前的小部件
- 規則總是在在實例之前應用.
這里的問題是我在 <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:
- An id is limited in scope to the rule it is declared in.
- the outermost widget applies the kv rules to all its inner widgets before any other rules are applied
- Rules are always applied before instances.
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模板網!