Python kivyで画面切り替えの最小構成 その2




# はじめに

[Python kivyで画面切り替えの最小構成 - Qiita](https://qiita.com/mkgask/items/836ec9e22ff81db9818d)

の続きです。

上記前回記事で作った画面切り替えですが、別の記事シリーズ（[PythonのKivyで音楽プレイヤー - Qiita](https://qiita.com/mkgask/items/dcd0f173998168fe5614)）で試作中のソフトに入れ込もうとしたら、機能的に全く足りていませんでした。
本当に画面を切り替えるだけの最小構成だったので当然と言えば当然なのですが・・・

なので、もう少し実践的に使える画面切り替えを試しました。
コードのベースは前回記事のものを使い、変更点のみ書いています



# 想定環境

- Windows 10（Pythonでkivyなので他OSでもほぼ同等です）
- Python 3.7.0
- pip 18.0
- kivy 1.10.1
- Nuitka 0.5.32.7（基本的には出てきませんが、実行ファイル形式出力が可能な状態を維持します）



# kvファイル

今回はkvファイルから先に紹介します。



## window1.kv

```yml:window1.kv
#<Window1@BoxLayout>    # 変更
<Window1>
    orientation: "vertical"

    ActionBar:
        ActionView:

            ActionPrevious:
                title: 'ウィンドウ1'
                with_previous: False

            ActionButton:
                text: '切り替え'
                on_press: print("change disp window 1"); app.root.change_disp2()

    BoxLayout:
        Label:
            text: '画面1'

        Button:    # ポップアップを表示するボタンを追加
            text: 'ボタン'
            on_press: root.button_press()
```

window1.pyファイルを作成し、pythonで動作を定義出来るようにするため、kvファイルでのクラス継承の記述を削除しました。
この記述があるとpython側が利用されず、kvファイルの定義のみでウィンドウが作成されてしまうため、追加したボタンの `root.button_press()` でメソッドが無いとしてエラーで落ちます。



## window2.kv

```yml:window2.kv
#<Window1@BoxLayout>    # 変更
<Window2>
    orientation: "vertical"

    ActionBar:
        ActionView:

            ActionPrevious:
                title: 'ウィンドウ2'
                with_previous: False

            ActionButton:
                text: '切り替え'
                on_press: print("change disp window 2"); app.root.change_disp()

    BoxLayout:
        Label:
            text: '画面2'

        Button:    # ポップアップを表示するボタンを追加
            text: 'ボタン'
            on_press: root.button_press()
```

window1.kvと同じ変更を行います。
ポップアップの内容はpython側で変更するので、別のポップアップだと区別出来るようにします。



## main.kv

```yml:main.kv
MainRoot

<MainRoot>
    # Window1    削除
```

MainRootにぶら下げていたWindow1を削除しています。
ここで使われるWindow1と、コード上でFactoryを使って生成したWindow1は別物で、初期画面ではここのWindow1、画面を切り替えて戻ってきた時はFactoryで生成したWindow1と、同じWindow1でも別のインスタンスのものなので、後々致命的な問題になる要因になっていました。
（設定画面に行って戻ってきてメイン画面が初期化されてたらソフトとして使い物にならない）

なのでここでは空のウィンドウしか生成しません。



# pythonファイル



## window1.py

```py:window1.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.core.window import Window

class Window1(BoxLayout):
    
    def button_press(self):
        popup_content = GridLayout(cols = 1)

        popup_close = Button(
            text = 'OK',
            size_hint_y = None,
            height = 40)

        popup_content.add_widget(Label(text = 'ウィンドウ 1 ポップアップ'))
        popup_content.add_widget(popup_close)
        popup = Popup(
            title = 'Test popup',
            size_hint = (None, None),
            size = (256, 256),
            content = popup_content)
        popup_close.bind(on_release = popup.dismiss)

        popup.open()
```

ウィンドウ1のボタンが押された時の処理を記述します。
`ウィンドウ 1 ポップアップ` のテキストと、 `OK` と表示される押したらポップアップを閉じるボタンがあるポップアップを表示します。

ポップアップを1つ表示したいだけなんですが、その割にはちょっと記述量が多い気がします。
ポップアップの定義をkvファイルに追い出して、コード上ではFactoryから生成、openだけするほうがすっきり分かりやすい形になりそうです。



## window2.py

```py:window2.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.core.window import Window

class Window2(BoxLayout):
    
    def button_press(self):
        popup_content = GridLayout(cols = 1)

        popup_close = Button(
            text = 'OK',
            size_hint_y = None,
            height = 40)

        popup_content.add_widget(Label(text = 'ウィンドウ 2 ポップアップ'))
        popup_content.add_widget(popup_close)
        popup = Popup(
            title = 'Test popup',
            size_hint = (None, None),
            size = (256, 256),
            content = popup_content)
        popup_close.bind(on_release = popup.dismiss)

        popup.open()
```

window1.pyとほぼ同じ構成です。
ポップアップに表示するテキストが2になっているので、ここで区別します。



## main.py

```py:main.py
# -*- coding: utf-8 -*
import os
import kivy

from kivy.app import App
from kivy.core.window import Window
from kivy.factory import Factory

from kivy.uix.boxlayout import BoxLayout

# 日本語フォント表示対応
from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path

resource_add_path('{}\\{}'.format(os.environ['SYSTEMROOT'], 'Fonts'))
LabelBase.register(DEFAULT_FONT, 'MSGOTHIC.ttc')

# 画面ごとに分離してバラで読み込む
from kivy.lang import Builder

Builder.load_file('window1.kv')
Builder.load_file('window2.kv')

from window1 import Window1    # 追加
from window2 import Window2    # 追加



class MainRoot(BoxLayout):
    window1 = None
    window2 = None

    def __init__(self, **kwargs):
        # 起動時に各画面を作成して使い回す
        self.window1 = Factory.Window1()
        self.window2 = Factory.Window2()
        super(MainRoot, self).__init__(**kwargs)
        self.change_disp()    # 追加

    def change_disp(self):
        self.clear_widgets()
        self.add_widget(self.window1)

    def change_disp2(self):
        self.clear_widgets()
        self.add_widget(self.window2)



class MainApp(App):
    def __init__(self, **kwargs):
        super(MainApp, self).__init__(**kwargs)
        self.title = '画面切り替えテスト'



if __name__ == "__main__":
    MainApp().run()
```

作ったwindow1.pyとwindow2.pyをfrom、importして使えるようにします。

また、main.kvのところで説明した通り、初期では空ウィンドウしか生成していないので、 `change_disp()` メソッドをコード上から実行して初期画面表示を行っています。



# スクリーンショット

![clipboard01](https://user-images.githubusercontent.com/8291395/44945330-fa391980-ae21-11e8-80ce-7643b587a11e.jpg)

![clipboard02](https://user-images.githubusercontent.com/8291395/44945334-058c4500-ae22-11e8-8517-aa565cc85898.jpg)



# おまけ

## kivy用requirements.txt

これくらいみんな自前で作ってる気がしますが(´・ω・`)

```txt:requirements.txt
pip
wheel
setuptools
docutils
pygments
pypiwin32 
kivy.deps.sdl2
kivy.deps.glew
kivy.deps.gstreamer
kivy.deps.angle
kivy
```

`python -m pip install -r requirements.txt` でkivyに必要なライブラリをインストール出来ます。
gstreamerは動画や音声周りのライブラリのようなので、そのあたりを使う予定が無ければインストールしなくても良いようです。



# 参考資料

[Popup — Kivy 1.10.1 documentation](https://kivy.org/doc/stable/api-kivy.uix.popup.html)

[Installation on Windows — Kivy 1.10.1 documentation](https://kivy.org/doc/stable/installation/installation-windows.html#installation)

[requirements.txt を使って pip upgrade — Elliptium](http://tink.elliptium.net/2014/01/15/pip_upgrade_with_a_requirements_txt.html)