「Pack ジオメトリマネージャ」の版間の差分

提供: tknotebook
移動: 案内検索
(packメソッドを使ってみる)
 
(1人の利用者による、間の49版が非表示)
2行: 2行:
 
[[メインページ]]>[[コンピュータの部屋#Python]]>[[Tkinter Tips]]
 
[[メインページ]]>[[コンピュータの部屋#Python]]>[[Tkinter Tips]]
  
Tkinterにはウィジェットを別のウィジェット(親ウィジェット)上に配置するための「ジオメトリマーネジャ」というものを持っています。
+
tkinterは、ウィジェットを別のウィジェット(親ウィジェット)上に配置するための「ジオメトリマーネジャ」というものを持っています。
 
ジオメトリマネージャとは、ウィジェットの位置や大きさを決定する仕掛けです。
 
ジオメトリマネージャとは、ウィジェットの位置や大きさを決定する仕掛けです。
  
TkInterには Pack, Grid, Place の3種類のジオメトリマネージャがありますが、この記事では Pack ジオメトリマネージャを紹介します。
+
tkinterには Pack, Grid, Place の3種類のジオメトリマネージャがありますが、この記事では Pack ジオメトリマネージャを紹介します。
  
 
==Pack ジオメトリマネージャとは?==
 
==Pack ジオメトリマネージャとは?==
  
Pack ジオメトリマネージャとは、ウィジェットを'''Pack(「積めて」)'''配置してゆくタイプのジオメトリマネージャです。所謂エラスティックなGUIデザインを実現してくれます。
+
Pack ジオメトリマネージャとは、ウィジェットを'''Packして(「積めて」)'''配置してゆくタイプのジオメトリマネージャです。所謂エラスティックなGUIデザインを実現してくれます。
 
言葉で説明するのは難しいので、早速図とコードをたくさん使った説明に入りましょう。
 
言葉で説明するのは難しいので、早速図とコードをたくさん使った説明に入りましょう。
  
 
==packメソッドを使ってみる==
 
==packメソッドを使ってみる==
  
Packジオメトリマネージャの主役となるのは、全てのウィジェットに用意され散る pack というメソッドです。
+
Packジオメトリマネージャの主役となるのは、全てのウィジェットに用意されている pack というメソッドです。
  
最初は packメソッド最も簡単なコードを紹介しましょう。ウィンドウ全面に貼られた FrameウィジェットにButtonを貼り付けてみます。
+
最初は packメソッドを使った簡単なコードから紹介しましょう。トップレベルウィンドウ前面に貼られた FrameウィジェットにButtonを貼り付けてみます。
  
 
  import tkinter as tk
 
  import tkinter as tk
43行: 43行:
  
 
の影響が Frameウィジェットに及ぶのを防ぐためのコードです。このコードがないと、
 
の影響が Frameウィジェットに及ぶのを防ぐためのコードです。このコードがないと、
Frameのサイズは Buttonウィジェットを表示するのに必要な最小限のサイズに自動的にリサイズします。
+
Frameのサイズは Buttonウィジェットを表示するのに必要な最小限のサイズに自動的に縮んでしまいます。
  
 
  frame.pack_propagate(False)
 
  frame.pack_propagate(False)
  
としておくと、Frameウィジェットの大きさは コンストラクタに渡した width, height パラメータの値のままに保たれます。
+
としておくと、Frameウィジェットの大きさは コンストラクタに渡した width, height パラメータの値のままに保たれます。またトップレベルウィンドウはFrameウィジェットと同じ大きさになります。
  
 
ここで何が起きているかを簡単に説明しておきましょう。
 
ここで何が起きているかを簡単に説明しておきましょう。
58行: 58行:
  
 
図の黄色いところは'''「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」'''です。
 
図の黄色いところは'''「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」'''です。
これは、Buttonウィジェットのサイズとは異なることに注意してください。
+
これは、Buttonウィジェットのサイズとは'''異なる'''ことに注意してください。
 +
 
 
'''「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」'''はFrameオブジェクトの最上端に、Buttonウィジェットのテキストを表示するのに必要なだけの高さが確保され、横方向はFrameの横方向全体が確保されます。Buttonは確保されたエリアの'''「中央」'''に表示されます。
 
'''「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」'''はFrameオブジェクトの最上端に、Buttonウィジェットのテキストを表示するのに必要なだけの高さが確保され、横方向はFrameの横方向全体が確保されます。Buttonは確保されたエリアの'''「中央」'''に表示されます。
  
71行: 72行:
 
  tk.Button(frame, text="A").pack(side=tk.TOP)
 
  tk.Button(frame, text="A").pack(side=tk.TOP)
  
と同じです。side=tk.TOP は親ウィジェットの上端にウィジェットを配置することを意味します。
+
と同じです。'''side=tk.TOP''' は親ウィジェットの上端にウィジェットを配置することを意味します。
 
ではこれを2回連続して使ったらどうなるでしょうか?
 
ではこれを2回連続して使ったらどうなるでしょうか?
  
98行: 99行:
 
==side=tk.LEFT を使ってみる==
 
==side=tk.LEFT を使ってみる==
  
さてここまでは side=tk.TOP だけ使ってきましたが size=tk.Left と組み合わせてみましょう。
+
さて、さらに size=tk.Left を使ってみましょう。
  
 
  import tkinter as tk
 
  import tkinter as tk
113行: 114行:
  
  
を実行すると、下図のように、今度は BボタンがFrameウィジェットの左側に貼り付いて表示されます。
+
このコードを実行すると、下図のように、今度は BボタンがFrameウィジェットの左側に貼り付いて表示されます。
  
 
[[ファイル:Pack frame 03.png]]
 
[[ファイル:Pack frame 03.png]]
  
BボタンはFrameウィジェットの左側に張り付いてますが、少し縦中央より下に表示されていることにお気づきでしょうか?
+
BボタンはFrameウィジェットの左側に張り付いてますが、'''少し縦中央より下に表示されている'''ことにお気づきでしょうか?
 
このずれは以下の図を見ればわかります。
 
このずれは以下の図を見ればわかります。
  
124行: 125行:
 
BボタンはFrameウィジェットのAボタンが占めていない残りの部分の左側に張り付きます。
 
BボタンはFrameウィジェットのAボタンが占めていない残りの部分の左側に張り付きます。
 
side=tk.LEFT で左へ張り付く場合、ボタンの占めるエリアは、幅はボタンのキャプションを表示するのに十分なだけの幅になりますが、
 
side=tk.LEFT で左へ張り付く場合、ボタンの占めるエリアは、幅はボタンのキャプションを表示するのに十分なだけの幅になりますが、
縦はFrameウィジェットのAボタンが占めていない残りの部分の高さと同じになります。つまり高さはとれるだけ目いっぱいになるのです。
+
縦はFrameウィジェットのAボタンが占めていない残りの部分の高さと同じになります。つまり'''高さはとれるだけ目いっぱい'''になるのです。
  
 
Bボタンはこうして取られたエリアの中央に置かれまず。だからFrameウィジェットの中央より若干下になるのです。
 
Bボタンはこうして取られたエリアの中央に置かれまず。だからFrameウィジェットの中央より若干下になるのです。
130行: 131行:
 
==tk.Bottom, tk.Right も使ってみる==
 
==tk.Bottom, tk.Right も使ってみる==
  
そろそろなれたでしょうから、tkBottomと tk.Right も使ってみましょう。
+
そろそろ慣れてきたでしょうから、tkBottomと tk.Right も使ってみましょう。
 
もうすでに察しはついていると思いますが、'''tk.Bottom''' はウィジェットを'''下'''に、tk.Right はウィジェットを右に張り付かせる指定です。
 
もうすでに察しはついていると思いますが、'''tk.Bottom''' はウィジェットを'''下'''に、tk.Right はウィジェットを右に張り付かせる指定です。
  
147行: 148行:
 
  root.mainloop()
 
  root.mainloop()
  
このコードを実行すると、既に予想されていると思いますが、下の図のようになります。
+
このコードを実行すると、下の図のようになります。
  
 
[[ファイル:Pack frame 04.png]]
 
[[ファイル:Pack frame 04.png]]
  
もうわかっているとは思いますが、念のため、ボタンの確保したエリアとボタンの位置を図で示しておきましょう。
+
わかっているとは思いますが、念のため、ボタンの確保したエリアとボタンの位置を図で示しておきましょう。
  
 
[[ファイル:Pack 図面04-1.png]]
 
[[ファイル:Pack 図面04-1.png]]
157行: 158行:
 
==Frameウィジェットを必要最小限の面積にしてみる(pack()の既定の動作)==
 
==Frameウィジェットを必要最小限の面積にしてみる(pack()の既定の動作)==
  
ここまで、Frameウィジェットのサイズは強制的に 200 x 200 にしてきましたが、pack()メソッドの本来の動作では Frameウィジェットは、ボタンを表示する最小限の大きさに自動縮小します。上で示した4個のボタンの場合で試してみましょう。
+
さてここまではボタンウィジェットの動きをわかりやすくするため、Frameウィジェットのサイズを強制的に 200 x 200 にしてきましたが、
 +
pack()メソッドの本来の動作では Frameウィジェットは、'''ボタンを表示する最小限の大きさに自動縮小します'''。上で示した4個のボタンの場合で試してみましょう。
  
 
  import tkinter as tk
 
  import tkinter as tk
174行: 176行:
  
  
上のコードでは Frameの自動縮小を抑えるためのおまじない frame.pack_propagate(False) を削除しています。
+
このコードでは Frameの自動縮小を抑えるためのおまじない frame.pack_propagate(False) を削除しています。
またわかりやすいように Frameウィジェットの背景色を green(緑) にしました。結果は以下のようになります。
+
またわかりやすいように Frameウィジェットの背景色を green(緑) にしてみました。結果は以下のようになります。
  
 
[[ファイル:Pack frame 05.png]]
 
[[ファイル:Pack frame 05.png]]
186行: 188行:
 
実はボタンにはボタンを表示するための必要最小限のサイズがあります。ボタンの場合、既定ではボタンのテキスト(キャプション)を表示するのに必要な大きさのことです。
 
実はボタンにはボタンを表示するための必要最小限のサイズがあります。ボタンの場合、既定ではボタンのテキスト(キャプション)を表示するのに必要な大きさのことです。
  
Packジオメトリマネージャは Frame の大きさを色々変えてみてボタンを貼り付け、全てのボタンのテキストが全て正常に表示できるような'''最小'''の大きさに Frameウィジェットのサイズを決めてくれるのです。
+
Packジオメトリマネージャは Frame の大きさを色々変えてみてボタンを貼り付け、全てのボタンのテキストが全て正常に表示できるような'''最小'''の大きさを計算し、その大きさに Frameウィジェットのサイズを変更するのです。正確なアルゴリズムはよくわかりませんが(^^; 直感的には大変わかりやすい動きです。
 +
 
 +
尚、目聡い方はトップレベルウィンドウがFrameウィジェットより大きくなっていることに気付いていると思いますが、これはトップレベルウィンドウにも最小サイズがあるためです。トップレベルウィンドウはタイトルバーのボタンやタイトルを表示するための巾が必要だからです。
  
 
==fillを使ってみる==
 
==fillを使ってみる==
  
ここまで、packメソッドの side引数だけを使ってきましたが、fill引数も使ってみましょう。
+
さてここまでは packメソッドでボタンの位置決めを行いましたが、ボタンの形はボタン自身に任せてきました。しかし、packメソッドのfill引数を使うと、張り付き先の形に合うようにウィジェットを変形できます。
  
fill引数はウィジェットが packメソッドで確保したエリアに合わせてい、ウィジェットの大きさを伸長させるパラメータです。
+
fill引数とは'''ウィジェットが packメソッドで確保したエリアに合わせて、ウィジェットの大きさを伸長させる'''パラメータです。
  
以下がその使用例です。
+
早速使用例をお見せしましょう。
  
 
  import tkinter as tk
 
  import tkinter as tk
220行: 224行:
 
fill=tk.BOTH は常に、ウィジェットのために確保されたエリアいっぱいにウィジェットを引き延ばします。
 
fill=tk.BOTH は常に、ウィジェットのために確保されたエリアいっぱいにウィジェットを引き延ばします。
  
Frameがリサイズするようにするとこんな風になります。
+
'''Frameを自動縮小するようにすると'''こんな風になります。
  
 
  import tkinter as tk
 
  import tkinter as tk
240行: 244行:
 
==Frameの大きさを変えてみる==
 
==Frameの大きさを変えてみる==
  
ここまではプログラムを書いて実行して様子を見ていただけでしたが、今度はFrameウィジェットの大きさを実行時に変えてみましょう。
+
ここまでの例では、プログラムを書いて実行して pack の様子を見ていただけでしたが、今度はFrameウィジェットの大きさを実行時に変えてみましょう。
  
下のプログラムは今まで使ってきたFrameウィジェットに4個のボタンを表示するだけのプログラムですが(分かりやすいようにFrameウィジェットの背景を緑に変えてありますが)ウィンドウのサイズをマウスを使って変えてやると、下の図ように9なります。
+
下のプログラムは今まで使ってきたFrameウィジェットに4個のボタンを表示するだけのプログラムですが(分かりやすいようにFrameウィジェットの背景を緑に変えてありますが)ウィンドウのサイズをマウスを使って変えてやると、下の図ようになります。
  
 
  import tkinter as tk
 
  import tkinter as tk
261行: 265行:
 
[[ファイル:Pack frame 08.png]]     [[ファイル:Pack frame 09.png]]
 
[[ファイル:Pack frame 08.png]]     [[ファイル:Pack frame 09.png]]
  
Frameウィジェットは side=tk.TOP で トップレベルウィンドウに貼り付けてあるのでこれは当然の動きでしょう。
+
まだ説明してはいませんが、Frameウィジェットは side=tk.TOP で トップレベルウィンドウに貼り付けてあるので、
 +
トップレベルウィンドウがリサイズするとこのような動きになります。
  
 
しかし、ここではトップレベルウィンドウのリサイズに従って、Frameウィジェットを同じ大きさに追従させたいので、コードを若干手直しします。
 
しかし、ここではトップレベルウィンドウのリサイズに従って、Frameウィジェットを同じ大きさに追従させたいので、コードを若干手直しします。
283行: 288行:
 
  frame.pack(fill=tk.BOTH, expand=True)
 
  frame.pack(fill=tk.BOTH, expand=True)
  
の tk.BOTH の意味は既にお分かりだとは思いますが、expand=True の意味はまだ説明していません。とりあえずここでは、Frameウィジェットがトップレベルウィンドウのリサイズに追従して動くために必要なものとお考え下さい。トップレベルウィンドウとFrameウィジェットが一緒にリサイズするようになります。
+
の tk.BOTH の意味は既にお分かりだとは思いますが、expand=True の意味はまだ説明していません。とりあえずここでは、Frameウィジェットがトップレベルウィンドウのリサイズに追従して動くために必要なものとお考え下さい。これでトップレベルウィンドウとFrameウィジェットが一緒にリサイズするようになりました。
 +
 
 +
 
 +
さて、'''Frameウィジェットのサイズが実行時に変わると何が起きている'''のでしょうか?
 +
 
 +
図から明らかなように、各ボタンの「占めるエリア」は、Frameウィジェットがリサイズされると'''改めて packした時の指示内容と順番で取り直されます'''。そしてそれに従ってボタンウィジェットが配置しなおされます。
 +
Frameウィジェットのリサイズに伴ってこの処理は瞬時に実行されるので、利用者がウィンドウを連続してリサイズすると、ボタンが連続して移動し、伸び縮みしているかのように見えます。
 +
 
 +
これは、ウィジェットをPackメソッドで置いたときの指示と順番が、コンテナのウィジェットのリサイズ時もリアルタイムに保たれている、というように考えることもできるでしょう。つまり、各ウィジェットは、ウィンドウがリサイズされると、packで指示されたルールに従ってダイナミックに再配置されることになります。
 +
 
 +
==expandを使ってみる==
 +
 
 +
最後に、packメソッドの引数として最も分かりにくく、かつ必要不可欠な expand引数について説明します。
 +
 
 +
expand引数は既に Frameウィジェットをトップレベルウィンドウいっぱいに広げるのに使いましたが、
 +
ウィジェットが pack で占拠するエリアを確保する際、'''残っている全てのエリアを占拠する'''ように指示する引数です。
 +
 
 +
例えばウィンドウにツールバーとステータスバーを tk.TOP, tk.BOTTOM で配置する場合、
 +
その間に挟まるエリア全てを占拠して伸び縮みするウィジェットを配置するのに expand は便利です。要は余った領域に詰め物をしたいときに使います。
 +
 
 +
まずはこのコードと実行結果を見てください。
 +
 
 +
import tkinter as tk
 +
 +
root = tk.Tk()
 +
 +
frame = tk.Frame(root, width=200, height=200, bg="green")
 +
frame.pack_propagate(False)
 +
frame.pack(fill=tk.BOTH, expand=True)
 +
tk.Button(frame, text="A").pack(side=tk.TOP)
 +
tk.Button(frame, text="B").pack(side=tk.LEFT)
 +
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
 +
 +
root.mainloop()
 +
 
 +
[[ファイル:Pack frame 12.png]]
 +
 
 +
図から何が起きているかお分かりとは思いますが、Cボタンを expand=1 で packメソッドで配置すると、'''Cボタンのために確保された領域の大きさ'''は AボタンとBボタンで確保された領域以外の残された部分全部であることが分かります。side=tk.BOTTOM ですからCボタンは下に張り付くはずですが、残りの領域全域を確保してしまっています。
 +
 
 +
では、side=tk.BOTTOM に何か意味はあるのでしょうか? さらにボタンを追加したらどこに追加されるのでしょうか?
 +
 
 +
やってみましょう。
 +
 
 +
import tkinter as tk
 +
 +
root = tk.Tk()
 +
 +
frame = tk.Frame(root, width=200, height=200, bg="green")
 +
frame.pack_propagate(False)
 +
frame.pack(fill=tk.BOTH, expand=True)
 +
tk.Button(frame, text="A").pack(side=tk.TOP)
 +
tk.Button(frame, text="B").pack(side=tk.LEFT)
 +
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
 +
tk.Button(frame, text="D").pack(side=tk.RIGHT)
 +
 +
root.mainloop()
 +
 
 +
[[ファイル:Pack frame 13.png]]
 +
 
 +
なんと、DボタンはAボタンとCボタンの間にねじ込まれました。つまり、side=tk.BOTTOM, expand=1 で配置されたウィジェットの上部には、もう領域が残っていないにも関わらず別のウジェットをねじ込めるのです。ウィジェットが張り付く方向とは逆側が'''「やわらかい」'''、あるいは目に見えない隙間が空いていると考えてもよいかもしれません。
 +
 
 +
但しねじ込みは大変らしく、Dボタンの高さは side=tk.Right にも拘わらず、テキストを表示する最小限しか確保されていません。expand=1 で伸びた図形の'''柔らかさ'''はその程度のようです。
 +
 
 +
次に、全てのボタンを fill=tk.BOTH で張り付けて、各ボタンのために確保された領域を明確にしておきましょう。
 +
 
 +
import tkinter as tk
 +
 +
root = tk.Tk()
 +
 +
frame = tk.Frame(root, width=200, height=200, bg="green")
 +
frame.pack_propagate(False)
 +
frame.pack(fill=tk.BOTH, expand=True)
 +
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.BOTH)
 +
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.BOTH)
 +
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
 +
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)
 +
 +
root.mainloop()
 +
 
 +
[[ファイル:Pack frame 14.png]]
 +
 
 +
Dボタンが AボタンとCボタンの間の隙間の右端のみを占拠していることが分かります。size=tk.RIGHT は右端の上下方向を占拠しようとする圧力というか志向しか持っていないからです。隙間を全て占拠するには Dボタンも expand=1 で ぱ packする必要があります。
 +
 
 +
最後に、Dボタンにも expand=1 を付けたケース、さらに、fill=tk.BOTHを付けたケースも載せておきます。
 +
 
 +
import tkinter as tk
 +
 +
root = tk.Tk()
 +
 +
frame = tk.Frame(root, width=200, height=200, bg="green")
 +
frame.pack_propagate(False)
 +
frame.pack(fill=tk.BOTH, expand=True)
 +
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.BOTH)
 +
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.BOTH)
 +
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
 +
