Part 6: Graphical user interfaces / グラフィカル・ユーザー・インターフェイス

GUI programming and Tk / GUI プログラミングと Tk

GUI objects / GUI オブジェクト

All modern GUI toolkits, including the Tk package used in Python, are based on an object-oriented model of the user interface. Typical object types are windows, entry fields, buttons, text fields, graphics fields, menus, etc. Constructing a GUI means creating all the necessary objects, putting them together correctly, and specifying the actions associated with them.

Python で使われる Tk パッケージを含め、最近の GUI ツールキットは全て、オブジェクト指向モデルに基づくユーザーインターフェースを持っています。 典型的なオブジェクト型は、ウインドウ、入力欄、ボタン、テキスト欄、グラフィックス欄、メニュー、などです。 GUI を作るということは、必要なオブジェクトを全て創作し、それらを正しく集め、付随する動作を指定する、ということを意味します。

Event-driven computing イベント駆動型の計算

In traditional programs, the program flow is determined by the code. With a GUI, it is the user who determines what happens by activating menus, buttons, etc. This requires a different program structure: a main loop checks for user actions and calls appropriate subroutines that do the work. The main loop is terminated only when the user decides to quit the program.

旧来のプログラムでは、プログラムの流れはコードによって決定されます。 GUI では、利用者がメニューやボタンなどを操作することによって、何が起こるかが決定されます。 これには、従来とは異なったプログラム構造が必要です:メインループが利用者の操作を点検し、それに適したサブルーチンを呼びます。 メインループが停止するのは、利用者がプログラムを終了すると決めた時だけです。

User interface design / 利用者とのインターフェースの設計

This course covers only the technical aspects of creating a GUI. An equally important question is the design of a user interface, i.e. deciding what to put where and how. There is an extensive literature on this subject, but the most important recommendation is to study well-written programs and try to imitate their behaviour. The most common beginner's mistake is trying to be too innovative. The goal is not to create a GUI that packs the most features into a single window, using tricks specific to the application, but to design a GUI that users can understand quickly, based on their experience with other programs.

このコースでは、GUI を作成する際の技術的な側面だけを扱います。 同様に重要なのは、利用者とのインターフェースの設計、すなわち何をどこにどのように設置するかという問題です。 この問題には多くの文献が存在します。しかし、最も大事なこととして推奨するのは、良く書かれたプログラムを調べ、その動作を模倣してみることです。 初心者が最も犯しがちな誤りは、革新的であろうとし過ぎることです。 そのアプリケーションに独特の技を用いて、殆どの機能を一つのウインドウに詰め込む様な GUI を創造するというのではなく、むしろ利用者が他のプログラムでの経験に基づいて直ちに理解できるような GUI を設計することを目指すべきです。

Tk and Python / Tk と Python

Python does not have a user interface package of its own, but there are Python interfaces to several such packages. The most popular one is Tk, which was originally developed for Tcl, a very simple scripting language. Tk has several advantages: it is easy to use, complete, and available for all major operating systems. However, it is rather closely tied to Tcl, and although the Python interface tries to hide this as much as possible, there remain some features that don't seem to make sense with Python. An interface toolkit designed for Python from the start would probably look different.

Python は利用者とのインターフェースに独自のパッケージを持っていませんが、類似のパッケージへの Python からのインターフェースはいくつか存在します。 最も親しまれているのは Tk で、元々はとても単純なスクリプト言語である Tcl 用に開発されたものです。 Tk にはいくつかの利点があります:使用法が簡単で完全性があり、主だった全てのオペレーティングシステムで使えます。 しかし、Tk は Tcl とより密接に結び付いています。Python インターフェースからは出来るだけ隠すようにしてはいますが、Python とは相容れない様に見える側面も幾つか残っています。 初めから Python 用に設計されたインターフェースツールキットがあったら、おそらく Tk とは外見の異なるものだったでしょう。

Tk's world view / Tk の世界の概観

Tk knows about two types of objects: windows and widgets (from "window" and "gadget"). Widgets are the user interface objects that populate windows: buttons, text fields, etc. Widgets are arranged in a hierarchical order; every widget has a master (another widget or, for the outermost widget, a window), its position on the screen and many of its properties depend on its master. Windows are part of a hierarchy as well, but of a different kind: a window's position and content is completely independent of that of its master, but it disappears when its master is closed. The top of the window hierarchy is the root window, which has no master. When the root window is closed, the Tk event main loop is terminated.

