PR
Python+Kivy入門

カスタムウィジェットの作成方法: KivyのWidgets【kv言語】

この記事は約10分で読めます。

Kivyでは様々なUIウィジェットを提供していますが、独自にカスタマイズしたウィジェットを作ることが可能です。この記事ではカスタムウィジェットの作成方法とカスタムウィジェットからルートウィジェットにアクセスする方法を解説します。

カスタムウィジェットの作り方

Kivyでは様々なUIウィジェットを提供していますが、独自にカスタマイズしたウィジェットを作ることが可能です。

カスタムウィジェットを作るにはウィジェットを継承します。継承したサブクラスはカスタムウィジェットとして扱われます。

例えば、単純に何らかの処理だけを記述したい場合は、Widgetクラスを継承します。WidgetクラスはUIウィジェットの基底になるクラスです。

from kivy.uix.widget import Widget

class CustomWidget1(Widget):
    pass

単体のウィジェットを継承したい場合はそのウィジェットを継承します。

from kivy.uix.label import Label

class CustomWidget2(Label):
    pass
from kivy.uix.label import Button

class CustomWidget3(Button):
    pass

レイアウトを継承した場合もカスタムウィジェットになります。

from kivy.uix.boxlayout import BoxLayout

class CustomWidget4(BoxLayout):
    pass

これらのカスタムウィジェットはkvコードでクラスルールなどのルールでスタイルを定義します。(もちろんPythonコードでも書けます。)

# Pythonファイル
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button

class CustomWidget1(Widget):
    pass

class CustomWidget2(Label):
    pass

class CustomWidget3(Button):
    pass

class CustomWidget4(BoxLayout):
    pass

class RootWidget(BoxLayout):
    pass

class MyApp(App):
    def build(self):
        return CustomWidget()

if __name__ == '__main__':
    MyApp().run()
# kvファイル
<CustomWidget1>:

<CustomWidget2>:
    text: "Label"

<CustomWidget3>:
    text: "Button"

<CustomWidget4>:
    Label:
    Button:

<RootWidget>: # ルートウィジェットのツリー
    CustomWidget1:
    CustomWidget2:
    CustomWidget3:
    CustomWidget4:
    Label: 
        text: "ウィジェットを並べたい順番で配置する。"
    Button:
        text: "通常のウィジェットとカスタムウィジェットを組み合わせられます。"
    

カスタムウィジェットを定義したらルートウィジェットのツリーに配置します。ルートウィジェットのツリーは画面を構成します。これをしないとカスタムウィジェットは独立しているため表示されません。

<RootWidget>:
    CustomWidget1:
    CustomWidget2:
    CustomWidget3:
    CustomWidget4:

ここではルートウィジェットの「RootWidget」にカスタムウィジェットを表示したい順番で並べています。

kvコードだけでカスタムウィジェットを作る

単純な処理の場合、動的クラス(Dynamic Class)ルールを使用してkvコードだけでカスタムウィジェットを定義することもできます。動的クラスはクラスの継承をkvコードで定義するのでPythonコードの下記の記述と同じ意味を持ちます。

from kivy.uix.label import Button

class CustomWidget3(Button):
    pass
<CustomWidget3@Button>:
    text: "button"

<RootWidget>:
    CustomWidget3:

クラスルールと同じようにルーウィジェットのツリーに配置します。動的クラスの詳細は下記のページで説明しています。

カスタムウィジェットでidを使う場合

カスタムウィジェットでidを定義をする場合、通常はルートウィジェットのツリーで定義します。idにはスコープがあり、ルートウィジェットのツリーでidを定義することによってルートウィジェットから参照できるようになります。カスタムウィジェットはルートウィジェットのツリー内にありますが、ルートウィジェットから枝分かれした別のツリーになります。そのため、それらのツリーはスコープ外になり、互いにアクセスできません。

RootWidget             # ルートウィジェット
---+ CustomWidget1     # 他の子を預かっている
---+ CustomWidget2     # 他の子を預かっている
---+ CustomWidget3     # 他の子を預かっている
---+-CustomWidget4   # 更に枝分かれした別のツリー
---+ Label             # ルートウィジェットの子
---+ Button            # ルートウィジェットの子

ツリーを横に並べると分かりやすいかもしれません。下図の構成の場合、縦ラインのウィジェットには互いにアクセスできますが、横ラインのウィジェットにはアクセスできません。ルートウィジェットとAppはどのウィジェットもアクセスできます。(ただし、アクセスするためのキーワードが異なります)

Kivy親と子の関係図

idについての詳細は下記の記事をご覧ください。

アクセスするためのキーワードについては下記の記事をご覧ください。

下記のようにルートウィジェットのツリーにidを定義します。

<RootWidget>:
    CustomWidget1:
    CustomWidget2:
        id: custom2
    CustomWidget3:
        id: custom3
    CustomWidget4:
        id: custom4
    Label:
        id: root_label
    Button:
        id: root_button

kvコードでカスタムウィジェットからRootにアクセス

kvコードの場合、カスタムウィジェットから他のクラスのメソッドや変数にアクセスする際も、idと同じで他のツリーのウィジェットのクラスにはアクセスできません。(Pythonコードからはアクセスできます。)自身のクラスとルートウィジェットのクラスのメソッドや変数にアクセスできます。

複雑な構成の場合、そのウィジェットでしか処理できない場合を除いて、各クラスで処理を実装するのではなく、ルートウィジェット方で処理を管理する方が好ましいです。また、アプリ全体に関する処理はAppサブクラスで管理すると良いかもしれません。

以下にカスタムウィジェットからルートにアクセスするサンプルを示します。

custom_widget.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button

class CustomWidget1(Widget):
    def custom1(self):
        print("custom1")

class CustomWidget2(Label):
    def custom2(self):
        print("custom2")

class CustomWidget3(Button):
    def custom3(self):
        print("custom3")

class CustomWidget4(BoxLayout):
    def custom4(self):
        print("custom4")

    obj = CustomWidget3()
    obj.custom3()

class RootWidget(BoxLayout):
    def root1(self):
        print("Root")

class CustomWidget(App):
    def build(self):
        return RootWidget()

if __name__ == '__main__':
    CustomWidget().run()

customwidget.kv

<CustomWidget1>:
    canvas.before:
        Color:
            rgba: 42/255, 100/255, 89/255, 1
        Rectangle:
            size: self.size
            pos: self.pos

<CustomWidget2>:
    id: custom2
    text: "custom_widget2"
    on_touch_down: 
        if self.collide_point(*args[1].pos):self.custom2(); self.parent.root1(); app.root.root1()

<CustomWidget3>:
    id: custom3
    text: "custom_widget3"
    on_press:
        self.custom3()
        self.parent.root1() #下の行と同じ
        app.root.root1() #上の行と同じ

<CustomWidget4>:
    Label:
        id: custom4_label
        text: f"custom_widget4"
    Button:
        id: custom4_button
        text: f"custom_widget4 Button"
        on_press:
            app.root.root1()
            root.custom4() #下の行と同じ
            self.parent.custom4() #上の行と同じ

<RootWidget>: # ルートウィジェットのツリー
    orientation: 'vertical'
    CustomWidget1:
    CustomWidget2:
        id: custom2
    CustomWidget3:
        id: custom3
    CustomWidget4:
        id: custom4
    Label:
        id: root_label
        text: "ウィジェットを並べたい順番で配置する。"
    Button:
        id: root_button
        text: "通常のウィジェットとカスタムウィジェットを組み合わせられます。"
    

Comment

タイトルとURLをコピーしました