PythonでTkinterを使ってGUIを開発する複数回連載の第2回目です。
このシリーズの他の部分については、以下のリンクをチェックしてください。
- TkinterによるPythonのGUI開発
- Tkinterを使ったPythonのGUI開発。パート2
- TkinterによるPython GUI開発。パート3
StackAbuse Tkinter チュートリアルシリーズの最初のパートでは、Python を使って簡単なグラフィカルインターフェースを素早く構築する方法を学びました。
この記事では、Tkinterが提供する2つの異なる方法を用いて、いくつかの異なるウィジェットを作成し、画面上に配置する方法を説明しましたが、それでもまだ、このモジュールの能力の表面をほとんど覆っていません。
チュートリアルの第2部では、プログラムの実行中にグラフィカルインターフェースの外観を変更する方法、インターフェースをコードの残りの部分と巧みに接続する方法、そしてユーザから簡単にテキスト入力を取得する方法について学びますので、準備をしておいてください。
グリッドの詳細オプション
前回の記事では、ウィジェットを行と列に配置できる grid()
メソッドについて学びました。
このメソッドを使うと、 pack()
メソッドを使うよりもはるかに整然とした結果を得ることができます。
しかし、伝統的なグリッドには欠点もあり、それは次の例で説明することができます。
import tkinter
root = tkinter.Tk()
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew")
label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Simple button")
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)
label1.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
root.mainloop()
出力
上記のコードは、Tkinterチュートリアルの最初の部分を読んでいれば簡単に理解できるはずですが、とりあえず簡単におさらいしておきましょう。
3行目では、メインの root
ウィンドウを作成しています。
5-7行目では、3つのフレームを作成しています。
ここでは、ルートがそれらの親ウィジェットであることと、それらのエッジが微妙な3D効果を与えることを定義しています。
9-11 行目では、grid()
メソッドを用いてフレームをウィンドウ内に配置しています。
各ウィジェットが占めるグリッドセルを指定し、sticky
オプションを使用して水平方向と垂直方向にストレッチしています。
13-15行目では、ラベル、何もしないボタン、そしてメインウィンドウを閉じる(破壊する)ボタンの3つのシンプルなウィジェットを作成しています – 1フレームに1つのウィジェットです。
そして、17-19 行目では pack()
メソッドを使用して、ウィジェットをそれぞれの親フレームの中に配置しています。
見ての通り、3つのウィジェットを2行2列で配置しても、美観に優れた結果にはなりません。
frame3は行全体を自分用に確保し、
stickyオプションで水平方向にストレッチしていますが、ストレッチできるのは個々のグリッドセルの境界内だけです。
ウィンドウを見た瞬間、私たちは直感的にbutton2` を含むフレームは2列をまたぐべきだと思うでしょう。
幸運なことに、grid()
メソッドの作成者はこのようなシナリオを予測し、カラムスパンのオプションを提供しています。
11行目に小さな修正を加えた後、次のようになります。
import tkinter
root = tkinter.Tk()
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew", columnspan=2)
label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Simple button")
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)
label1.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
root.mainloop()
これで、frame3
がウィンドウの幅全体に広がるようになります。
出力
place() メソッド
通常、Tkinter ベースの美しく整然としたインタフェースを構築する場合、 place()
と grid()
メソッドがすべてのニーズを満たすはずです。
しかし、このパッケージにはもう一つ、ジオメトリマネージャである place()
メソッドが用意されています。
この place()
メソッドは、Tkinter の 3 つのジオメトリマネージャの中で最もシンプルな原理に基づいています。
このメソッドでは、ウィジェットの正確な座標を直接指定するか、ウィンドウのサイズに対する相対的な位置を指定します。
次の例を見てください。
import tkinter
root = tkinter.Tk()
root.minsize(width=300, height=300)
root.maxsize(width=300, height=300)
button1 = tkinter.Button(root, text="B")
button1.place(x=30, y=30, anchor="center")
root.mainloop()
出力
5行目と6行目では、ウィンドウの寸法を300×300ピクセルに正確に指定します。
8 行目では、ボタンを作成しています。
最後に、9行目で place()
メソッドを使用して、ボタンをルートウィンドウの中に配置しています。
3つの値を指定します。
xと
yパラメータを使用して、ウィンドウ内のボタンの正確な座標を定義します。
3つ目のオプションであるanchorは、ウィジェットのどの部分が (x,y) 点で終わるかを定義します。
この場合、ウィジェットの中央のピクセルにしたいです。
grid() の sticky
オプションと同様に、 n
, s
, e
, w
を組み合わせることで、ウィジェットのエッジやコーナーにアンカーを固定することができます。
place()` メソッドは、ここでミスがあっても気にしません。
もし、座標がウィンドウの境界の外を指していたとしても、ボタンは表示されません。
このジオメトリマネージャをより安全に使うには、ウィンドウのサイズに相対する座標を使用することです。
import tkinter
root = tkinter.Tk()
root.minsize(width=300, height=300)
root.maxsize(width=300, height=300)
button1 = tkinter.Button(root, text="B")
button1.place(relx=0.5, rely=0.5, anchor="center")
root.mainloop()
出力
上の例では、9 行目を修正しました。
x と y の絶対座標の代わりに、相対座標を使用するようにしました。
relxと
rely` を 0.5 に設定することで、ウィンドウのサイズに関係なく、ボタンがその中心に配置されるようにしました。
さて、もう一つ place()
メソッドについて、おそらく皆さんが興味を持たれるであろうことがあります。
それでは、このチュートリアルの例2と4を組み合わせてみましょう。
import tkinter
root = tkinter.Tk()
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew", columnspan=2)
label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Simple button")
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)
label1.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
button1 = tkinter.Button(root, text="B")
button1.place(relx=0.5, rely=0.5, anchor="center")
root.mainloop()
出力
上の例では、例 2 からコードを取り、21 行目と 22 行目で例 4 の小さなボタンを作成し、同じウィンドウの中に配置しました。
ルートウィンドウに grid()
と place()
メソッドを明確に混在させているにもかかわらず、このコードが例外を発生させないことに驚かれるかもしれません。
さて、place()
はシンプルで絶対的な性質を持っているので、実際には pack()
や grid()
と混在させることができます。
ただし、本当に必要な場合のみです。
この場合、結果は明らかにかなり醜いものです。
もし、中央のボタンがもっと大きければ、インターフェースの使い勝手に影響が出るでしょう。
そうそう、練習として、21行目と22行目をフレームの定義の上に移動させてみて、どうなるかを見てみるといいでしょう。
通常、インターフェイスで place()
を使用するのは良いアイデアではありません。
特に大きな GUI では、すべてのウィジェットに (相対的であっても) 座標を設定するのは大変な作業で、ウィンドウはすぐに散らかってしまいます。
ユーザーがウィンドウのサイズを変更しようとしたり、特にウィンドウにコンテンツを追加しようとしたりした場合に、このような問題が発生します。
ウィジェットの設定
ウィジェットの外観は、プログラムの実行中に変更することができます。
ウィンドウの要素の外観のほとんどは、configure
オプションの助けを借りて、コードの中で変更することができます。
次の例を見てみましょう。
import tkinter
root = tkinter.Tk()
def color_label():
label1.configure(text="Changed label", bg="green", fg="white")
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=1, row=0, sticky="nsew")
frame3.grid(column=0, row=1, sticky="nsew", columnspan=2)
label1 = tkinter.Label(frame1, text="Simple label")
button1 = tkinter.Button(frame2, text="Configure button", command=color_label)
button2 = tkinter.Button(frame3, text="Apply and close", command=root.destroy)
label1.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
root.mainloop()
出力
5 行目と 6 行目では、新しい関数を簡単に定義しています。
新しい color_label()
関数は label1
の状態を設定します。
configure()` メソッドが受け取るオプションは、新しいウィジェットオブジェクトを作成し、その外観の初期状態を定義するときに使用するオプションと同じものです。
この場合、新しく名前を変えた “Configure button” を押すと、すでに存在している label1
のテキスト、背景色 (bg) と前景色 (fg – この場合、テキストの色) が変更されます。
次に、他のウィジェットを同じように色付けするために使用する別のボタンをインターフェースに追加するとします。
このとき、color_label()
関数はインターフェイスに表示されている特定のウィジェットだけを変更することができます。
複数のウィジェットを変更するためには、変更したいウィジェットの総数と同じ数の同じ関数を定義する必要があります。
これは可能ではありますが、明らかに貧弱な解決策です。
もちろん、もっとエレガントな方法でこのゴールに到達する方法もあります。
この例を少し拡大してみましょう。
import tkinter
root = tkinter.Tk()
def color_label():
label1.configure(text="Changed label", bg="green", fg="white")
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame4 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame5 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=0, row=1, sticky="nsew")
frame3.grid(column=1, row=0, sticky="nsew")
frame4.grid(column=1, row=1, sticky="nsew")
frame5.grid(column=0, row=2, sticky="nsew", columnspan=2)
label1 = tkinter.Label(frame1, text="Simple label 1")
label2 = tkinter.Label(frame2, text="Simple label 2")
button1 = tkinter.Button(frame3, text="Configure button 1", command=color_label)
button2 = tkinter.Button(frame4, text="Configure button 2", command=color_label)
button3 = tkinter.Button(frame5, text="Apply and close", command=root.destroy)
label1.pack(fill='x')
label2.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
button3.pack(fill='x')
root.mainloop()
出力
さて、これでラベルが2つ、ボタンが3つできました。
例えば、「設定ボタン1」が「シンプルラベル1」を設定し、「設定ボタン2」が「シンプルラベル2」を全く同じように設定するとしましょう。
もちろん、上のコードはこのようには動作しません。
どちらのボタンも color_label()
関数を実行しますが、それでも片方のラベルを変更するだけです。
おそらく、最初に思いつく解決策は、color_label()
関数を修正して、ウィジェットオブジェクトを引数として受け取り、それを構成するようにすることでしょう。
それから、ボタンの定義を変更して、それぞれがコマンドオプションで個別のラベルを渡すようにすればよいでしょう。
# ...
def color_label(any_label):
any_label.configure(text="Changed label", bg="green", fg="white")
# ...
button1 = tkinter.Button(frame3, text="Configure button 1", command=color_label(label1))
button2 = tkinter.Button(frame4, text="Configure button 2", command=color_label(label2))
# ...
残念ながら、このコードを実行すると、ボタンが作成された瞬間に color_label()
関数が実行されてしまい、望ましい結果とは言えません。
では、どうすれば正しく動作するようになるのでしょうか?
ラムダ式による引数の受け渡し
ラムダ式は、いわゆる無名関数を一行で定義するための特別な構文を提供します。
ラムダがどのように動作し、通常どのような場合に利用されるかについて詳細に説明することは、このチュートリアルの目標ではありませんので、ラムダ式が間違いなく便利である我々のケースに焦点を当てましょう。
import tkinter
root = tkinter.Tk()
def color_label(any_label):
any_label.configure(text="Changed label", bg="green", fg="white")
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame4 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame5 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=0, row=1, sticky="nsew")
frame3.grid(column=1, row=0, sticky="nsew")
frame4.grid(column=1, row=1, sticky="nsew")
frame5.grid(column=0, row=2, sticky="nsew", columnspan=2)
label1 = tkinter.Label(frame1, text="Simple label 1")
label2 = tkinter.Label(frame2, text="Simple label 2")
button1 = tkinter.Button(frame3, text="Configure button 1", command=lambda: color_label(label1))
button2 = tkinter.Button(frame4, text="Configure button 2", command=lambda: color_label(label2))
button3 = tkinter.Button(frame5, text="Apply and close", command=root.destroy)
label1.pack(fill='x')
label2.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
button3.pack(fill='x')
root.mainloop()
出力
前の短い例でやったのと同じように、color_label()
関数を修正しました。
引数を受け取るようにし、この場合は任意のラベル (テキストを含む他のウィジェットも同様に動作します) を指定し、テキスト、テキストの色、背景色を変更して設定しました。
興味深いのは、22行目と23行目です。
ここでは、実際に2つの新しいラムダ関数を定義して、 color_label()
関数に異なる引数を渡して実行しています。
こうすることで、ボタンが初期化された瞬間に color_label()
関数を呼び出すのを避けることができます。
ユーザー入力の取得
Tkinterチュートリアルシリーズの2回目の記事も終わりに近づいてきましたので、この時点で、プログラムのユーザから入力を得る方法を示すとよいでしょう。
そのためには、Entry
ウィジェットが役に立ちます。
次のスクリプトを見てください。
import tkinter
root = tkinter.Tk()
def color_label(any_label, user_input):
any_label.configure(text=user_input, bg="green", fg="white")
frame1 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame2 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame3 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame4 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame5 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame6 = tkinter.Frame(root, borderwidth=2, relief='ridge')
frame1.grid(column=0, row=0, sticky="nsew")
frame2.grid(column=0, row=1, sticky="nsew")
frame3.grid(column=1, row=0, sticky="nsew")
frame4.grid(column=1, row=1, sticky="nsew")
frame5.grid(column=0, row=2, sticky="nsew", columnspan=2)
frame6.grid(column=0, row=3, sticky="nsew", columnspan=2)
label1 = tkinter.Label(frame1, text="Simple label 1")
label2 = tkinter.Label(frame2, text="Simple label 2")
button1 = tkinter.Button(frame3, text="Configure button 1", command=lambda: color_label(label1, entry.get()))
button2 = tkinter.Button(frame4, text="Configure button 2", command=lambda: color_label(label2, entry.get()))
button3 = tkinter.Button(frame5, text="Apply and close", command=root.destroy)
entry = tkinter.Entry(frame6)
label1.pack(fill='x')
label2.pack(fill='x')
button1.pack(fill='x')
button2.pack(fill='x')
button3.pack(fill='x')
entry.pack(fill='x')
root.mainloop()
出力してください。
5 行目と 6 行目を見てください。
見てわかるように、color_label()
メソッドは新しい引数を受け取ります。
この引数 – 文字列 – は、ラベルの text
パラメータの設定を変更するために使用されます。
さらに、29 行目では新しい Entry
ウィジェットを作成しています(36 行目では、13 行目で作成した新しいフレームの中にそれを入れています)。
24 行目と 25 行目では、それぞれのラムダ関数に追加の引数を渡していることが分かります。
Entryクラスの
get()` メソッドは、ユーザーが入力フィールドに入力した文字列を返します。
ですから、すでにお気づきのように、”configure” ボタンをクリックすると、それに割り当てられたラベルのテキストは、ユーザーが新しい入力フィールドにタイプしたテキストに変更されるのです。
結論
このチュートリアルのパートで、Tkinterモジュールに対するあなたの理解のギャップを埋めることができたと思います。
Tkinterのいくつかの高度な機能は最初は少し厄介に思えるかもしれませんが、Python用の最も人気のあるGUIパッケージを使ってインターフェースを構築する一般的な考え方はとてもシンプルで直感的です。
Tkinterの基本チュートリアルの最終回では、非常に限られたコードで複雑なユーザインタフェースを作成するための非常に賢いショートカットを発見する予定です。
$(document).ready(function() {)
$(‘プレコード’).each(function(i, block) {)
hljs.lineNumbersBlock(ブロック);
});
});