GUI

Python Tkinter Windows/Dialogs (2) ウィンドウの動作とスタイル

ウィンドウの動作や見え方など、変えられることを確認する。

ウィンドウタイトル

ウィンドウのタイトルを調べたり、変更したりする場合。

oldtitle = window.title()
window.title('無題')

サイズと位置

Tk では、画面上のウィンドウの位置と大きさはジオメトリとして知られている。完全なジオメトリの指定は次のようになる widthxheight±x±y.

幅と高さ (通常はピクセル単位) の指定と、x(水平方向の位置)は、先頭にプラスまたはマイナスを付けて指定する。つまり、+25はウィンドウの左端が画面の左端から25ピクセルでなければならず、-50はウィンドウの右端が画面の右端から50ピクセルでなければならないことを意味する。同様に、Y(垂直)方向の位置が+10であれば、ウィンドウの上端が画面の上端から10ピクセル下になるように、-100であれば、ウィンドウの下端が画面の下端から100ピクセル上になるようにという意味。

サイズと位置を変更した例。画面の右上にウィンドウを配置している。

window.geometry('300x200-5+40')

新しいジオメトリ値を提供しないだけで、同じ方法で現在のジオメトリを取得することができる。しかし、ジオメトリを変更した直後にこの操作を行うと、一致しないことがわかる。すべての描画は、イベントループを介したアイドルタイムに対応して、バックグラウンドで効果的に行われる。この描画が行われるまで、ウィンドウの内部ジオメトリは更新されない。もし、すぐに更新するように強制したいのであれば、次のようにする。

window.update_idletasks()
print(window.geometry())

リサイズ動作

デフォルトでは、ルートウィンドウを含むトップレベルウィンドウは、ユーザーによってサイズ変更できる。ユーザーがウィンドウのサイズを変更できないようにしたい場合は、resizableメソッドを使用して制御できる。このメソッドの最初のパラメーターはユーザーが幅を変更できるかどうかを制御し、2番目のパラメーターはユーザーが高さを変更できるかどうかを制御する。すべてのリサイズを無効にするには、次のようにする。

window.resizable(FALSE,FALSE)

ウィンドウがリサイズ可能な場合、ウィンドウのサイズを制限する最小および/または最大サイズを指定できる(ここでも、パラメータは幅と高さ)。

window.minsize(200,100)
window.maxsize(500,500)


ウィンドウのジオメトリから現在のサイズを取得する方法を説明した。もし、ジオメトリを指定しなかったり、ユーザーがサイズを変更しなかった場合、大きさはジオメトリマネージャからウィンドウの要求サイズから取得できる。描画と同様、ジオメトリの計算はイベントループのアイドルタイムにのみ行われるため、ウィジェットが画面に表示されるまで有用な応答は得られない。

window.winfo_reqwidth()   # or winfo_reqheight

クローズボタンに対応する

ほとんどのウィンドウには、タイトルバーに閉じるボタンがある。デフォルトでは、ユーザがこのボタンをクリックすると、Tk はウィンドウを破壊する。しかし、代わりに実行されるコールバックを提供することができる。(一般的な使用例としては、開いているファイルに変更が加えられた場合、ユーザに保存するよう促すことが挙げられる。)

window.protocol("WM_DELETE_WINDOW", callback)

透明度

0.0(完全透明)から1.0(完全不透明)までのアルファチャンネルを指定することで、ウィンドウを部分的に透明にできる。

window.attributes("-alpha", 0.5)

macOSでは、さらに-transparent属性を指定することで、(-alphaと同じ仕組みで)ウィンドウの背景を透明にし、その影を消すことができる。また、ウィンドウとすべてのフレームの背景設定オプションをsystemTransparentの色に設定が必要。

全画面表示

ウィンドウを拡大してフルスクリーンにできる。

window.attributes("-fullscreen", 1)

その他のmacOS固有の属性

macOSのウィンドウには、上記の-transparent属性に加えて、いくつかの属性がある。

タイトルバーの(赤い)クローズウィジェットは、ウィンドウ内のコンテンツが変更されたこと(例えば、ファイルの保存が必要であること)を示すことができる。このことを示すには -modified 属性を 1 に設定し、modified インジケータを削除するには 0 に設定する。

macOS のドックでウィンドウのアイコンをバウンドさせることで、ユーザーの注意を引くことができる。これを行うには、ウィンドウの -notify 属性を設定する。

ウィンドウにドキュメントのコンテンツが含まれている場合、ドキュメントが参照するファイルを指定するアイコンをタイトルバーに配置することができる。ユーザーは、このアイコンを Finder でファイルをドラッグするための代理としてドラッグすることができる。ウィンドウの -titlepath 属性に、ファイルのフルパスを設定する。ウィンドウのタイトルを変更するのではなく(別途変更が必要)、アイコンを提供するだけであることに注意すること。

macOS では、ウィンドウは、ユーティリティウィンドウ、モーダルダイアログ、フローティングウィンドウなど、目的に応じてさまざまな外観を設定できる。MacWindowStyle という Tk でサポートされていないコマンドを使用すると、これらの外観の 1 つをウィンドウに割り当てることができる。Tk の多くのオプションは後で変更可能だが、これらの外観はウィンドウを作成した後、画面上に表示される前に割り当てる必要がある。

t = Toplevel(root)
t.tk.call("::tk::unsupported::MacWindowStyle", "style", t._w, "utility")

ユーティリティの他にも、フローティング、プレーン、モーダルなどの便利なアピアランススタイルがある。

