Kivyでウィジェットにidを指定した場合、Pythonファイルでidsを参照するとウィジェットを参照することができます。しかし、idsが取得できずにエラーが発生する場合があります。そのパターンについていくつかみてみましょう。
idsが参照できない
idsが取得できずにエラーが発生するケースを紹介します。
注意:この記事は前回の記事の捕捉になります。こちらの記事だけでも理解できるかもしれません。
ケース➀コンストラクタ内でのidsが参照できない場合
kvファイルのRoot要素にルートルールを指定し、コンストラクタ内でidsを参照するとエラーになります。詳しいコードをみてみましょう。
エラーになるコード
画面に表示される前にラベルのテキストを変更するという矛盾したサンプルですがそこは気にしないでください。要はコンストラクタ内でIDの参照ができないことを示しています。Root要素にルートクラス名を定義しています。
onkvpost1.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
#############################################
# エラーになる見本
#############################################
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.ids.mylabel.text = 'これはエラーになるよ'
class Onkvpost1(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
Onkvpost1().run()
onkvpost1.kv
RootWidget:
orientation: 'vertical'
Label:
id: mylabel
text: ''
これを実行すると下記のエラーが発生します。
in kivy.properties.ObservableDict.getattr
AttributeError: ‘super’ object has no attribute ‘getattr’. Did you mean: ‘setattr’?
これは「ids.mylabel.text」で値の変更を要求してるのに、元となるものが存在していないため起こります。RootWidget
の初期化時点で「self.ids.mylabel」がまだ空の状態で存在していないからです。
Kivyでは、ids(ウィジェットのIDを参照する辞書)はkvファイルが読み込まれてから生成されるため、コンストラクタでself.idsに直接アクセスしようとすると、mylabelが見つからないためにエラーが発生します。つまりこの時点ではまだルートウィジェットのインスタンスは生成されていないことになります。
正しいコード
これを解消するためにはkvファイルの読み込みが完了したあとに定義する必要があります。そこで役に立つのがon_kv_postメソッドです。以下のようにすると正しく動作します。kvファイルで定義したウィジェットツリーのインスタンスが作成されていない状態で、定義したIDやウィジェットツリーの他のウィジェットを参照・変更したい場合に役立ちます。
onkvpost2.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class RootWidget(BoxLayout):
def on_kv_post(self, base_widget):
# kvファイルが完全に読み込まれた後に呼び出される
self.ids.mylabel.text = 'エラーが解消されるよ'
class Onkvpost2(App):
pass
if __name__ == '__main__':
Onkvpost2().run()
onkvpost2.kv
RootWidget:
orientation: 'vertical'
Label:
id: mylabel
text: ''
クラスルールを適用した場合
この現象はRoot要素にクラスルールを指定した場合は発生しません。クラスルールを指定した場合とルートルールではidsの生成タイミングが異なることが原因だと思います。前の記事で紹介したライフサイクルを確認するサンプルコードでこれを確認することができます。
onkvpost3.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.ids.mylabel.text = 'クラスルールはエラーにならない'
class Onkvpost3(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
Onkvpost3().run()
onkvpost3.kv
<RootWidget>:
orientation: 'vertical'
Label:
id: mylabel
text: ''
こういった混乱を避けるためにもクラスルールの使用のみをお勧めします。
ケース②Appサブクラス内のidsが参照できない場合
次にAppサブクラス内でidsの参照をした場合はどうなるかみてみましょう。kvコードではクラスルールを指定しています。
class Onkvpost3(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.root.ids.mylabel.text = '???'
def build(self):
return RootWidget()
class Onkvpost3(App):
def build(self):
self.root.ids.mylabel.text = '???'
return RootWidget()
Appサブクラス内、buildメソッド内の両方共エラーになります。またidsは生成されていません。
では、先ほどのon_kv_postメソッドを使ったらどうなるでしょうか。
class Onkvpost3(App):
def build(self):
return RootWidget()
def on_kv_post(self, base_widget):
# kvファイルが完全に読み込まれた後に呼び出される
self.root.ids.mylabel.text = 'エラーが解消されるよ'
これはエラーが表示されていませんが画面には何も表示されていません。on_kv_postメソッドはここで実装しても呼ばれません。kvファイルが読まれた後に呼び出されるメソッドのためです。kvファイルが読まれるのはもっと後になります。
ケース③ScreenManagerでidsが参照できない場合
ScreenManagerを使用して画面遷移する場合、最初の画面のidsが参照できません。これの解決方法については下記のScreenManagerの記事にて解説しています。
まとめ
正しくidsを参照するためには下記のようにします。基本的にidsが生成された後の場所のメソッド内であれば参照できます。それからキーワードと名前空間を正しく書くことも大事です。また、場合によってはPythonコードで動的にウィジェットを作成することも検討してください。
- イベントに紐づけられたメソッドを定義してその中で使用する(Appサブクラスでもウィジェットを継承したサブクラスでもイベント時に呼ばれるメソッドはidsが生成された後に呼ばれるので参照できます。)
- ウィジェットを継承したクラス内でon_kv_postメソッドを定義してその中で使用する。クラスルールでも使用することができます。
Comment