Kivy 超入門(2):ウィンドウのレイアウト,入力フォーム

前回に引き続き,Harrison Kinsley さんのkivyの解説を日本語の文章にして,補足を加えながら説明していきたいと思います.今回はウィジェットとラベル,という内容でして,最終的にはログイン画面をつくることが目標です.ログインシステムでなくログイン画面です.その際にレイアウトと入力フォームというGUIアプリの必須知識を身に着けれます.

前もって気づいておけばよかったこととして,kivy をスムーズに理解するためには「クラスの継承」という知識を理解しているととてもスムーズになるのかと思います.筆者はクラスの継承をそこまで活用したことがなかったのですが,今回の投稿を書きながら勉強させていただきました.なので,クラスの継承の部分の説明が”弱い”かと思われますが,ご了承いただきたいです...







英語がわかる方はもとのこちらの動画を見た方が早いでしょう

ユーザー名の入力フォーム作成

前回IntorKivyというクラスを作って,そこでreturn(画面に返してくれる)したのはLabelという機能でした.これはラベルという言葉のイメージ通り,文字ボックスを表していました.今回はログイン画面を作るのですが,User name-PASS の組み合わせをを入力できるようにしなければならないので,ただ単に文字列を表示させるLabel という機能では実現できません.そこで,ログイン画面のクラスを新しく作り,それをreturnするようなプログラムを作る方針で行きます.まずは次のように書いて見ましょう.

from kivy.app import App
from kivy.uix.label import Label

class IntroKivy(App):
    def build(self):
        return LoginScreen()

if __name__ == "__main__":
    IntroKivy().run()

これを実行すると,まだ完成したプログラムでないため,エラーになります.ハイライトしている6行目が前回のHello,World!との変更点になります.前回は文字を表示させるだけでよかったので,すでに app.py に備わっているLabelという関数をreturn(返す,実行)するだけでよかったです.しかし,「ログイン画面を表示する」という機能はkivyにはデフォルトで存在しないため,自分で新しく作る必要があります.つまり,それを作った後にIntroKivyのreturn のあとに書くことで,ログイン画面を表示させるアプリを作る方針です.今この段階では,return LoginScreen() というところで「LoginScreenという名前の関数もしくはクラスをreturnさせますよ」という指示を作っただけになります.LoginScreen というクラスはこれから作っていく(まだ作っていない)ので,この段階では実行しても「LoginScreenが定義されていません」とエラーが出ます.

では LoginScreen というクラスを作っていきましょう.

GridLayout, TextInput というクラスをインポート

まず,プログラムの最初にいろんな機能(クラス)をインポートする部分を次のように変更(追加)します.

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput

新しく加わった3,4行目が,今回新しく使う機能になります.「kivy.uix.gridlayout」や「GridLayout」が具体的に何を意味しているのかは,前回の投稿のkivy.appが何を意味しているか説明している部分を参考にしてください.

GridLayout というクラスの機能について少し触れます.GridLayoutというクラスには文字通りウィンドウのレイアウト(配置)を決めてくれる機能です.HTMLなどを知っている方は,GridLayoutはCSSのことだと思っていただいてよいでしょう.「ウィンドウの中を何行にするか」「ウィンドウの大きさはどれぐらいにするか」「余白はどれぐらいに設定するか」などを決める部分になります.

思い出していただきたいのですが,今作ろうとしている画面はログイン画面になります.ログイン画面には文字を入力しなければなりませんね.pythonではinputというきのうでユーザーからの入力を可能にしていましたが,inputは標準入力(よくわからない方は,「実行されるコマンドライン」とでもイメージしてください)からの入力を受け取る機能であって,kivyによって作られたウィンドウからの入力を受ける機能ではありません.なので,kivyによって作られたウィンドウからの入力を可能にするために,4行目で TextInput というクラスをインポートしています.大文字に気を付けましょう.

ユーザ名入力フォームのクラスを作成

これまでで,必要な機能のimportはできました.あとはLoginScreenというクラスを作成するのみです.まず次の段階までを実行してみましょう.

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput

class LoginScreen(GridLayout):
    def __init__(self, **kwargs):
        super(LoginScreen, self).__init__(**kwargs)
        self.cols = 2
        self.add_widget(Label(text="Username:"))
        self.username = TextInput(multiline=False)
        self.add_widget(self.username)

class IntroKivy(App):
    def build(self):
        return LoginScreen()

if __name__ == "__main__":
    IntroKivy().run()

これを実行すると次のような画面が出てきます.
kivy2_1
左側はただ黒い背景にUsername:と表示されているだけですが,右側はクリックするとカーソルが出てきて文字が打てるようになります(ここまでだと日本語非対応).

class LoginScreen(GridLayout):

「今からGridLayoutというクラスを継承したLoginScreenというクラスを作成します.」という宣言になっています.

    def __init__(self, **kwargs):

「def __init__(initの両側はアンダーバー2個)」というのは__init__関数を定義しますという宣言ですね.Python のクラスの__init__は,クラス(今の場合はLoginScreen)が呼び出された(作成された)とき実行される機能になります.そのかっこの中(引数といいます)は,self は LoginScreen という自分のクラス(オブジェクト)の事を指しています.そしてもう一つの因数の「**kwargs」というものですが,ぶっちゃけなくっても大丈夫です.上のプログラムから「**kargs」というのをすべて消しても全く同様のプログラムが動きます.「**kwargs」の意味は,これはちょっと難しくって「残りの引数を辞書形式でkwargsという変数に格納する」という意味になります.次のサイトが自分にはわかりやすかったので記載させていただきます.ただし,こちらはpythonのバージョンが2.x系の説明なので,python3.x を使っている方はprint文を各自print()に変更して読んでいきましょう.

Python超入門(番外1):*arg と **kwarg

        super(LoginScreen, self).__init__(**kwargs)

「super」はkivy関係なしのpython の機能で,継承したクラス(親のクラス)を意味しています(基底クラスの参照とかいうらしいです).今書いている LogicScreen というクラスで継承しているのは GridLayout というクラスですので,この行では GridLayout の中の __init__(**kwarg)というプログラムを動かしています.プログラムの中身を読んでみると,内容はレイアウトの初期化を行っているようです.これからはLoginScreenの性質をいろいろ決めていきます.

レイアウトの設定

        self.cols = 2

LoginScreenのレイアウトを2列にしています.

ラベル,ウィジェットの配置

        self.add_widget(Label(text="Username:"))

self.add_widget()」で,先ほど2列に分けた最初の方(左側)に,かっこの中にあるクラス(ウィジェット)を置いています.かっこの中は前回習ったLabelの機能そのものです.

        self.username = TextInput(multiline=False)
        self.add_widget(self.username)

こ2行目のself.add_widget では,今度は残った右側の列にアプリを置くことを言っています.そのアプリは「self.username」となっていますが,それはすぐ上の行で定義されています.つまり,この行は次のように一行で書くこともできます.

        self.add_widget(TextInput(multiline=False))

先ほどとの違いは,Label でなく TextInput というクラスに変わっていることです.TextInput というクラスは先ほども書いた通り,kivyによって作られたウィンドウからの入力を可能にするための機能です.オプションの「multiline=False」について,TextInput では列に文字をたくさん入力していくと自動改行されず永遠と横に続くのですが,その際にEnterを押して改行を許す(列に何行も書くことを許す)場合にはTrueを,一行のみ書くことにするときにはこのようにFlaseとします.このFlaseをTrueに変えて実行し,文字を入力してEnterを押してみると確かに改行されることがわかります.

パスワードの入力欄を追加

LoginScreenの中を次のようにもう少しだけ追加変更してみましょう.

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput

class LoginScreen(GridLayout):
    def __init__(self, **kwargs):
        super(LoginScreen, self).__init__(**kwargs)
        self.cols = 2
        self.add_widget(Label(text="Username:"))
        self.username = TextInput(multiline=False)
        self.add_widget(self.username)

        self.add_widget(Label(text="Password:"))
        self.password = TextInput(multiline=False)
        self.add_widget(self.password)

