魅力的なPython: PythonでのTKプログラミング

PythonのGUIライブラリーを初めて使用する場合のヒント

David Mertzが、TKとTkinterラッパー (PythonのGUIライブラリー) を、詳しいコメント付きのソース・コード・サンプルを用いて紹介します。分かりやすいように、例として、今までの記事で使用したTxt2HtmlフロントエンドのGUIポートを用いています。もちろん、連載をお読みになっていることを前提としています。

David Mertz, Ph.D (mertz@gnosis.cx), Author, Gnosis Software, Inc.

Photo of David MertzDavid Mertz氏は多くの分野で活躍しています。ソフトウェア開発や、それについて著述もしています。その他、学術政策理念について分野を問わず、関係する雑誌に記事も書いています。かなり以前には、超限集合論、ロジック、モデル理論などを研究していました。その後、労働組合組織者として活動していました。そして、David Mertz氏自身は人生の半ばにもまだ達していないと思っているので、これから何かほかの仕事をするかもしれません。



2000年 12月 01日

GUIプログラミングを始める一番簡単な方法として、ScripticsのTKとTkinterラッパーを用いる方法を紹介したいと思います。developerWorks のCursesプログラミングで説明した、cursesライブラリーと比較しながら説明していきましょう。cursesはテキスト・コンソールを対象とし、TKはGUIをインプリメントするものであるにもかかわらず、この2つのライブラリーのインターフェースは驚くほどよく似ています。どちらのライブラリーを使用する場合も、ウィンドウとイベント・ループ、そして利用可能なウィジェットを参照できる基礎知識が必要です (もちろん、適切な参考資料と適量の練習も必要です)。

cursesに関する説明の場合と同様、今回は、Tkinterそのものの機能についてしか説明しません。Tkinterは多くのPythonの配布資料に同梱されているので、おそらくサポート・ライブラリーや他のPythonモジュールをダウンロードする必要はないでしょう。最後の参考文献に、より高度なユーザー・インターフェース・ウィジェットのコレクションを紹介していますが、Tkinterだけでも、独自の高度なウィジェットの作成を含め、多くのことを実行できます。基本Tkinterモジュールを学習すれば、もっと高度なウィジェット・コレクションを使用する場合にも重要な、TKの考え方が分かるようになるでしょう。

TKの概要

TKは、TCL言語に非常に近い関係にある、幅広く使用されているグラフィック・ライブラリーです。TKもTCLもJohn Ousterhoutが開発したものです。TKは1991年にX11ライブラリーとしてデビューしましたが、後に、人気が高いGUIのほとんどすべてに移植されました (Pythonに「標準」GUIがあるのと同じようなものです)。現在では、それほど人気のない言語だけでなく、人気の高い言語にも、TKバインディング (Tkinterモジュール) が備え付けられています。

説明を始める前に、正直なところをお話しておけば、わたしは、TKプログラミングの経験が豊富というわけではないのです。実際、わたしのTKプログラミング経験は、このコラムを書き始める約3日前に始まったにすぎません。この3日間、困難に遭遇しなかったわけではありませんが、最終的には、Tkinterをかなりよく理解できたように感じました。ここで忘れてはいけないことは、TKとTkinterラッパーはどちらも非常によく出来ており、使いやすく、まさにGUIプログラミングの一番分かりやすい入門用ツールだ、ということです。


テスト・アプリケーションから始める

テスト・アプリケーションとして、Txt2Html、つまりこれまでの多くのコラムで使用してきたファイル・フォーマット変換プログラムのラッパーを使用します (参考文献を参照してください)。Txt2Htmlを実行する方法はいくつかありますが、ここでのラッパーは、コマンド・ラインからTxt2Htmlを実行することに基づいています。アプリケーションはバッチ処理として実行し、コマンド・ライン引数を用いて、実行すべき変換の詳細を指定します (対話型選択画面のオプションをユーザーに提供するのは後で行うと良いでしょう。このオプションを使用すると、ユーザーには変換オプションの画面が示され、かつ変換を実際に行う前には、選択したオプションのフィードバックが画面に示されます)。

tk_txt2htmlは、ドロップダウンとネスト・サブメニューを持つトップバーをベースとしています。インプリメンテーションの詳細を除けば、これはcursesバージョンに非常によく似ています ( 魅力的なPython:Cursesプログラミング を参照してください)。TKは少ないコード量で多くの機能を実行するものですが、tk_txt2htmlとcurses_txt2htmlが同じ領域のものであることは明らかです。たとえば、TKでは、メニューのような機能は、最初から作成するのではなく。組み込みTkinterクラスに依存することができます。

構成オプションの設定のほかに、TKラッパーには、TK Textウィジェットを組み込んだスクロール・ヘルプ・ボックス (Messageウィジェット付きの情報ボックス) と、TKのダイナミック・ジオメトリー管理を実行するヒストリー・ウィンドウも含まれています。そして、ほとんどの対話式アプリケーションのように、ラッパーはTKのEntryウィジェットを用いてユーザー入力を受け入れます。

では、コードの細かい説明に入る前に、実行中のアプリケーションを見てみましょう。

