[PR]今日のニュースは
「Infoseek モバイル」


Python/Tkinterのとっつきぶぶん

 Pythonはオブジェクト指向のスクリプト言語です(何度も言ってるー) で、Python/TkinterによるGUIプログラミングの形態は、大きく

の2種類に分けられます。クラスを使うというのは、 Java AWT/Swing や Qt の考え方にとてもよく似ていて、 アプリケーションで最初に出すウィンドウをひとつのクラスとして作るものです。 もうひとつのクラスを使わない方法というのはTcl/Tkと同様、 最初にウィンドウを作って、ウィジェット(GUI部品)を作っては載せ、 作っては載せ、という手続き型の手順をたどるものです。 ここでは最初なので、両方のサンプルを載せましたが (白状すると、私自身の覚え書きも兼ねているので…ははは) 以降はPythonらしく(?)クラスを使った書き方だけを扱うことにします。

クラスを使わない方法
 ではまず、ダメなほう、じゃなかった、うーん、なんと申しましょうか、 クラスを使わないスクリプト言語としてのPythonに寄った書きかたで、 「Hello, World」という文字と押すとプログラムが終了するボタンのついたウィンドウを出す簡単なアプリケーションを書いてみます。

from Tkinter import *
import sys

def main():
  root = Tk() # Tkのルートウィンドウ
  # ウィジェットを作って
  laba = Label(text="Hello, World",bg="white",fg="red")
  cmda = Button(text="Bye")
  # 載せます
  laba.pack(side="top")
  cmda.pack(side="top")
  cmda.bind("<ButtonRelease-1>", clicked)
  root.mainloop()  # イベントループに入ります

def clicked(event):
  sys.exit(0)

if __name__ == "__main__":
  main()
# end.

この書き方に関しては、当サイトでは以後二度と出てきませんので、 「一期一会」という言葉を胸に、今生の別れをしておきましょう。 ただし、これだけのサンプルでも、幾つかの事柄が紹介できます。

スクリーンショット

むー、これだけのサンプルではこれだけですか。 あと幾つか、気になる部分もあると思いますが、それらは後述の「クラスを使う」 サンプル以降では本当に必要ないので、おっぽっといてしまいましょう。

クラスを使う方法、超基本
 さて、さっそくその「クラスを使う」、もっと説明的に言いますと、 オブジェクト指向言語としてのPythonに寄った書き方で、 プログラム全体をクラスとし、 アプリケーションの起動をそのクラスのインスタンス化とするものです。 Java AWT/SwingやQtは100%この書き方で、 PythonがそれらのGUIツールキットの先駆け?と思うほどよく似ています。 Pythonの文法なんか全然知らねーよ、という方でも、 Java AWT/SwingやQtの経験があれば、なんとなく分かります…よね?

# これは決まり文句です。
from Tkinter import *

# Tkinterの標準クラスFrameを継承します。
# Javaのアプレットと同じ作り方ですね
class App(Frame):

  # ウィジェットを載せていきます。
  # Tcl/Tkと同じ用語なので対応を覚えるとわかりやすいはずです
  def init(self):
    b = Button(self, text="Hello, World", command=self.cmd_clicked)
    b.pack(side="top")

  # ボタンが押されたときの処理を書きます
  def cmd_clicked(self):
    print "Hello"
    self.master.destroy()

  # ここから後も定石ということで
  def __init__(self, master=None):
    Frame.__init__(self, master)
    self.init()
    self.pack()

app = App()
app.mainloop() # イベントループに入ります。
# end.

__init__ の中の記述はもう定石ということで毎回使い回してOKです。 要点は何箇所かありますが、

b = Button(self, text="Hello, World", command=self.cmd_clicked)

ウィジェットは基本的にこんな感じでTcl/Tkと 同じ名前(一部違うものもありますが)のオプションとその値をずらずらと指定するか、

b['text'] = "Hello, World"
b['font'] = "maru16"

このようにウィジェット変数の、 オプションの名前をキーとするディクショナリ(辞書)の値としてセットするか、 ウィジェットのconfigメソッドを使い、

b.config(text="Hello, World", command=self.cmd_clicked)

このように指定します。ものの本では、最近のTkinterでは、 2番目のディクショナリを使う方法は「旧式扱い」として推奨されないようです。

ところで、

b = Button(self, text="Hello, World", command=self.cmd_clicked)

この先頭の「self」というのはなんやねんという話ですが、 これはこのウィジェットの「親ウィジェット」を指定する引数で、 省略するとTkのルートウィンドウになります。 Tcl/TkからPython/Tkinterに乗り移った(人聞きが悪いなあ) 皆さんに対しては、後述のFrameウィジェットを使ったサンプルで、 ちょっとだけこのあたりを触れてみます。

 もう1点、Buttonウィジェットのcommand=に関する話です。 これもTcl/Tkから移られた人(というか私)には混乱するところでした。 もともと、Tcl/Tkのcommandオプションには好きな形のTclコマンドを指定することができました。 セミコロン「;」で区切ったり、大かっこ「{ }」で囲むことで複数のコマンドを書くこともできましたし、 「-command "puts {Hello, World}"」のように、任意の引数(オプション)をつけることもできました。 しかし! Pythonでは、command=に指定できるのはPython命令の文字列ではなく、 関数の名前(もっと厳密に言うと、関数オブジェクトへの参照) だ、という点に注意されないといけません。ですから、指定できる関数は1つだけ、その形も 上の「def cmd_clicked(self):」にある通り、selfを除けば 引数を全く持たない形にしなくてはいけません。 ここはTcl/Tkを知らなければなんでもないのですが、 Tclerの皆さんは最初混乱するはずなので、十分注意してくださいね。