class IntroKivy(App):
    def build(self):
        return LoginScreen()

if __name__ == "__main__":
    IntroKivy().run()

実行結果は次のようになります.
kivy2_2
先ほどとくらべて,パスワードを入力する欄がもう一列増えました.このようにself.add_widgetを何度も書いていくと自動的に列が増えていきます.しかし,行数は最初にself.colと指定した分だけしか出てこないので注意しましょう.

さて,これでログインフォームができました,かと思いきやこれでは明らかに不十分です.理由はパスワードが見えてしまっているからです.self.password でTextInputをそのまま上のUsernameのものをコピペしてきているだけなので,当たり前っちゃ当たり前ですね.通常パスワードの入力欄はほかの人から見えないようになっています.なので,次のように「password」というオプションを追加してみましょう.

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput

class LoginScreen(GridLayout):
    def __init__(self, **kwargs):
        super(LoginScreen, self).__init__(**kwargs)
        self.cols = 2
        self.add_widget(Label(text="Username:"))
        self.username = TextInput(multiline=False)
        self.add_widget(self.username)

        self.add_widget(Label(text="Password:"))
        self.password = TextInput(multiline=False, password=True)
        self.add_widget(self.password)

class IntroKivy(App):
    def build(self):
        return LoginScreen()

if __name__ == "__main__":
    IntroKivy().run()

実行結果に文字を入力してみると,確かに暗証番号が隠れています.
kivy2_3

二重パスワードを追加

ついでに,必要な暗証番号をもう一つ加えて二つにし,より安全な認証にしてみましょう.もうお分かりかと思いますが,答えは次のようになります.

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput

class LoginScreen(GridLayout):
    def __init__(self, **kwargs):
        super(LoginScreen, self).__init__(**kwargs)
        self.cols = 2
        self.add_widget(Label(text="Username:"))
        self.username = TextInput(multiline=False)
        self.add_widget(self.username)

        self.add_widget(Label(text="Password:"))
        self.password = TextInput(multiline=False, password=True)
        self.add_widget(self.password)

        self.add_widget(Label(text="Two Factor Auth:"))
        self.tfa = TextInput(multiline=False, password=True)
        self.add_widget(self.tfa)

class IntroKivy(App):
    def build(self):
        return LoginScreen()

if __name__ == "__main__":
    IntroKivy().run()

実行結果に文字を入れてみました.
kivy2_4

余談

自分がクラスの継承をもっとよく理解してればいい投稿になったんだろうなととても残念,悔いが残る.が,今回の機会にある程度は勉強できたので,もっと理解したらそのとききれいに書き直せばいいやと開き直りました.kivyの使い方を理解するって,もしかして,いくつかのクラスの使い方さえしっかりわかってしまえばどうってことはないのかもしれない(じゃぁ,とっとと何か開発してみるよと言われると,やっぱそんな簡単なものじゃない気もする).ここまで進むと,書いているうちに割とkivyの奥まで見ることになって,個人的にはとても面白かったです.

次回はkvファイルというものを扱います.今までのレイアウトだと,環境のデフォルトの値によって大きい画面だったり小さい画面だったりしたと思いますが,その大きさの調節などを行います.HTML の細かい位置関係を CSS指定するみたいなもんですね.

間違いなどの指摘がありましたら是非コメントなどに残していただきたいです.長々とした駄文でしたが,読んでいただきありがとうございます.


Creating Apps in Kivy

このブログについて

IAtLeX です.ブログをはじめてさほど時間がたっていないので,未熟な内容が多々あるかと思いますが,それも時間が解決してくれるはず...Python系の記事を着々と充実させていきたいです.投稿主についてはこちらを参照してください.

このブログについて - http://iatlex.com/about_blog/

コメント

  1. NA より:

    self.add_widget(Label(Two Factor Auth:”))

    self.add_widget(Label(text=”wo Factor Auth:”))

    ではないかと思います。

    1. IAtLeX より:

      ご指摘ありがとうございます!注意を払って書いたつもりでしたが、こんな大きなミスがあるとは… 2017/05/02/14:55 に訂正いたしました。

コメントを残す

*