Tk には二種類のオブジェクトがあります:ウインドウとウイジェット("window"と"gadget(小道具)"から来ています)です。 ウイジェットは、ウインドウ中に配置されるユーザーインターフェースオブジェクトです;全てのウイジェットはマスター(これは別のウイジェットであるか、または最外殻のウイジェットに対してはウインドウになります)を持ちます。ウイジェットのスクリーン上の位置や多くの性質はそのマスターに依存します。 ウインドウも同様の階層構造を成しますが、性質が異なります:ウインドウの位置と内容はそのマスターから完全に独立していますが、マスターが閉じられると自身も消えます。 ウインドウの階層構造の最高位には、マスターを持たないルートウインドウがあります。 ルートウインドウが閉じられると、Tk イベントの主ループは停止します。

A typical Tk application starts by constructing the widget that will be attached to the root window and running its main loop. Widgets specify functions to be called in reaction to events (user actions), and these functions are called by the Tk main loop when the events occur. These functions can do anything at all; there are no restrictions. They can open and close windows and even modify the widgets in existing windows. They can also ask Tk to terminate its main loop.

典型的な Tk アプリケーションは、ルートウインドウに付加するウイジェットを作成し、その主ループを実行することから始まります。 ウイジェットはイベント(利用者の操作)に応じて呼ばれるべき関数を指定し、これらの関数はそのイベントが発生した時に Tk の主ループから呼び出されます。 これらの関数は何をしても構いません;制限はありません。 ウインドウを開閉したり、存在するウインドウ中のウイジェットを変更することさえできます。 また、Tk の主ループを停止するよう要求することもできます。

After a widget has been created, its creator must decide where to put it within its master. Tk provides two strategies for placing widgets: the placer provides a very flexible, but also very complicated, strategy, whereas the packer is simpler to use and does most of the work by itself, at the price of some loss of flexibility. Most Tk programs use only the packer, as will all examples in this course.

ウイジェットを作成した後には、それをマスターのどの位置に配置するかを決める必要があります。 Tk はウイジェットの配置に二つの方法を提供しています: placer は非常に柔軟であると同時にとても複雑な方法です。 一方 packer は、使い方が簡単で多くを自動で行いますが、柔軟さはある程度欠いています。 殆どの Tk プログラムは packer だけを用いており、このコースの例も全てそのようにします。

A very simple example / とても簡単な例

The following program is about as simple as a Tk application can get. It opens a window containing only a button. When the button is pressed, a message is printed to the screen. The program is terminated when the window is closed.

次のプログラムは、Tk のアプリケーションとしては最も単純なものです。 ボタン一つだけのウインドウを開きます。 ボタンが押されるとスクリーンにメッセージが印字されます。 ウインドウを閉じるとプログラムは停止します。

from Tkinter import *

def report():
    print "The button has been pressed"

object = Button(None, text = 'A trivial example', command = report)
object.pack()
object.mainloop()

The first statement imports all names from the module Tkinter. Due to the large number of definitions in this module, an individual import is rather inconvenient. Then the event action function is defined, which can be any Python function. The next line creates the button widget. Its first parameter is the widget's master; None indicates the root window. The remaining parameters specify the details of the button: the text it displays, and the command to be executed when the button is pressed. Then the widget's pack() method is called, which indicates that the packer is to be used for arranging the widget. There are no indications on how to arrange the objects, because there is just one widget and thus no choice. Finally, the main loop is started.

最初の文は Tkinter モジュールから全ての名前を import します。 このモジュールには多くの定義があるので、個々に import するのは少し不便です。 次にイベント動作関数を定義します。これには任意の Python 関数が使えます。 次の行はボタンウイジェットを生成します。第一パラメータはそのウイジェットのマスターです;None はルートウインドウを示します。 残りのパラメータがボタンの詳細を指定します:表示するテキスト、ボタンが押された時に実行される命令、です。 次にウイジェットの pack() メソッドが呼ばれます。これはウイジェットを配置するのに packer が使われることを示します。 オブジェクトをどの様に配置するかについては何も指示されていません。 ウイジェットが一つだけなので選択肢が無いからです。 最後に主ループが開始されます。