tk.Button(frame, text="D").pack(side=tk.RIGHT, expand=1)
 +
 +
root.mainloop()
 +
 
 +
 
 +
[[ファイル:Pack frame 15.png]]
 +
 
 +
import tkinter as tk
 +
 +
root = tk.Tk()
 +
 +
frame = tk.Frame(root, width=200, height=200, bg="green")
 +
frame.pack_propagate(False)
 +
frame.pack(fill=tk.BOTH, expand=True)
 +
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.BOTH)
 +
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.BOTH)
 +
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
 +
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
 +
 +
root.mainloop()
 +
 
 +
[[ファイル:Pack frame 16.png]]
 +
 
 +
==終わりに==
  
さて、Frameウィジェットのサイズが実行時に変わると何が起きるのでしょうか?
+
Packジオメトリマネージャの解説、いかがでしたでしょうか。実際にやってみないとなかなか感じがつかめないと思いますが、基本的なことは解説したと思いますのでそう迷うことはもうないと思います。
  
図から明らかなように、各ボタンの「占めるエリア」は、Frameウィジェットがリサイズされると改めて取り直されます。そしてそれに従ってボタンウィジェットが配置しなおされます。
+
Packジオメトリマネージャは tkinter の3種類のジオメトリマネージャの1個にすぎません。多くの画面デザインは Packジオメトリマネージャだけで間に合いますが、
Frameウィジェットのリサイズに従ってこの処理は連続して瞬時に実行されるので、ボタンが伸び縮みして表示されます。
+
Packジオメトリマネージャは、縦方向にウィジェットの位置を揃えたりするのが苦手なので、そういうところでは Gridジオメトリマネージャの併用が効果的です。
  
