PythonでTUIアプリを作ろう ( その4 Buttonを配置する )
UrwidのButtonオブジェクトとイベントを理解しよう。
それでは、最初のWidgetとしてButtonを使ってみましょう。ユーザーインタフェースにおいて、Buttonは、特に説明する必要もない一般的な構成部品です。前回作成したクラスのFrameにButtonを配置していきます。
Buttonオブジェクトの生成
UrwidのButtonは、下記のように生成します。
class urwid.Button(label, on_press=None, user_data=None)
# 使用例
btn_t = urwid.Button("T", self.push_t)
labelは、Button上に表示するテキスト、on_pressは、押されたときに呼び出されるルーティン、user_dataは、そのルーティンに渡されるパラメータになります。
画面に配置してみる。
それでは、Header部(1行を想定)にButtonを3つ配置してみます。こんな画面を作成していきます。labelは、画面幅が小さくなっても1行に入るよう、1文字に限定してみました。
同時に、ButtonのEventに対応するルーティンもそれぞれ用意しておきます。今回は、ルーティンに渡すパラメータはなしとしましょう。
Button生成とEventは、こんなコーディングになるでしょう。
def push_t(self, button=None):
self.foot_text.set_text("Pushed 'T'")
:
:
btn_t = urwid.Button("T", self.push_t)
(1) Header上のコンテナにButtonを配置する。
それでは、3つのButoonと、それぞれのEventをFrameのHeader部に追加していきます。
Buttonを横方向に配置する場合、GridFlowというコンテナを用意して、その上にButtonを載せていきます。
class urwid.GridFlow(cells, cell_width, h_sep, v_sep, align)
# 使用例
header = urwid.GridFlow([btn_t, btn_s, btn_q], 6, 1, 1, 'left')
cellsは、載せるWidgetのList、cell_widthはそれぞれの幅、h_sep、v_sepは間隔、alighは位置寄せです。くわしくは上のリンクを見てください。
(2) Footer部にメッセージ表示する。
Frameの下部(1行を想定)にFooterを用意し、Buttonが押されたときには「“Pushed ‘T’"」のようなメッセージを表示することとしましょう。
以上をまとめると、下記のクラスとなります。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# s04_01.py(ButtonとそのEventを追加したクラス)
#
import urwid
class Application:
main_loop = None
def __init__(self):
pass
def push_t(self, button=None):
self.foot_text.set_text("Pushed 'T'")
def push_s(self, button=None):
self.foot_text.set_text("Pushed 'S'")
def exit(self, button=None):
raise urwid.ExitMainLoop()
def unhandled_keypress(self, k):
if k in ('q', 'Q'):
self.exit()
self.text.set_text(repr(k))
def doformat(self):
btn_t = urwid.Button("T", self.push_t)
btn_s = urwid.Button("S", self.push_s)
btn_q = urwid.Button("Q", self.exit)
header = urwid.GridFlow([btn_t, btn_s, btn_q], 6, 1, 1, 'left')
self.text = urwid.Text(u"Buttonテスト")
filler = urwid.Filler(self.text, 'middle')
self.foot_text = urwid.Text(u"これはフッター")
frame = urwid.Frame(filler, header=header, footer=self.foot_text)
frame.focus_position = 'header'
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()
では、実際に動かしてみましょう。
python3 s04_01.py
Widgetに属性を追加する。
さて、どうでしょうか。
確かにButtonはちゃんと動作していますが、画面が白黒で味気ないですね。すこし色気をつけていきましょう。
(1) Paletteを準備する。
最初は属性全体を定義する入れ物、Paletteを定義します。Paletteとは、Widgetに与える属性を定義したリスト形式のものです。下記のように定義し、MainLoopオブジェクトに登録しておきます。
my_palette = [
('body', 'default', 'default'),
('foot', 'white', 'dark blue'),
('button', 'yellow', 'default'),
('button_focus', 'black', 'yellow'),
]
palett内の各Tupleの要素は、下記の意味を持ちます。
1番目: 属性の名称
2番目: フォアグラウンドカラー
3番目: バックグラウンドカラー
さらに、追加で定義できるようですが、通常はここまでで良いでしょう。詳細は、Urwid TutorialのDisplay Attributesを見てください。
また、指定できる属性はManualで確認のこと。
(2) AttrMapで属性を追加する。
Widgetへの属性設定は、AttrMapで行います。まずは、Buttonでの設定例を示します。
# 設定例
urwid.AttrMap(button, 'button', 'button_focus')
‘button'や ‘button_focus'という値は、先のPaletteに登録したものと対応します。
Manulにあるように、最初のパラメータが通常属性、2番めがフォーカスが当たった時の属性になります。
Button定義と同時に属性を与える場合、下記のように設定すると良いでしょう。
btn_t = urwid.AttrMap(urwid.Button("T", self.push_t), 'button', 'button_focus')
ここでは、Buttonにはすべて同じ属性を与えることとし、生成部分を一つにまとめてしまいましょう。
def create_mybutton(label, callback, user_data=None):
button = urwid.Button(label, on_press=callback)
return urwid.AttrMap(button, 'button', 'button_focus')
ただ、ここで生成したオブジェクトは、AttrMapで修飾されたオブジェクトであり、Buttonオブジェクトではありません。元のオブジェクトにアクセスしたい場合は、btn_t.base_widgetのような形で使用します。
それでは、属性を追加した画面の全体像を示しましょう。
MainLoopの2番めのパラメータとして、my_paletteが追加されていることに注意してください。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# s04_02.py(ButtonとそのEvent、さらに属性を追加したクラス)
#
import urwid
my_palette = [
('body', 'default', 'default'),
('foot', 'white', 'dark blue'),
('button', 'yellow', 'default'),
('button_focus', 'black', 'yellow'),
]
def create_mybutton(label, callback, user_data=None):
button = urwid.Button(label, on_press=callback)
return urwid.AttrMap(button, 'button', 'button_focus')
class Application:
main_loop = None
def __init__(self):
pass
def push_t(self, button=None):
self.foot_text.set_text("Pushed 'T'")
def push_s(self, button=None):
self.foot_text.set_text("Pushed 'S'")
def exit(self, button=None):
raise urwid.ExitMainLoop()
def unhandled_keypress(self, k):
if k in ('q', 'Q'):
self.exit()
self.txt.set_text(repr(k))
def doformat(self):
btn_t = create_mybutton("T", self.push_t)
btn_s = create_mybutton("S", self.push_s)
btn_q = create_mybutton("Q", self.exit)
header = urwid.GridFlow([btn_t, btn_s, btn_q], 6, 1, 1, 'left')
self.txt = urwid.Text(u"Buttonテスト")
filler = urwid.Filler(self.txt, 'middle')
self.foot_text = urwid.Text(u"これはフッター")
footer = urwid.AttrWrap(self.foot_text, "foot")
frame = urwid.Frame(urwid.AttrWrap(filler, 'body'), header=header, footer=footer)
frame.focus_position = 'header'
return frame
def run(self):
self.main_loop = urwid.MainLoop(self.doformat(), my_palette, unhandled_input=self.unhandled_keypress)
self.main_loop.run()
def main():
Application().run()
if __name__=="__main__":
main()
下記のコマンドで実行です。
python3 s04_02.py
ほんの少しですが、華やかになりましたね。
Urwidのマウスサポートに驚く
はじめてUrwidのButtonをテストしていて驚いたのは、何もしなくてもマウスがサポートされていることでした。
今回のプログラムを動かし、マウスでButtonをクリックしてみてください。マウス関係のイベントは何も定義していないのに、ちゃんと動くことがわかるでしょう。
これは同時に、タッチ対応のシステムであれば、画面タッチも使えるということです。Androidのスマートフォンで動作させた場合、タッチ機能が使えるのは嬉しいですね。
これで、Buttonオブジェクトの説明はおしまい。
次回は、UrwidのListBoxについて説明しましょう。