step01 GUIプログラミングとは

『山之口洋の極・楽 python 講座【基礎編】』では、python言語を使い、プログラミング(アプリ開発)の基礎を学んだ。テキスト処理に強く、洗練された構文を持つ言語であることが実感できたと思う。
基礎の学習は、コマンドプロンプトというCUI(Character User Interface)環境で行われた。プログラムが上から順に1行ずつ読まれ、実行される様子が目に見えるので、言語の基本的な構文(文法)を学ぶのに適しているからだ。学習の方法も、写経(訳が分からないままに書き写す)が主で、それに外部仕様を与えられてプログラムを作る第2のステップを少しだけ加味した程度だった。
この『極・楽 python 講座【応用編】』では、学ぶ対象も方法も、【基礎編】とは様変わりする。
開発する対象は、Windows(やAndroid、iOS)のようなGUI(Graphical User Interface)環境で動作するプログラムである。コマンドプロンプトという暗い世界から、グラフィックス(絵)やサウンド(音)を駆使した明るい世界にようやく踏み出せるわけだ。
だが、そのためには、これまでに学んだ内容に加え、つぎの項目を習得しなくてはならない。

イベント駆動プログラミング(event driven programming)
CUI環境では、プログラムを上から順に、1行ずつ処理していくのに対し、GUIのアプリは、いつ、どこから来るか分からないユーザの指示(イベント)に適切に対応しなくてはならない。イベントとは、マウス操作や、キー入力や、他のアプリケーションウィンドウからの通知などである。そのため、GUIプログラムには、発生したイベントを検知するイベントループと、イベントの種類毎にそれらを処理し、ユーザとのインタラクション(データのやり取り)を行うイベントハンドラが必ず含まれる。
クラスとオブジェクト
【基礎編】のstep09では、プログラムを構成するための、最も基本的なパラダイムとして、関数モジュールを習得した。これらは、構造的プログラミングという構成論に属する仕組みであり、読みやすくデバッグしやすいプログラムを可能とする。
本ステップではこれらに加えて、より先進的な構成論であるオブジェクト指向プログラミングに属する枠組みとして、クラスオブジェクトを習得する。詳細は「step03 クラスとオブジェクト」で解説する。

学習方法についても、これから先はかなり多様な内容を習得しなければならないので、効率を重視し、スピードアップしなければならない。サンプルプログラムを書き写し、実行してみる写経は、入門段階では欠かせないが、これからは効率が悪すぎる。言語の基礎はすでに身についているはずだから、1文ずつではなく、プログラムという「文章」全体を眺めて、意味を理解できる読解力が重要になってくる。つまり写経から読経へとステップアップするわけだ。

tkhello:はじめてのGUIプログラム

【基礎編】では、はじめてのpythonプログラムとしてhello.pyを作った。このstepでは、はじめてのGUIプログラムとしてtkhello.pywを作ろう(リスト1-1に示す)。名前から分かるように、GUI版の"Hello World!"である。


リスト1-1: tkhello.pyw

GUIのプログラムを作るには、GUIのツールキットという、ウィンドウのパーツを集めたモジュールが必要であり、いくつかの種類が存在する。ここでは、python環境に標準で付属しているtkinterを使う※1

1 tkinterモジュールのリファレンスマニュアルを添付する。英文だが、プログラミング言語関連の文書は比較的楽に読めるので、読経の途中で、分からないところはマメに確かめる習慣をつけよう。

tkinterモジュールの概要については「step04 tkinterモジュールの使い方」で扱うので、ここではGUIプログラムの基本的な形を見ておこう。
main()以外の関数定義はない。前半では、rootという変数にアプリのウィンドウを作り、そこに直接※2、Label、Text、Buttonという3つのウィジェット(widget)を並べて、アプリのウィンドウを準備している。

2 より本格的なアプリでは、ウィンドウ上にトップレベルのフレームを作り、そこにウィジェットを配置するのが一般的である。まあ、最初のうちはこれで十分だ。

「ウィジェット」とは聞き慣れない言葉だが、画面上でユーザとのインタラクションを行う各種部品のことである。TextはWindowsでいうTextコントロールではなく、メモ帳のようなテキスト編集用領域だ。
プログラムを読みながら、イベント駆動プログラムの組み立てを理解しよう。まず、イベントループは、root.mainloop()がそれだ。イベントハンドラは、Buttonのpushイベントに対応する関数が、Buttonを作成するインストラクタ内で、commandオプション引数として指定されている。この場合はシステム定義の関数だが、自分で定義した関数や、無名関数(lambdaキーワードで定義する、無名の関数)を指定することで、イベントハンドラは自由に定義できる。
プログラムの実行結果を図1-1に示す。拡張子が.pyでなく.pywになっているのは、GUI環境用のプログラムを示す。実習環境が適切に設定されていれば、ファイルをダブルクリックするだけで、コマンドプロンプト抜きでプログラムが実行されるはずだ。