つまり、ウィジェットをPackメソッドで置いたときの配置の指示が、貼り付けられたコンテナウィジェットがリサイズしてもリアルタイムに保たれるわけです。
+
tkinterの画面デザインの参考になれば幸いです。

2019年2月19日 (火) 08:24時点における最新版

メインページ>コンピュータの部屋#Python>Tkinter Tips

tkinterは、ウィジェットを別のウィジェット(親ウィジェット)上に配置するための「ジオメトリマーネジャ」というものを持っています。 ジオメトリマネージャとは、ウィジェットの位置や大きさを決定する仕掛けです。

tkinterには Pack, Grid, Place の3種類のジオメトリマネージャがありますが、この記事では Pack ジオメトリマネージャを紹介します。

Pack ジオメトリマネージャとは?

Pack ジオメトリマネージャとは、ウィジェットをPackして(「積めて」)配置してゆくタイプのジオメトリマネージャです。所謂エラスティックなGUIデザインを実現してくれます。 言葉で説明するのは難しいので、早速図とコードをたくさん使った説明に入りましょう。

packメソッドを使ってみる

Packジオメトリマネージャの主役となるのは、全てのウィジェットに用意されている pack というメソッドです。

最初は packメソッドを使った簡単なコードから紹介しましょう。トップレベルウィンドウ前面に貼られた FrameウィジェットにButtonを貼り付けてみます。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack()