簡単なサンプル、および
 では、もう少しだけ違うサンプルを作ってみます。

from Tkinter import *

class App(Frame):
  def init(self):
    a = Label(self, text="Hello, World",bg="white",fg="red")
    b = Button(self, text="Bye", command=self.cmd_clicked)
    a.pack(side=TOP)
    b.pack(side=TOP)

  def cmd_clicked(self):
    self.master.destroy()

  def __init__(self, master=None):
    Frame.__init__(self, master)
    self.pack()
    self.init()

if __name__ == "__main__":
  app = App()
  app.mainloop()
# end.

 このように、ラベルはLabel、ボタンは(先ほど出てきたように)Button というクラスで定義されているので、それをインスタンス化し、 オプションを設定して、packメソッドで載せていけばOKです。 これらのオプションはほとんどTcl/TkのTkから類推が効くはずです。 逆にTcl/Tkを知らなくて、いきなりPython/Tkinterから入ろうという皆さんの場合、 Tcl/Tkの余計な邪念に毒されていない分…いやいや、あの、その、 ええと、Tcl/Tkの余計なバッドアイデアのポイズンを受けていない代わりに (そのままやん)それらを網羅した情報を集めるのが大変かもしれませんね。

ちょっとメモ(1) - プログラムを終了する2つめの方法
Tkinterのプログラムを終了するには、 もちろんPythonのsys.exit()関数を使ってもいいのですが、 その他にはTkのルートウィンドウをdestroy()メソッドで吹き飛ばす、 という方法があり、このサイトでもそちらを使っています。
self.master.destroy()

ちょっとメモ(2) - ウィジェット変数
えーと、Tcl/Tkと違って、各ウィジェットには「名前」はありません。 「Label」「Entry」などを使ってウィジェットを作ったときに返される値 (=オブジェクト)をPythonの変数に格納して保持します。 このへんはC言語的というか、Java言語的というか、 ポインタチックな扱いになっているので、つまり、
self.a = Label(self, text="Hello")
self.b = self.a
self.b['text'] = "Hello, again"
のように変数間で代入したりができます。 で、ラベルのようにあとあとウィジェットを参照することがまったくないことがはっきりしているウィジェットの場合は、
a = Label(self, "Hello")
このようにローカル変数「使い捨て」でも構いません。 しかし、エントリのようにあとあとになって入力された内容を知りたいなどの理由で参照する必要があるウィジェットの場合、必ず
self.a = Entry(self)
このようにインスタンス変数にして保持するようにします。 Pythonの場合インスタンス変数も宣言せずにがんがん作れるので、 面倒なら上のように全部 self. をつけるくせにしてもいいかもしれません。

ちょっとメモ(3) - 記号定数
Tkinterでは、ウィジェットを載せるpackメソッドで位置を指定する際に、
  a.pack(side='left', anchor='e')
このように文字列を使いますが、 これらに対応する記号定数も別途定義されているので、 それらを使えば、
  a.pack(side=LEFT, anchor=E)
このように書くこともできます。

Frameを使う
 GUIの作り方、という部分に関してはPython/TkinterはTcl/Tkと全く同じなので、 Tcl/Tkをご存知の方には2度同じよーなことを説明するまでもないのですが、 一応一歩ずつステップを踏んでまいりましょう。 今度は、Frameを使ってウィジェットを配置していく例です。

from Tkinter import *

class App(Frame):
  def init(self):
    self.master.title("Hello!")
    f = Frame(self, relief=GROOVE, bd=3)
    b1 = Button(f, text="Button1", command=self.cmd1)
    b2 = Button(f, text="Button2", command=self.cmd2)

    b3 = Button(self, text="Bye", command=self.cmd_quit)
    for e in [b1, b2]: e.pack(side=LEFT, padx=5)
    for e in [f, b3]:  e.pack(side=TOP, padx=5, pady=5)

  def cmd1(self):
    print "Button 1"
  def cmd2(self):
    print "Button 2"
  def cmd_quit(self):
    self.master.destroy()

  def __init__(self, master=None):
    Frame.__init__(self, master)
    self.pack()
    self.init()

if __name__ == "__main__":
  app = App()
  app.mainloop()
# end.

スクリーンショット

 Tcl/Tkでは、ウィジェット同士の載せる、載せられるという親子関係を「.a.b.c.d」 のように階層的なウィジェットの名前で表現しますが、 Python では、 LabelやEntryなどのウィジェットを作るコンストラクタの最初の引数に、 自分の親となるウィジェットへの参照を渡すことで表現します。
fa = Frame(self)
laba = Label(fa, text = 'Value 1:')
txfa = Entry(fa, width=10)
これすなわち、ラベル laba とエントリ txfa がフレーム fa の上に載るという親子関係ができるわけで、こう書けばあとは
for g in [laba, txfa]:
    g.pack(side = 'left')
こんな感じで実際に載せていくだけです。 なお、この親ウィジェットへの参照を省略するとTkのルートウィンドウが親となりますが、 このサイトで紹介している書き方では全てのウィジェットは 「class App(Frame):」とある通り、ルートウィンドウに乗せたFrameが始祖となります。 従って、親ウィジェットへの参照を省略すると、 pack順序が変になったりするおかしなバグの元になるので、必ず指定が必要です。

セクションのサブメニューに戻る
(first uploaded 2000/04/08 last updated 2002/03/11)