Arranging widgets / ウイジェットの配置

A Tk window can contain only one widget. Of course one-widget windows are not very useful, so there is a way to construct more complicated ones: the use of frames. A frame is a widget that acts like a subwindow. It is normally invisible (although a visible border can be added), but it contains other widgets that usually aren't. A frame can contain any number of widgets of any kind.

一つの Tk ウインドウ中のウイジェットは一つだけです。 もちろん、ウイジェット一つだけのウインドウではあまり有用でないので、もっと複雑なものを作る方法があります:それにはフレームを使います。 フレームとは、サブウインドウの様に動作するウイジェットです。 フレームは通常目に見えません(ただし目に見える境界線を付けることも可能です)が、それに含まれるウイジェットは見えるものです。 フレームには任意の数、任意の種類のウイジェットを置けます。

If a frame contains several widgets, there must be some specification of how to arrange them. This information is supplied by optional keyword arguments to the method pack. The simplest specification is side=some_side, with the choice of TOP, BOTTOM, LEFT and RIGHT. The packer then tries to place the widget as close as possible to the indicated side of the frame, within the constraints imposed by other widgets. If, for example, you specify side=TOP for all widgets in a frame, they will be stacked up vertically and centered horizontally, with the first widget for which pack was called at the top.

フレームが複数のウイジェットを持つならば、それらをどのように配置するかを指定する必要があります。 この情報は、pack メソッドへのオプションのキーワード引数によって与えます。 最も簡単なものは、side=some_side で、TOPBOTTOMLEFTRIGHT から選びます。 すると packer はそのウイジェットを、他のウイジェットとかち合わない範疇で、フレームの指定された側のなるべく近くに配置しようとします。 例えば、フレーム中の全部のウイジェットに side=TOP とした場合は、それらは pack が呼ばれた最初のウイジェットを頂上にして縦に積み上げられるでしょう。 横方向には中央に置かれます。

More complicated constructions can be made by using frames within frames. The packer specifications for any widget affect its arrangement within the surrounding frame, which then has its own packer options for placing it within another frame.

フレーム中にフレームを入れることによって、より複雑なものを作ることができます。 任意のウイジェットに対する packer 指定は、その周りのフレーム内部での配置を決めます。そしてそのフレームには自身の packer オプションがあり、他のフレーム中での配置を決めます。

The following example shows the use of frames to construct a window containing four buttons in two centered lines:

次に示すのは、2列に並んだ4つのボタンを含むウインドウを、フレームを使って作成した例です:

from Tkinter import *

def report():
    print "A button has been pressed"

outer_frame = Frame(None)
outer_frame.pack()

first_line = Frame(outer_frame)
first_line.pack(side=TOP)
button1 = Button(first_line, text='Button 1', command=report)
button1.pack(side=LEFT)
button2 = Button(first_line, text='Button 2', command=report)
button2.pack(side=LEFT)

second_line = Frame(outer_frame)
second_line.pack(side=TOP)
button3 = Button(second_line, text='Button 3', command=report)
button3.pack(side=LEFT)
button4 = Button(second_line, text='Button 4', command=report)
button4.pack(side=LEFT)

outer_frame.mainloop()

Note that the first frame has master None, meaning the root window. The inner frames, representing the lines, have the outer frame as master, and the buttons are attached to the inner frames.

最初のフレームのマスターが、ルートウインドウを意味する None であることに注意して下さい。 内側のフレームは行になっており、上の外側フレームをマスターとしています。 ボタンはその内側フレームに付けられます。

Creating your own widgets / 自分自身のウイジェットを創る

In principle, any GUI can be built up like the last example, i.e. by constructing object by object and providing the packer options. However, such a program quickly becomes large and unreadable, and modifying it will be next to impossible. The GUI objects have to be structured in some way, and the usual way is the definition of application-specific higher-level widgets. The example above, for example, would benefit from a "line of buttons" widget.

上例の様に、オブジェクトを一つ一つ作り packer オプションを与えれば、原理的にはどんな GUI でも作れます。 しかし、その様なプログラムはすぐに大きくて読み難いものになり、変更するのも殆ど不可能になるでしょう。 何らかの方法で GUI オブジェクトを作る必要があります。 通常採られる方法は、アプリケーションに専用の高級ウイジェットを定義することです。 例えば、上の例では "ボタンの行" のウイジェットを作れば有効でしょう。