root.mainloop()

これを実行すると、こんな感じになります。

Pack frame 01.png

このコードでは packメソッドの動きが分かりやすくなるように、ちょっとしたおまじないのコードを使っています。

frame.pack_propagate(False)

tk.Button(frame, text="A").pack()

の影響が Frameウィジェットに及ぶのを防ぐためのコードです。このコードがないと、 Frameのサイズは Buttonウィジェットを表示するのに必要な最小限のサイズに自動的に縮んでしまいます。

frame.pack_propagate(False)

としておくと、Frameウィジェットの大きさは コンストラクタに渡した width, height パラメータの値のままに保たれます。またトップレベルウィンドウはFrameウィジェットと同じ大きさになります。

ここで何が起きているかを簡単に説明しておきましょう。

tk.Button(frame, text="A").pack()

を実行すると、Buttonウィジェットは frameウィジェットの上端に張り付きます。

Pack 図面01.png

図の黄色いところは「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」です。 これは、Buttonウィジェットのサイズとは異なることに注意してください。

「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」はFrameオブジェクトの最上端に、Buttonウィジェットのテキストを表示するのに必要なだけの高さが確保され、横方向はFrameの横方向全体が確保されます。Buttonは確保されたエリアの「中央」に表示されます。

packメソッドを2つ使ってみる

