PythonでTUIアプリを作ろう ( その4 Buttonを配置する )

UrwidのButtonオブジェクトとイベントを理解しよう。


それでは、最初のWidgetとしてButtonを使ってみましょう。ユーザーインタフェースにおいて、Buttonは、特に説明する必要もない一般的な構成部品です。前回作成したクラスのFrameにButtonを配置していきます。


Buttonオブジェクトの生成

UrwidButtonは、下記のように生成します。

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について説明しましょう。


ソースコードについて

GitHubに登録しました。今回のコードは、Section04となります。