Widgets are defined by subclassing existing widgets, which can be either standard Tk Widgets or other application-defined widgets. Almost all higher-level widgets are subclasses of Frame. Their initialization method creates all the widgets within the frame and specifies their arrangement. Such widgets can then be used as if they were standard widgets.

ウイジェットは、既に存るウイジェットをサブクラス化することで定義されます。 後者は標準的な Tk ウイジェットでも他のアプリケーションで定義されたウイジェットでも構いません。 殆ど全ての高級ウイジェットは、Frame のサブクラスです。 それらの初期化メソッドは、そのフレーム内の全ウイジェットを生成し、それらの配置を指定します。 すると、今度はそれらのウイジェットを標準ウイジェットの様に利用することが出来ます。

With a "button line" widget, the four-button example can be written as follows:

"ボタン行" ウイジェットを用いれば、上の4ボタンの例は次の様に書けます:

from Tkinter import *

def report():
    print "A button has been pressed"

class ButtonLine(Frame):

    def __init__(self, master, button_data):
	Frame.__init__(self, master)
	for text, command in button_data:
	    Button(self, text=text, command=command).pack(side=LEFT)

outer_frame = Frame(None)
outer_frame.pack()

first_line = ButtonLine(outer_frame, [('Button 1', report),
				      ('Button 2', report)])
first_line.pack(side=TOP)

second_line = ButtonLine(outer_frame, [('Button 3', report),
				       ('Button 4', report)])
second_line.pack(side=TOP)

outer_frame.mainloop()

Tk widgets: an overview / Tk ウイジェット:概観

It is impossible to describe all Tk widgets here in detail, but an overview of the widgets and their most common application is a good start for further exploration in the Tk manual.

ここで全ての Tk ウイジェットを詳細まで記述するのは不可能ですが、それらを多く使われる応用も含め概観することは、Tk マニュアルをさらに調べるための良い出発点になるでしょう。

There are also various image objects to display drawings, photos, etc. また、描画や写真などを表示する種々のイメージオブジェクトもあります。

For more information about these widgets, see the href="http://www.pythonware.com/library.htm">Tkinter documentation and the Tk man pages.

これらのウイジェットについてのさらなる情報については、Tkinter の文書と Tk マニュアルページを参照してください。

A not-so-simple example / それ程単純ではない例

The following program displays a small window where the user can type a filename and then either display the text in a window or print it. Instead of typing the filename, a file browser can be used. Here's a screen snapshot showing the main window, a text window, and the file browser:

次のプログラムは、利用者がファイル名を入力する小さなウインドウを表示し、その後にそのテキストをウインドウに表示するかまたは印刷します。 ファイル名をタイプする代りに、ファイルブラウザを使うこともできます。 これは、主ウインドウ、テキストウインドウ、ファイルブラウザが表示されているスクリーンスナップショットです:

[screen snapshot]

The program uses three widgets that are not standard Tk widgets, but part of the Python standard library: a "dialog box" for displaying error messages, a file browser, and a text widget with scroll bars. The program also defines two widgets of its own: a "filename entry" widget, consisting of a label, an entry field for the filename, and a button to run the file browser, and a "button bar" that contains a group of left-aligned buttons and a group of right-aligned buttons. It also defines the two full-window widgets by classes, one for the text display window, and one for the main window.

このプログラムは、標準的な Tk ウイジェットではなく、Python の標準ライブラリに含まれているウイジェットを三つ使っています:エラーメッセージを表示する "ダイアログボックス"、ファイルブラウザ、スクロールバー付きのテキストウイジェトです。 このプログラムは、自身のウイジェットも二つ定義しています:ラベル、ファイル名の記入欄、ファイルブラウザを起動するボタンから成る "ファイル名エントリー" と、左に整列したボタンの組と右に整列したボタンの組から成る "ボタンバー" です。 さらに、ウインドウ全体を表すウイジェットを二つ、クラスによって定義しています。一つはテキスト表示ウインドウ、もう一つは主ウインドウです。

The program uses one packer option that has not been mentioned yet. Normally all widgets have a fixed size, depending on their contents. For some widgets, however, there is no obvious correct size, e.g. for entry fields. Entry fields get an arbitrary size assigned by the packer. The option fill=X tells the packer to extend the widget in the x direction to fill whatever space is available in the widget's master.