これまで

tk.Button(frame, text="A").pack()

というコードでButtonウィジェットを配置してきましたが、実はこれは

tk.Button(frame, text="A").pack(side=tk.TOP)

と同じです。side=tk.TOP は親ウィジェットの上端にウィジェットを配置することを意味します。 ではこれを2回連続して使ったらどうなるでしょうか?

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.TOP)

root.mainloop()

を実行すると、下図のように縦積みになって表示されます。

Pack frame 02.png

これはつまり、Buttonウィジェットが下図のように自身を表示するエリアを確保するからです。

Pack 図面02.png

つまり2番目のButtonウェイジェットは1番目のボタンウィジェットで確保されたエリアを除いた部分のFrameウィジェットのエリアの最上端を確保するのです。

side=tk.LEFT を使ってみる

さて、さらに size=tk.Left を使ってみましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)

root.mainloop()


このコードを実行すると、下図のように、今度は BボタンがFrameウィジェットの左側に貼り付いて表示されます。

Pack frame 03.png

BボタンはFrameウィジェットの左側に張り付いてますが、少し縦中央より下に表示されていることにお気づきでしょうか? このずれは以下の図を見ればわかります。

Pack 図面03.png

BボタンはFrameウィジェットのAボタンが占めていない残りの部分の左側に張り付きます。 side=tk.LEFT で左へ張り付く場合、ボタンの占めるエリアは、幅はボタンのキャプションを表示するのに十分なだけの幅になりますが、 縦はFrameウィジェットのAボタンが占めていない残りの部分の高さと同じになります。つまり高さはとれるだけ目いっぱいになるのです。