図1-1: tkhello.pywの実行結果

このプログラムは動きも乏しく、あまり面白みもないサンプルだが、今後作るプログラムのテンプレート(ひな形)として使えるだろう。

デジタル時計

せっかくのGUIなので、もう少し動きのあるプログラムが書きたいかもしれない。そこで、リスト1-1のプログラム(tkhello.pyw)を少し書きかえて、デジタル時計を作ってみよう(リスト1-2を参照)。
画面構成としては、Labelウィジェットが2つあるだけで、tkhello.pywよりさらに簡単だが、ラベルに表示された文字列(日付と時刻)を更新するために、無限ループを回している。500msに1回巡回するこのループは、画面表示更新ループと呼ばれ、イベントループとは異なる。datetimeモジュールの仕様は、教科書などのドキュメントで確認すること。
このプログラムにも、自前のイベントハンドラはない。


リスト1-2: dwatch.pyw

プログラムの実行結果を図1-2に示す。非常に小さなプログラムだが、結構実用に耐える。プログラミングに慣れたら、アラーム(目覚まし)機能やスケジュール表示機能を自分で追加することもできよう。
ウィジェットの配置とウィンドウサイズの指定について、簡単に解説しておく。ここで採用しているのは、「pack方式」と呼ばれる方法だ。まず、ウィンドウの最低サイズ(幅200px、高さ100px)を決める。つぎに、2つのラベルを、横幅一杯に(fill = tk.X)、縦方向に、隙間無く並べていく(pack)。各ラベルの高さは、表示文字列のフォントやサイズから自動的に決定される。最終的に、ウィンドウサイズは、すべてのウィジェットがちょうど納まるサイズまで縦横に拡張される。
ウィジェットの配置方式には、この他、座標を指定して自前で配置する「place方式」と、縦横のマス目にウィジェットをはめ込んでゆく「grid方式」がある(詳細はstep04やリファレンスを参照)。

図1-2: dwatch.pywの実行結果

ランチャー

イベントとウィジェット、イベントハンドラの関係を学ぶために、このstepではもう1つプログラムを作ろう。アプリや自前のpythonスクリプトをボタン1つで実行できる、ランチャーと呼ばれるプログラムである(リスト1-3を参照)。


リスト1-3: launcher.pyw

このアプリには5つのボタンと、それが押された時に呼ばれるハンドラ関数だけからできている。この場合、ハンドラを呼び出すイベント(event)は、ボタン上でマウスの左ボタンがクリックされた(<Butoon-1>)というものだ。
このように、イベントとは、

など、画面上で起こりうるさまざまな出来事を指す。多くの場合、それはユーザによる操作であり、イベントを受け取る対象は、画面上に配置されたウィジェット(ウィンドウ自体も含む)である。
CUIプログラムと異なり、アプリの側からは、どのウィジェットにどのようなイベントが来るか、あらかじめ分からない。そこで、ウィジェットに割り当てるイベントハンドラに、それぞれのイベント発生時の処理を定義しておく。発生したイベントは、イベントキューと呼ばれる待ち行列に並べられ、対応するイベントハンドラはイベントループ中で自動的に呼び出されるため、イベントハンドラ関数をプログラム中で明示的に呼び出すことは滅多にない(例外もある)。
このプログラムでは、ウィジェットは5つのボタンだけで、それぞれのボタンには、 が割り当てられる。これらを整理するために、リストButtonDefが用いられる。GUIプログラムではこのように、ウィンドウ中に多くのモノ(Object)が配置されることが多い。モノはこのプログラムのようにウィジェットであったり、キャンバス上の各種図形であったりするが、それらに割り当てるパラメータを整理するために、リストディクショナリが役立つ。
5つのイベントハンドラの内訳は、 となっている。docalcやsys.exitは、他の言語のように関数名ではなく、関数自体を表す関数オブジェクトである。ハンドラ関数の引数eは、発生したイベントを表すオブジェクトである。たとえばマウスクリックのイベントでは、(e.x, e.y)がマウスカーソルのある座標を表すなど、イベントに付随する情報をこのeを介して受け取れる。

3 lambdaは、名前を持たない小さな関数を定義するための仕組み。教科書pp.123を参照のこと。

リストButtonDefを舐めるforループでは、それぞれ、

が行われる。
プログラムの実行結果を、図1-3に示す。各ボタンをクリックし、登録されたアプリケーションの起動を確認しよう。

図1-3: launcher.pywの実行結果