このプログラムは、未だ述べていない packer オプションを一つ使っています。 通常、全てのウイジェットは、その中味に依って大きさが固定されます。 しかし、正確な大きさがあらかじめ明白でないウイジェットもあります。例えば入力欄です。 入力欄は、packer によって適当に大きさを割り当てられます。 fill=X というオプションを packer に指定すると、そのウイジェットは、マスター中で得られる限りの空間を埋める様に、x方向に引き伸ばされます。

Note that the actions linked to the buttons are defines as methods of the widgets. This is necessary to give the actions access to the attributes of the widget, which they often need. In a real Tk program, almost all actions are defined by widget methods.

ボタンに連動する動作は、そのウイジェットのメソッドとして定義されていることに注意しましょう。 これは、その動作がウイジェットの属性にアクセス出来るようにするために必要です。このことはしばしば必要になります。 実際の Tk プログラムでは、殆ど全ての動作がウイジェットのメソッドによって定義されています。

from Tkinter import *
from Dialog import Dialog
from FileDialog import LoadFileDialog
from ScrolledText import ScrolledText

class FilenameEntry(Frame):

    def __init__(self, master, text):
	Frame.__init__(self, master)
	Label(self, text=text).pack(side=LEFT)
	self.filename = StringVar()
	Entry(self, textvariable=self.filename).pack(side=LEFT, fill=X)
	Button(self, text="Browse...", command=self.browse).pack(side=RIGHT)

    def browse(self):
	file = LoadFileDialog(self).go(pattern='*')
	if file:
	    self.filename.set(file)

    def get(self):
	return self.filename.get()


class ButtonBar(Frame):

    def __init__(self, master, left_button_list, right_button_list):
	Frame.__init__(self, master, bd=2, relief=SUNKEN)
	for button, action in left_button_list:
	    Button(self, text=button, command=action).pack(side=LEFT)
	for button, action in right_button_list:
	    Button(self, text=button, command=action).pack(side=RIGHT)


class FileNotFoundMessage(Dialog):

    def __init__(self, master, filename):
	Dialog.__init__(self, master, title = 'File not found',
			text = 'File ' + filename + ' does not exist',
			bitmap = 'warning', default = 0,
			strings = ('Cancel',))


class TextWindow(Frame):

    def __init__(self, master, text):
	Frame.__init__(self, master)
	text_field = ScrolledText(self)
	text_field.insert(At(0,0), text)
	text_field.pack(side=TOP)
	text_field.config(state=DISABLED)
	ButtonBar(self, [],
		  [('Close', self.master.destroy)]).pack(side=BOTTOM, fill=X)


class MainWindow(Frame):

    def __init__(self, master):
	Frame.__init__(self, master)
	Label(self, text="Enter a filename and " +
                         "select an action").pack(side=TOP)
	self.filename_field = FilenameEntry(self, "Filename: ")
	self.filename_field.pack(side=TOP, fill=X)
	ButtonBar(self, [('Show', self.show), ('Print', self.lpr)],
		  [('Quit', self.quit)]).pack(side=BOTTOM, fill=X)

    def show(self):
	filename = self.filename_field.get()
	try:
	    text = open(filename).read()
	except IOError:
	    FileNotFoundMessage(self, filename)
	else:
	    new_window = Toplevel()
	    new_window.title(filename)
	    TextWindow(new_window, text).pack()

    def lpr(self):
	filename = self.filename_field.get()
	import os
	os.system('lpr ' + filename)


mw = MainWindow(None)
mw.pack()
mw.mainloop()

Learning more about Tk / Tk についてさらに学ぶには

Tkinter documentation has been insufficient for a long time, and there is still no fully complete documentation available. However, the documentation provided by href="http://www.pythonware.com/">Pythonware is almost complete and sufficient for most people's needs. Start by reading the introduction and then use the reference as you develop your programs.

Tkinter に関する文書は長期に渡って不十分なままですし、入手可能であって完全な記述というのは未だにありません。 しかし、Pythonware が提供する文書はほぼ完全で、殆どの人達の必要を満たすに十分でしょう。 手始めに introduction を読んでから、プログラムを開発する際には、 reference を利用すると良いでしょう。


Exercises / 練習問題


Table of Contents / 目次