Help us understand the problem. What is going on with this article?

自分がこれまでに躓いたり戸惑ったりしたKv言語の事いろいろ

はじめに

私はKivyを触り始めて1年半ぐらいなのですが、何となくですが自分なりにKivyがどういった物か摑めてきたと思うので、ここでこれまで自分がつまずいた事をまとめたいと思います。

1. root ruleはinstanceの定義でclass ruleは型の定義

root ruleとはkvファイル内で

Widget:
    略

のようにクラス名:から始まるもので、class rule

<MyWidget>:
    略

のように<クラス名>:から始まるものです。このroot ruleというのが分かりにくい名前で、最初の頃は「widget階層の根(root)にあたるwidgetは必ずこれで定義しないといけない?」と思ってました。実際はそんな事とは関係なく、見出しの通り只の生成するinstanceを定義しているだけです。以下にKv言語を使ったコードとそれとほぼ同等のPythonのみのコードを書いたので比べてみて下さい。

まずはroot rule

part1_with_kv.py
from kivy.lang import Builder

KV_STRING = r"""
FloatLayout:
    Label:
        text: 'Label'
"""

instance = Builder.load_string(KV_STRING)
part1_pure_python.py
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label

instance = FloatLayout()
instance.add_widget(Label(text='Label'))

次はclass rule

part2_with_kv.py
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout

KV_STRING = r"""
<MyWidget>:
    Label:
        text: 'Label'
"""
Builder.load_string(KV_STRING)

class MyWidget(FloatLayout):
    pass
part2_pure_python.py
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label

class MyWidget(FloatLayout):

    def __init__(self, **kwargs):
        super(MyWidget, self).__init__(**kwargs)
        self.add_widget(Label(text='Label'))

class ruleが型(class)をいじってるのに対し、root ruleはinstanceを作り、その作ったinstanceだけをいじっています。それが分かれば以下のコードにも困りません。

from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout

KV_STRING = r"""
<MyLabel>:
    text: 'Label'
    font_size: '80sp'
MyLabel:
    color: 1, 1, 0, 1
"""


class MyLabel(Label):
    pass


instance = Builder.load_string(KV_STRING)  # A
instance2 = MyLabel()  # B

root = BoxLayout()
root.add_widget(instance)
root.add_widget(instance2)

runTouchApp(root)

Screenshot at 2018-04-17 11-47-58.png

これは

  • MyLabelという"型"を定義(class ruleの部分)
  • そのMyLabelのinstanceを一つ作り、文字色を黄色にする(root ruleの部分)

というKvのコードになってます。class ruleに書いた物(文字の大きさを80sp, 表示する文字列を'Label')がAとB両方のinstanceに反映されているのに対し、root ruleに書いた物(文字色を黄色)は片方(A)のinstanceにしか反映されていないのが確認できます。

2. widgetの定義の仕方が色々ある

上の章で既に下の2つの仕方がでました。

  • pythonとKv言語を両方使う(part2_with_kv.py)
  • pythonのみ(part2_pure_python.py)

実は他にもあり、それはKv言語のみです。

part2_pure_kv.py
from kivy.lang import Builder

KV_STRING = r"""
<MyWidget@FloatLayout>:
    Label:
        text: 'Label'
"""
Builder.load_string(KV_STRING)

この色々やり方があるというのは初心者を惑わしますね。基本的に全ての事はKv言語を用いなくてもできると思ってます。ですが用いることで大幅にコード量を減らしわかりやすくできるのです。でも逆にKv言語では出来ないor難しくなることもあります。どういった時にどういった方法がいいかは、多分使っていくうちに分かってくると思います。

3.定義されたwidgetへのアクセス方法も色々ある

from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label

class MyWidget(FloatLayout):

    def __init__(self, **kwargs):
        super(MyWidget, self).__init__(**kwargs)
        self.add_widget(Label(text='Label'))

instance = MyWidget()  # A
instance = Factory.MyWidget()  # B
instance = Builder.load_string('MyWidget:')  # C

A,B,Cは全てMyWidgetのinstanceを作るのに使える方法です。Aは普通のPythonの方法で、同じmodule内のclassなのでそのまま使えますね。B,CはKivy特有で、Aとは違いどこからでも同じ書き方でMyWidgetを作れます。どこからでもというのは例え違うmodule内だったとしてもという意味です。つまりは定義されたwidget(元からKivyが持っているButton等も含めて)はpackage名、module名など指定することなく作れるのです。じゃあもし同名のwidgetが別の場所で定義されていたらどうなるのか?というとそれに関しては少しこの記事に書いたので参考にしてください。

4. class ruleは厳密には定義の追加

以下のコードの実行結果は

from kivy.lang import Builder
from kivy.factory import Factory


class MyWidget(Factory.Widget):

    def on_width(self, __, value):
        print('on_width', value)

    def add_widget(self, *args, **kwargs):
        print('add_widget')
        super().add_widget(*args, **kwargs)

    def remove_widget(self, *args, **kwargs):
        print('remove_widget')
        super().remove_widget(*args, **kwargs)


Builder.load_string(r'''
<MyWidget>:
    width: 200
    Widget:
''')

Builder.load_string(r'''
<MyWidget>:  # A
    width: 400
    Widget:
''')


MyWidget()

以下のようになります。

標準出力
add_widget
on_width 200
add_widget
on_width 400

同じwidgetに対するclass ruleを複数回読み込ませると、上書きではなく蓄積されます。なのでMyWidgetは初期状態で2つ子を持つことになりますし、widthに関しても最終的に400になってはいるもののその過程で一度width: 200に設定されているのが分かります。もしこのような蓄積を避けたいのであればA行を<-MyWidget>:としてください。するとそれまでにあったMyWidgetに関する(Kv言語上の)定義が消され、以下のような結果になります。

標準出力
add_widget
on_width 400

最後に

長くなりましたがとりあえず今思い出せたのはこれぐらいです。これからKivyを始める人の役に立てれば幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away