アイコン化・撤退

ほとんどのシステムでは、ウィンドウをアイコン化することで、一時的に画面から外すことができる。Tk では、ウィンドウがアイコン化されているか否かをウィンドウの状態と呼ぶ。ウィンドウの状態には、通常状態、アイコン化状態 (アイコン化されたウィンドウの場合) のほか、引き出し状態、アイコン状態、拡大状態などがある。

現在のウィンドウの状態を直接問い合わせたり、設定できる。また、iconify、deiconify、withdraw というメソッドがあり、それぞれ iconic、normal、withdraw の状態を設定するためのショートカット。

thestate = window.state()
window.state('normal')
window.iconify()
window.deiconify()
window.withdraw()

スタックオーダー(積み上げ順)

スタックオーダーとは、画面上のウィンドウを下から上に向かって「配置」する順番のこと。2つのウィンドウの位置が重なった場合、積み重ね順の上側にあるウィンドウは、下側にあるウィンドウを覆い隠したり、重なったりする。

あるウィンドウを常に積み重ね順の一番上にできる(この属性が設定されていない場合、少なくとも他のすべてのウィンドウの上に来るようにする)。

window.attributes("-topmost", 1)

現在の積み上げ順を、低い順から一覧で確認できる。

root.tk.eval('wm stackorder '+str(window))

また、あるウィンドウが他のウィンドウより上か下かを確認するだけでもOK。

if (root.tk.eval('wm stackorder '+str(window)+' isabove '+str(otherwindow))=='1') ...
if (root.tk.eval('wm stackorder '+str(window)+' isbelow '+str(otherwindow))=='1') ...

また、ウィンドウを積み重ねの一番上(一番下)、または指定したウィンドウのすぐ上(下)に上げる、または下げることができる。

window.lift()
window.lift(otherwin)
window.lower()
window.lower(otherwin)

なぜ、積み重ね順を取得するためにウィンドウを渡す必要があるか、それは積み上げ順序は、トップレベル・ウィンドウだけでなく、兄弟ウィジェット(同じ親を持つウィジェット)にも適用される。複数のウィジェットがグリッド状に重なっている場合、互いのウィジェットを相対的に上下させることができる。

from tkinter import *
from tkinter import ttk


root = Tk()
little = ttk.Label(root, text="Little")
bigger = ttk.Label(root, text='Much bigger label')
little.grid(column=0,row=0)
bigger.grid(column=0,row=0)
root.after(2000, lambda: little.lift())
root.mainloop()

画面情報

winfoコマンドを使って、特定のウィジェットの情報を調べた。また、ディスプレイやスクリーン全体に関する情報を提供することもできる。詳しくは、winfoコマンドのリファレンスを参照すること。

例えば、画面の色深度(ピクセルあたり何ビットか)と色モデル(最近のディスプレイでは通常truecolor)、ピクセル密度、解像度を調べることができる。

print("color depth=" + str(root.winfo_screendepth())+ " (" + root.winfo_screenvisual() + ")")
print("pixels per inch=" + str(root.winfo_pixels('1i')))
print("width=", str(root.winfo_screenwidth()) + " height=", str(root.winfo_screenheight()))

マルチモニター

通常は気にする必要はないが、システム上に複数のモニタがあり、少しカスタマイズしたい場合は、Tkにいくつかのツールがあるためそれを使用する。

まず、マルチモニタを表現する方法は2つある。1つ目は、論理的に分離されたディスプレイを使用する方法。これは X11 システムではよくあることだが、例えば xrandr システムユーティリティを使って変更できる。このモデルの欠点は、一度スクリーンにウィンドウを作成すると、別のスクリーンに移動することができないこと。Tk ウィンドウが動作しているスクリーンは :0.0 (X11 フォーマットのディスプレイ名) のようなもので、決定できる。

root.winfo_screen()

最初にトップレベルを作成するとき、画面構成オプションで作成する画面を指定できる。

また、複数のモニターを1つの大きな仮想ディスプレイとして表現することも可能で、これはmacOSやWindowsの場合。画面に関する情報を求めると、Tkはプライマリモニタの情報を返す。例えば、2台のフルHDモニタを横に並べている場合、画面の解像度は3840×1080ではなく、1920×1080と報告される。これはおそらく良いことで、ウィンドウの位置やサイズを調整するときに、マルチモニタを気にする必要がなく、すべてがプライマリモニタに正しく表示されることを意味する。

ユーザーがウィンドウをプライマリ・モニターから別のモニターに移動させた場合は、その位置を尋ねると、プライマリモニタからの相対的な位置になる。つまり、サイドバイサイドのFHDモニター設定において、モニターの左端近くに配置されたウィンドウでwinfo_xメソッドを呼び出すと、100(プライマリーモニター上にある場合)、-1820(プライマリーモニターの左側のモニターにある場合)、2020(プライマリーモニターの右側のモニターにある場合)が返されるかもしれない。ジオメトリの指定が「+-1820+100」のように少し奇妙に見えるかもしれないが、先ほど見たジオメトリ方法を使用してウィンドウを別のモニターに配置できる。

複数のモニターにまたがるディスプレイ全体の大きさは、おおよそ知ることができる。これを行うには、トップレベル・ウィジェットの最大サイズ、つまり、ユーザーがリサイズできる大きさを確認する(すでに変更した後にこれを行えない)。ディスプレイのフルサイズより少し小さいかもしれない。例えば、macOSの場合、画面上部のメニューバーのサイズ分だけ小さくなる。

root.wm_maxsize()