tk_txt2html.pyのスクリーンショット

基本を学ぶ

Tkinterプログラムが実行しなければならないことは、実際には3つしかありません。

最小限の [Tkinter] プログラム
import Tkinter        # import the Tkinter module
root = Tkinter.Tk()   # create a root window
root.mainloop()       # create an event loop

これは、まぎれも無く本物のTkinterプログラムです ("hello world" さえも管理しないなんて役に立たない、ということは気にしないでください)。このプログラムが実行しなければならないことは、ウィジェットを作成して、ルート・ウィンドウを生成することだけです。この方向で拡張すれば、プログラムのルート.mainloop() メソッド呼び出しは、プログラマーが特に手を加えることなく、どんなユーザー対話にも対応できるでしょう。


main() 関数

tk_txt2html.pyのもっと現実的なmain() 関数を紹介しましょう。ここで実行したいのは、from Tkinter import ではなく、John Graysonのimport Tkinter ステートメントであることに注意してください (参考文献にリストされている彼の著書を参照してください)。これは、ネームスペースへの悪影響 (from ... import ステートメントによく見られる警告) を心配しているというよりは、Tkinterクラスの使用については明示的にしておきたいからです。Tkinterクラスを自分独自の関数やクラスと取り違える危険を犯したくはありません。少なくとも最初のうちは、上に述べたのと同じことを行うことをお勧めします。

tk_txt2html main() 関数
def main():     global root, history_frame, info_line
root = Tkinter.Tk()
root.title('Txt2Html TK Shell')
init_vars()
#-- Create the menu frame, and menus to the menu frame
menu_frame = Tkinter.Frame(root)
menu_frame.pack(fill=Tkinter.X, side=Tkinter.TOP)
menu_frame.tk_menuBar(file_menu(), action_menu(), help_menu())
#-- Create the history frame (to be filled in during runtime)
history_frame = Tkinter.Frame(root)
history_frame.pack(fill=Tkinter.X, side=Tkinter.BOTTOM, pady=2)
#-- Create the info frame and fill with initial contents
info_frame = Tkinter.Frame(root)
info_frame.pack(fill=Tkinter.X, side=Tkinter.BOTTOM)
# first put the column labels in a sub-frame
LEFT, Label = Tkinter.LEFT, Tkinter.Label   # shortcut names
label_line = Tkinter.Frame(info_frame, relief=Tkinter.RAISED, borderwidth="1")
label_line.pack(side=Tkinter.TOP, padx=2, pady=1)
Label(label_line, text="Run #", width="5").pack(side=LEFT)
Label(label_line, text="Source:", width="20").pack(side=LEFT)
Label(label_line, text="Target:", width="20").pack(side=LEFT)
Label(label_line, text="Type:", width="20").pack(side=LEFT)
Label(label_line, text="Proxy Mode:", width="20").pack(side=LEFT)
# then put the "next run" information in a sub-frame
info_line = Tkinter.Frame(info_frame)
info_line.pack(side=Tkinter.TOP, padx=2, pady=1)
update_specs()
#-- Finally, let's actually do all that stuff created above
root.mainloop()

この簡単なmain() 関数でも注意すべき点がいくつかあります。

  1. どのウィジェットにも親があります。ウィジェットを作成する場合は常に、インスタンス作成の最初の引数が新規ウィジェットの親となります。
  2. 別のウィジェット作成引数がある場合は、名前で引き渡されます。このPython機能により、オプションを指定したり、デフォルトのままにしたりする場合のフレキシビリティーが向上します。
  3. ウィジェット・インスタンス (Frame) の数はグローバル変数です。適用範囲を理論的に変えないように関数間で変数を引き渡すことによって、これらの変数をローカルにすることもできますが、メリットよりも問題の方が大きくなります。さらに、これらの基本UI要素をグローバルにしておけば、あらゆる関数でそれらの要素を利用することができます。ただし、独自のグローバル変数には適正な命名規則を使用するようにしてください (前もって警告しておきますが、Python開発者はハンガリー語の表記が嫌いなようです)。
  4. ウィジェットを作成したら、ジオメトリー・マネージャー・メソッドを呼び出して、TKにウィジェットの配置場所を認識させます。特にウィンドウをサイズ変更したり、ウィジェットを動的に追加したりする場合には、TKが魔法をたくさん使って詳細情報を計算してくれます。しかし、どのような場合でも、どの魔法を使用するかをTKに認識させることが必要です。

ジオメトリー・マネージャーを適用する

TKには、.pack().grid()、および.place() の3つのジオメトリー・マネージャーが用意されています。tk_txt2htmlで使用するのは最初の2つだけですが、細かい (言い換えれば、非常に複雑な) 制御には.place() が役立ちます。大抵の場合は、.pack() を使用することになるでしょう。

.pack() メソッドを引数なしで呼び出すことができるのは確かですが、そのようにする場合は、ディスプレイのどこかでウィジェットを終了しなければならない可能性があるため、おそらく.pack() には何らかの情報を提供する必要があるでしょう。引数の中で一番重要なのは、side です。有効な値はLEFT、RIGHT、TOP、およびBOTTOMです (これらはTkinterのネームスペースでは変数であることに注意してください)。