Bボタンはこうして取られたエリアの中央に置かれまず。だからFrameウィジェットの中央より若干下になるのです。

tk.Bottom, tk.Right も使ってみる

そろそろ慣れてきたでしょうから、tkBottomと tk.Right も使ってみましょう。 もうすでに察しはついていると思いますが、tk.Bottom はウィジェットをに、tk.Right はウィジェットを右に張り付かせる指定です。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)
tk.Button(frame, text="C").pack(side=tk.BOTTOM)
tk.Button(frame, text="D").pack(side=tk.RIGHT)

root.mainloop()

このコードを実行すると、下の図のようになります。

Pack frame 04.png

わかっているとは思いますが、念のため、ボタンの確保したエリアとボタンの位置を図で示しておきましょう。

Pack 図面04-1.png

Frameウィジェットを必要最小限の面積にしてみる(pack()の既定の動作)

さてここまではボタンウィジェットの動きをわかりやすくするため、Frameウィジェットのサイズを強制的に 200 x 200 にしてきましたが、 pack()メソッドの本来の動作では Frameウィジェットは、ボタンを表示する最小限の大きさに自動縮小します。上で示した4個のボタンの場合で試してみましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green", )
#frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)
tk.Button(frame, text="C").pack(side=tk.BOTTOM)
tk.Button(frame, text="D").pack(side=tk.RIGHT)

