PythonでTUIアプリを作ろう ( その3 Python Urwidの基本オブジェクト構造 )
UrwidのMainLoop、Widget、Eventを理解しよう。
Urwidの基本機能を理解するには、まず、UrwidのWebサイトにアクセスし、ドキュメンテーションを見ることが第一歩でしょう。
最初に”Tutorial”にあるスクリプトを動かして、Urwidの基本的な動きや、画面表示について感触を掴みます。これを軽く終了した後、Manualに目を通していくというのが、最初のアプローチになると思います。
ここでは、その一助となるよう、少し補足を加えていきましょう。
基本構造を理解する
ほとんどのUIアプリケーションでは、「メッセージループを生成し、そこに生じるイベントを処理すること」が、プログラムの基本動作になりますが、Urwidもその例に漏れません。まずは、Urwidにおける登場人物ならぬ、登場オブジェクトを把握していきます。
(1) MainLoop
まずは、MainLoop。これがすべての中心となります。UrwidのManualには、下記のように図示されています。
MainLoopクラスは、Display ModuleとWidget、Eventとの仲介をする中心クラスです。Display Moduleから入力されたインプットをWidgetに伝えたり、画面レンダリングをすることなど、ほとんどの機能はこのクラスを経由して実行されます。
(2) Widget
次に、Widget。
Widgetは、画面を構成する部品です。Widgetは、層構造を取ることができ、あるwidget上に、別のWidgetが乗るような部品構造を取ることが一般的です。
上の図では、Widget “a"上に、”b”、さらに”c”、”d”、”e”というような層構造を示しています。
一般に、最下層でベースとなる部品をコンテナと呼びますが、この図ではWidget “a"がコンテナにあたります。
(3) Event
ユーザーによってキー入力などの処理が行われると、それに対応するEventが発生します。Eventは、それぞれのWidgetに伝えられていくことになります。(2)で示した構造では、“a"から順番にEventが伝搬されていきます。
実例で理解する
具体的に、Tutorialでも取り上がられている下記のスクリプトを例にしましょう。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# s03_01.py
#
import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
txt.set_text(repr(key))
txt = urwid.Text(u"Hello World")
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill, unhandled_input=show_or_exit)
loop.run()
このスクリプトは、
- 画面の最上行に"Hello World"と表示後、キー入力を待つ。
- キーが”q”か”Q”の場合、終了。
他のキーであれば、入力キーを最上行に表示。
というものです。
ここでは、最後の3行で、Widgetと、MainLoopを定義しています。
まず、Widgetですが、今回はテキスト一行のみです。Fillerについては、ここでは置いておきましょう。これをMainLoopにセットすることで、画面が作成されメッセージループに入るということになります。
unhandled_inputメソッド
つぎにEventですが、今回は表示されているText Widgetは表示機能なので、入力に対応するメソッドを持っていません。
Urwidでは、
「Widgetが扱わないイベントは、最終的にunhandled_inputメソッドに渡る」
という構造になっているので、必要な処理はここに記述することになるでしょう。なお、このメソッドは事前にMainLoopクラスに設定しておく必要があります。
スクリプトの終了方法
さて、このスクリプトを終了するためには、最終行でセットしたメッセージループを抜けなければなりません。
Urwidでは、どこでも良いので、urwid.ExitMainLoop()という例外をraiseします。これにより、スクリプトが終了することになります。
オブジェクト概念図
以上の基本オブジェクト構造を、概念図にしてみました。
まとめると、
- Widgetは層構造で、その最終レベルはMainLoopと1:1に対応。(このWidgetは、MainLoopのwidgetプロパティとしてアクセスすることができる。)
- Eventは、まず上記のWidgetに渡される。その後、次のWidgetへと伝搬されていく。
- Widgetの処理対象から外れたEventは、unhandled inputメソッドが定義されていれば、そこで処理対象となる。
ということになります。
クラス化する
今回のスクリプトを、今後の展開のベースとなるようクラス化しておきます。ここでは、Applicationクラスとして実装しました。
また、最終のWidgetをFrameに変更します。Frameは、上部からHeader、Body、Footerと3つの部分から構成されるコンテナです。HeaderとFooterはなくても良いので、どのような形にでも適用できる一般的なものとなります。また、小さなことですが、表示位置も画面中央に変更しておきます。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# s03_02.py
#
import urwid
class Application:
main_loop = None
def __init__(self):
pass
def exit(self):
raise urwid.ExitMainLoop()
def unhandled_keypress(self, k):
if k in ('q', 'Q'):
self.exit()
self.text.set_text(repr(k))
def doformat(self):
self.text = urwid.Text(u"Hello World")
filler = urwid.Filler(self.text, 'middle')
frame = urwid.Frame(filler)
return frame
def run(self):
self.main_loop = urwid.MainLoop(self.doformat(), unhandled_input=self.unhandled_keypress)
self.main_loop.run()
def main():
Application().run()
if __name__=="__main__":
main()
次回は、このクラスをベースに、新たなWidgetを追加していきましょう。