.pack() の魔法の多くは、ウィジェットをネストできることが土台となっています。特に、Frameウィジェットは (各種タイプの境界線を表示する場合に) 他のウィジェットのコンテナーの役割を果たします。そのため、希望する方向の複数のフレームをパックして、各フレーム内に他のウィジェットを追加することはとりわけ簡単です。フレーム (および他のウィジェット) は、.pack() メソッドの呼び出し順にパックされます。そのため、2つのウィジェットが両方ともside=TOP を求めた場合は、最初に要求した方に、最初に提供されます。

tk_txt2htmlでは、.grid() も少し使用します。グリッド・ジオメトリー・マネージャーは、親ウィジェットを透明の格子で覆います。ウィジェットが.grid(row=3, column=4) を呼び出した場合は、3行目、4列目の場所に配置することを親に要求していることになります。親の総行数と総列数は、すべての子による要求を確認することによって計算されます。

ウィジェットがディスプレイに表示されていない状態にならないよう、専用のウィジェットにはジオメトリー・マネージャーを適用することを忘れないでください。


メニュー

Tkinterでは、メニューを簡単に作成できます。ここで紹介する例は非常に簡単ですが、必要であれば、別のフォント、ピクチャー、チェック・ボックスなど、あらゆる種類の子ウィジェットからなるメニューを生成できます。ここで、tk_txt2htmlのメニューは、前述の行ですべて作成されます。

menu_frame.tk_menuBar(file_menu(), action_menu(), help_menu())

この行だけでは、簡単すぎてごまかされた気持ちになるかもしれません。実行しなければならない作業の大半は、*_menu() という関数で行われます。一番簡単な関数を説明しましょう。

ドロップダウン・メニューを作成する
def help_menu():
help_btn = Tkinter.Menubutton(menu_frame, text='Help', underline=0)
help_btn.pack(side=Tkinter.LEFT, padx="2m")
help_btn.menu = Tkinter.Menu(help_btn)
help_btn.menu.add_command(label="How To", underline=0, command=HowTo)
help_btn.menu.add_command(label="About", underline=0, command=About)
help_btn['menu'] = help_btn.menu
return help_btn

ドロップダウン・メニューは、Menuウィジェットを子とするMenubuttonウィジェットです。Menubuttonは、.pack() によって (または.grid() などによって) 適切な場所にパックされます。そして、Menuウィジェットは、.add_command() メソッドによって追加された項目から成り立ちます (前述のMenubuttonのディクショナリーへの奇妙な割り当てに注意してください。これは無視しましょう。ここでは何も考えずに手順に従い、自分のコードでも同じことを行ってください)。


ユーザー入力を取得する

次に紹介する例では、Labelウィジェットが出力を表示する方法を示します (TextウィジェットとMessageウィジェットの例の出典については、参考文献を参照してください)。フィールド入力の基本ウィジェットはEntryです。使い方は簡単ですが、テクニックは、以前Pythonのraw_input() やcursesの.getstr() を使用した場合に習ったこととは少し異なります。TKのEntryウィジェットが戻すのは、代入できる値ではありません。代わりに、引数を取ることでフィールド・オブジェクトを生成します。たとえば、次の関数を使用すると、ユーザーは入力ファイルを指定できるようになります。

ユーザー・フィールド入力を受け取る
def GetSource():
get_window = Tkinter.Toplevel(root)
get_window.title('Source File?')
Tkinter.Entry(get_window, width="30",
  textvariable=source).pack()
Tkinter.Button(get_window, text="Change",
   command=lambda: update_specs()).pack()

ここで、注意すべき点がいくつかあります。新しいToplevelウィジェットと、この入力用のダイアログ・ボックスを作成し、textvariable 引数を用いてEntryウィジェットを作成することで入力フィールドを指定しました。しかし、待ってください。もっとあります。

textvariable 引数が指定するのは、単純なストリング変数ではありません。StringVarオブジェクトです。この場合、main() から呼び出されたinit_vars() 関数には、次の行が含まれています。

source = Tkinter.StringVar()
source.set('txt2html.txt')

これで、ユーザー入力を取得できるオブジェクトが作成され、そのオブジェクトに初期値が提供されます。このオブジェクトは、リンクするEntryオブジェクト内で変更が加えられるたびにすぐに修正されます。変更は、1つの読み取りが終了した時点ではなく、Entryウィジェット内でraw_input() というスタイルのキー・ストロークがあるたびに発生します。

ユーザーの入力値で何かを実行する場合には、StringVarインスタンスの.get() メソッドを使用します。次に例を示します。

source_string = source.get()

まとめ

ここで紹介したテクニックと、アプリケーション・ソース・コード全体で使用したテクニックを使って、Tkinterプログラミングを始めましょう。少しプログラミングしてみれば、難しくないことが分かるでしょう。TKライブラリーはPython以外の多くの言語からもアクセスできるというメリットがあるので、PythonのTkinterモジュールで学んだことは、他の言語にも通用するはずです。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=231478
ArticleTitle=魅力的なPython: PythonでのTKプログラミング
publish-date=12012000