root.mainloop()


このコードでは Frameの自動縮小を抑えるためのおまじない frame.pack_propagate(False) を削除しています。 またわかりやすいように Frameウィジェットの背景色を green(緑) にしてみました。結果は以下のようになります。

Pack frame 05.png

この動作を一口で説明するのは大変難しいです。

Frameの大きさが固定の場合は、ボタンは tk.XXX の指定に従って Frame の四隅に張り付くだけでした。 Frameの大きさは十分広く、ボタンを表示するだけの十分なエリアがありました。

実はボタンにはボタンを表示するための必要最小限のサイズがあります。ボタンの場合、既定ではボタンのテキスト(キャプション)を表示するのに必要な大きさのことです。

Packジオメトリマネージャは Frame の大きさを色々変えてみてボタンを貼り付け、全てのボタンのテキストが全て正常に表示できるような最小の大きさを計算し、その大きさに Frameウィジェットのサイズを変更するのです。正確なアルゴリズムはよくわかりませんが(^^; 直感的には大変わかりやすい動きです。

尚、目聡い方はトップレベルウィンドウがFrameウィジェットより大きくなっていることに気付いていると思いますが、これはトップレベルウィンドウにも最小サイズがあるためです。トップレベルウィンドウはタイトルバーのボタンやタイトルを表示するための巾が必要だからです。

fillを使ってみる

さてここまでは packメソッドでボタンの位置決めを行いましたが、ボタンの形はボタン自身に任せてきました。しかし、packメソッドのfill引数を使うと、張り付き先の形に合うようにウィジェットを変形できます。

fill引数とはウィジェットが packメソッドで確保したエリアに合わせて、ウィジェットの大きさを伸長させるパラメータです。

早速使用例をお見せしましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

実行結果がこれ

Pack frame 06.png

図を見れば明らかですが, fill=tk.X は、ウィジェット用に「確保されたエリア」の横方向(X方向)いっぱいにウィジェットを引き延ばします。 同様に fill=tk.Y は、方向が縦(Y方向)になるだけで動きは同じです。 Cボタンで fill=tk.Y が効かないのは、もちろん、packがcボタンのエリアをテキストの高さの分しか確保しないからです。

fill=tk.BOTH は常に、ウィジェットのために確保されたエリアいっぱいにウィジェットを引き延ばします。

Frameを自動縮小するようにするとこんな風になります。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
#frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

Pack frame 07.png

Frameの大きさを変えてみる

ここまでの例では、プログラムを書いて実行して pack の様子を見ていただけでしたが、今度はFrameウィジェットの大きさを実行時に変えてみましょう。

下のプログラムは今まで使ってきたFrameウィジェットに4個のボタンを表示するだけのプログラムですが(分かりやすいようにFrameウィジェットの背景を緑に変えてありますが)ウィンドウのサイズをマウスを使って変えてやると、下の図ようになります。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()


Pack frame 08.png     Pack frame 09.png

まだ説明してはいませんが、Frameウィジェットは side=tk.TOP で トップレベルウィンドウに貼り付けてあるので、 トップレベルウィンドウがリサイズするとこのような動きになります。

しかし、ここではトップレベルウィンドウのリサイズに従って、Frameウィジェットを同じ大きさに追従させたいので、コードを若干手直しします。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

Pack frame 10.png     Pack frame 11.png

frame.pack(fill=tk.BOTH, expand=True)

の tk.BOTH の意味は既にお分かりだとは思いますが、expand=True の意味はまだ説明していません。とりあえずここでは、Frameウィジェットがトップレベルウィンドウのリサイズに追従して動くために必要なものとお考え下さい。これでトップレベルウィンドウとFrameウィジェットが一緒にリサイズするようになりました。


さて、Frameウィジェットのサイズが実行時に変わると何が起きているのでしょうか?

図から明らかなように、各ボタンの「占めるエリア」は、Frameウィジェットがリサイズされると改めて packした時の指示内容と順番で取り直されます。そしてそれに従ってボタンウィジェットが配置しなおされます。 Frameウィジェットのリサイズに伴ってこの処理は瞬時に実行されるので、利用者がウィンドウを連続してリサイズすると、ボタンが連続して移動し、伸び縮みしているかのように見えます。

これは、ウィジェットをPackメソッドで置いたときの指示と順番が、コンテナのウィジェットのリサイズ時もリアルタイムに保たれている、というように考えることもできるでしょう。つまり、各ウィジェットは、ウィンドウがリサイズされると、packで指示されたルールに従ってダイナミックに再配置されることになります。

expandを使ってみる

最後に、packメソッドの引数として最も分かりにくく、かつ必要不可欠な expand引数について説明します。

expand引数は既に Frameウィジェットをトップレベルウィンドウいっぱいに広げるのに使いましたが、 ウィジェットが pack で占拠するエリアを確保する際、残っている全てのエリアを占拠するように指示する引数です。

例えばウィンドウにツールバーとステータスバーを tk.TOP, tk.BOTTOM で配置する場合、 その間に挟まるエリア全てを占拠して伸び縮みするウィジェットを配置するのに expand は便利です。要は余った領域に詰め物をしたいときに使います。

まずはこのコードと実行結果を見てください。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)

