自分がこれまでに躓いたり戸惑ったりした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`

```python3:part1_with_kv.py
from kivy.lang import Builder

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

instance = Builder.load_string(KV_STRING)
```

```python3: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`

```python: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
```

```python3: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だけをいじっています。それが分かれば以下のコードにも困りません。

```python3
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](https://qiita-image-store.s3.amazonaws.com/0/144673/5de177cd-74c6-3a81-367e-1d053348f342.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言語のみです。

```python3: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へのアクセス方法も色々ある

```python3
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が別の場所で定義されていたらどうなるのか？というとそれに関しては少し[この記事](http://qiita.com/gotta_dive_into_python/items/c73a1a1e7587c9e03bcf)に書いたので参考にしてください。

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

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

```python3
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()
```

以下のようになります。

```text:標準出力
add_widget
on_width 200
add_widget
on_width 400
```

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

```text:標準出力
add_widget
on_width 400
```

# 最後に

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