root.mainloop()

Pack frame 12.png

図から何が起きているかお分かりとは思いますが、Cボタンを expand=1 で packメソッドで配置すると、Cボタンのために確保された領域の大きさは AボタンとBボタンで確保された領域以外の残された部分全部であることが分かります。side=tk.BOTTOM ですからCボタンは下に張り付くはずですが、残りの領域全域を確保してしまっています。

では、side=tk.BOTTOM に何か意味はあるのでしょうか? さらにボタンを追加したらどこに追加されるのでしょうか?

やってみましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
tk.Button(frame, text="D").pack(side=tk.RIGHT)

root.mainloop()

Pack frame 13.png

なんと、DボタンはAボタンとCボタンの間にねじ込まれました。つまり、side=tk.BOTTOM, expand=1 で配置されたウィジェットの上部には、もう領域が残っていないにも関わらず別のウジェットをねじ込めるのです。ウィジェットが張り付く方向とは逆側が「やわらかい」、あるいは目に見えない隙間が空いていると考えてもよいかもしれません。

但しねじ込みは大変らしく、Dボタンの高さは side=tk.Right にも拘わらず、テキストを表示する最小限しか確保されていません。expand=1 で伸びた図形の柔らかさはその程度のようです。

次に、全てのボタンを fill=tk.BOTH で張り付けて、各ボタンのために確保された領域を明確にしておきましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.BOTH)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.BOTH)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

Pack frame 14.png

Dボタンが AボタンとCボタンの間の隙間の右端のみを占拠していることが分かります。size=tk.RIGHT は右端の上下方向を占拠しようとする圧力というか志向しか持っていないからです。隙間を全て占拠するには Dボタンも expand=1 で ぱ packする必要があります。

最後に、Dボタンにも expand=1 を付けたケース、さらに、fill=tk.BOTHを付けたケースも載せておきます。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.BOTH)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.BOTH)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
tk.Button(frame, text="D").pack(side=tk.RIGHT, expand=1)

root.mainloop()


Pack frame 15.png

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.BOTH)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.BOTH)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)

root.mainloop()

Pack frame 16.png

終わりに

Packジオメトリマネージャの解説、いかがでしたでしょうか。実際にやってみないとなかなか感じがつかめないと思いますが、基本的なことは解説したと思いますのでそう迷うことはもうないと思います。

Packジオメトリマネージャは tkinter の3種類のジオメトリマネージャの1個にすぎません。多くの画面デザインは Packジオメトリマネージャだけで間に合いますが、 Packジオメトリマネージャは、縦方向にウィジェットの位置を揃えたりするのが苦手なので、そういうところでは Gridジオメトリマネージャの併用が効果的です。

tkinterの画面デザインの参考になれば幸いです。