前回の.pltファイルからグラフを作成するプログラムに下図のようなユーザーインタフェースを作成する。
!python3 Plt2PngTk.py
2022-02-09 17:12:52.305 Python[29590:3167700] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
class
を使いこなす。GUIアプリケーションを作成するのには様々な方法がありますが、 複数のプラットフォーム(Linux, Windows,Macos)で (ほぼ)同じように動作するGUIアプリケーションが作成できるプログラムライブラリ(フレームワーク)が 存在します。よく知られたフレームには次のようなものがあります。
Tcl/Tk
GTK - The GIMP Toolkit
Qt - 公式には「キュート」と呼ぶ。
wxWidgets
FLTK (軽いが機能が限定される(らしい)。使ったことないです。すいません。)
これらのフレームワークはいずれもpython
から利用するためのモジュールが提供されています。
Tcl/Tk
を利用するための python モジュール tkinter
は pythonの標準配布パッケージの中に含まれていることから、よく使われています。(ドキュメントについては、公式ページでも”標準ドキュメントが頼りないものだとしても、代わりとなる、リファレンス、チュートリアル、書籍その他が入手可能です。”と述べています。)
なお、J-PARC制御計算機では Python2/tkinter, wxPython, gtk が利用可能です。
Tkウィジェットのためのモジュールは、python2ではTkinter
と呼ばれていました。python3への移行にさいして、モジュールの構成などが見直され、
Tkのためのモジュールはttkinter
となりました。その後、tkinter
はpython2にもバックポートされました(ただし別途にインストールが必要です)。
つまり、tkinter
を使うようにすれば、python3/python2の両方で動作するPythonプログラムを作れるということです。
Tkの画面/窓を画面上で設計するためのツールもいくつか存在するようです。PAGEはそのようなツールの一つです(GUI作成教育ビデオ)。Python3で動作します。公式にサポートされているのは、LinuxとWindowsですが、macosでも動作するようです。
追記:PySimpleGUIというTk,Qt,Gtk,Wxなどを使ったユーザインタフェースを簡単に使うためのモジュールも出てきています。単純なアプリケーション作成には便利そうですが、規模が大きくなってきた時に耐えられるのか?という心配があります。また、標準ライブラリではないため、継続性が心配です。
Wikipediaからの引用
なお、Tcl 言語の名前は「ツールコマンド言語」を意味する英語「tool command language」に由来し、Tk の名前は「ツールキット」を意味する英語「toolkit」に由来する。
"The Quick Python Book", Daryl Harms & Kenneth McDonald,2000, Mannig, USA.には、 "The first basic idea behind Tk is the concept of a widget, which is short for window gadget.
と記載されています。
!python3 HelloWorldTk.py
2022-02-09 17:12:56.533 Python[29641:3167876] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
この画面 は tkinterの三つの部品(ウィジェット)からできています。 二つのボタンとそれらが収められている窓(tk)です。
上のボタン(Push Me!)を押すと、何かが起こります。下のボタン(Quit)を押すと、プログラムは終了します。
実際のプログラムをみてみましょう。
%%file HelloWorldTk.py
#!python3
#-*- coding:utf-8 -*-
import tkinter, tkinter.ttk as ttk # tkinterモジュール、ttkモジュール
from tkinter.ttk import Button # ttkのButtonウィジェットを使います。
from tkinter.constants import *
def hello(root):
root.title("Hello World in Tk") #窓に名前をつけます。
# ボタンの部品を作ります
# btn =Button(root, text="Push me!", command=print_hello)
msg="Hello World!"
btn =Button(root, text="Push me!", command=lambda msg=msg:print(msg))
qbtn=Button(root, text="Quit", command=root.quit)
# 部品を配置します。
btn.grid(row=0, column=0)
qbtn.grid(row=1, column=0)
def print_hello():
"""
btnが押された時に、実行されます(callback関数)。
端末に"Hello World."と印刷します。
"""
print("Hello","World.")
if __name__ == "__main__":
tk=tkinter.Tk() # 部品を収める窓を作ります。
hello(tk) # 窓の中に部品を作ります。
tk.mainloop() # Event loopを起動します。
Overwriting HelloWorldTk.py
!python3 HelloWorldTk.py
2022-02-09 17:13:08.482 Python[29645:3167991] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
%pycat HelloWorldTk.py
#!python3 #-*- coding:utf-8 -*- import tkinter, tkinter.ttk as ttk # tkinterモジュール、ttkモジュール from tkinter.ttk import Button # ttkのButtonウィジェットを使います。 from tkinter.constants import * def hello(root): root.title("Hello World in Tk") #窓に名前をつけます。 # ボタンの部品を作ります # btn =Button(root, text="Push me!", command=print_hello) msg="Hello World!" btn =Button(root, text="Push me!", command=lambda msg=msg:print(msg)) qbtn=Button(root, text="Quit", command=root.quit) # 部品を配置します。 btn.grid(row=0, column=0) qbtn.grid(row=1, column=0) def print_hello(): """ btnが押された時に、実行されます(callback関数)。 端末に"Hello World."と印刷します。 """ print("Hello","World.") if __name__ == "__main__": tk=tkinter.Tk() # 部品を収める窓を作ります。 hello(tk) # 窓の中に部品を作ります。 tk.mainloop() # Event loopを起動します。
Tkなどのグラフィカルユーザーインターフェースを持つプログラムでは、 ユーザーからのマウス、キーボードなどの入力や、その他の装置からの通知(例えば、EPICSのCA monitor エベント)を 監視する mainloop が動作しています。
mainloopはこれらのエベントが発生すると、このエベントを待っている部品(ウィジェット)の指定された
コールバック関数を実行します。 上記の例では、Button
を作成した時に指定した command=print_hello
などのprint_hello
やroot.quit
がコールバック関数です。
(講座中に出た質問に対するお答えとして)
ボタンウィジェットのcommand引数に渡す関数には引数がありません。ボタンを作るごとにcallback関数を作ると、同じようなcallback関数を複数作ることになってしまいます。異なる部分をパラメータ化して、callback関数を共通化することができれば便利です。
これをPythonで実現するには:
lambda
式を使い、既定引数としてパラメータを渡す。といった方法が考えられます。lambda
式を使う方法では、
msg="special message"
btn =Button(root, text="Push me!", command=lambda msg=msg:print(msg))
とすることで、Button
ウィジェットに実行時のパラメータを渡すことができます。 渡すパラメータを変更可能なオブジェクトとすることで、動的に動作を変更することも可能です。もっともこのような場合には、Classを使った方法をとることが多いでしょう。
蛇足: command引数のように関数そのものを実引数とできるのは、Pythonでは関数そのものもプログラムで処理できるデータ(オブジェクト)となっているからです。 関数そのものをデータとして処理できることを使って、pythonで"関数プログラミング"的な手法を利用できますが、これはまた別のお話し、いつか別のときにお話しすることにいたしましょう。
lambda
式¶lambda
式の名前 lambda
は数学でのlambda
算法から来ています。チャーチ(Alonzo Church)のlambda
算法はチューリングのチューリング機械と同様に、計算可能性の議論の為に導入されました。lambda
式と類似の概念はLISP(LAMBDA), javascript(無名関数)などのプログラム言語でも利用されています。
Pythonでのlamda
式は、関数定義と似た機能を持っていますが、処理の本体が一つの式であるという強い制限があります。
関数定義を使うと二つの引数x,y
からその和を求める関数は、
def sum(x,y):
return x+y
となりますが、同様の関数オブジェクトを
sum_l=lambda x,y: x+y
と定義することができます。
実際にこのsum_l
を使ってみます。
>>> sum_l=lambda x,y : x+y
>>> sum_l(1,2)
3
lambda
式の値は関数オブジェクトそのものですから、
>>> (lambda x,y : x+y)(3,4)
7
というような使い方も可能です。
ウィジェットには.bind
というメソッドも用意されており、マウスのクリック/リリースやキーボードのキー押下などのエベントのそれぞれに対して、callback関数を定義することができます。このcallback関数には、callback関数を呼び出す要因となったエベントを表すエベントオブジェクトが引数として渡されます。
btn=ttk.Button(root, text="Push me!", command=print_hello)
ここから、プログラムの中を見ていきます。
この文でbtn
に代入されているのはどんなデータなのでしょう?
import tkinter, tkinter.ttk as ttk
root=tkinter.Tk()
btn=ttk.Button(root, text="Push me!")
print(f"{btn = !r}")
btn = <tkinter.ttk.Button object .!button>
2022-02-09 17:13:10.893 Python[29582:3167633] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
というように、tkinter.ttk.Button
クラスのオブジェクト(object
)であることがわかります。
では、クラス
やオブジェクト
とはなんでしょうか?
クラス(class)とオブジェクト(object)(インスタンス,instance, とも)はオブジェクト志向プログラム(OOP:Object Oriented Program)と呼ばれる方法で中心となる考え方です。プログラムをデータとそのデータに特有な処理をセットにしたソフトウェア部品を組み合わせるように作る考え方がオブジェクト志向プログラムと呼ばれます。関数は与えられたデータに対する処理を表しますが、データと処理を同時にもっているのがオブジェクトという考え方です。
文字型のデータはその一部だけを取り出したり、複数のデータを結合するなど特有の機能が用意されています。このような考え方を発展させて、問題にあったデータとそれを取り扱うアルゴリズムを纏めて、新しいデータ型を生む出す仕組みが、クラスとオブジェクだと考えていいでしょう。
先ほどのtkinterのプログラム例では二つのオブジェクト(btn
とqbtn
)が作られました。
これらのオブジェクトでは、ラベルはそれぞれ異なりますし、押した時の動作も異なりますが、その他は同じような働きをしています。つまり、これらの部品がオブジェクト(表示窓の中に使われる部品、オブジェクトということで、 ウィジェット(Widget=window+object)と呼ばれます)。 ttk.Button
は、これらのオブジェクトがもつ同じ性質(同じクラス)につけられた名前(クラス名)です。
クラス
とオブジェクト
の関係は次のよう書き表されます。
HelloWoldTk.pyでは
tkinter.Tk
やtkinter.Button
がクラスroot=Tk()
やbtn=ttk.Button(...)
がオブジェクです。
tkiniterで使える Tkのウィジェットには以下のものがあります。
テーマ付きウィジェット(ttk)としてさらに、
といったウィジェットが定義されています。
Googleで"Radio button 画像"と検索してみても、でてくるのは計算機上のユーザーインタフェースの画像ばかりでした。 Wikipediaにあったラジオボタンの画像がこちら。(このラジオボタンは四角いですけれど)
ラジオボタンを、"正しくは"Radial Button"と信じている人も既に出ているもようです。 (実物のラジオボタンを見たことなければ、そういう人が出てくるのも不思議ではないですが。)
本来の話に戻りましょう。
root=tkinter.Tk()
¶この文もtkinter.Tk
クラスのオブジェクトを作成し、それを変数root
に割り当てています。
dirt(root)
とすることで、root
オブジェクトが持つメンバ変数や、メソッドのリストを見ることができます。
help(root)
でより詳細な説明が端末に出力されます。情報量が多すぎて、一気に読むのは難しいでしょう。まあ、全てを一気に理解しなくても、tkinterのアプリケーションを作ることはできます。
tkinter
, tkinter.ttk
には グラフィカルユーザインタフェースを作成するための部品(クラス)が
あらかじめ用意されています。 次の図は tkinter.ttk
モジュールで定義されているクラスの関係を示した図です。
これらの部品(ウィジェット)をTk()
の窓の中に配置し、それらの部品がユーザーの操作に対して、どう反応するかを指定していくのが、グラフィカルユーザインタフェースのプログラムの中心作業となります。
ttkはThemed Tk
の名前が示すように、style
の集合体であるtheme
を変えることで、tkkに基づいたウィジェットを使ったアプリケーションの見映えを簡単に変えることができるようになります。
python tkinter
を利用しているとき、その背後ではTcl/Tk
のインタプリタが動作しています。
このインタプリタを使って、pythonからTcl/Tkのプログラム文を実行できます。また、Tcl/Tkインタプリタ内の変数の値を読み出したり、設定することも可能です。
ptyhon
プログラムが接続中のTcl/Tk
のインタプリタ中の変数の現在値を, .getvar()
メソッドを通じて読み込むことも
できます。.setvar()
で変数名と新しい値を与えると、その名前をもつTcl/Tkインタプリタの変数の値が変更されます。
root.getvar( "tk_patchLevel")
Tk
のオブジェクトroot
で、root.call("command","arg1",...)
を実行することで、Tcl/TkのコマンドをPythonプログラム中から実行できます。
また、Tcl/Tkの文を文字列として、Tcl/Tkのインタプリターに実行させることもできます。
pytho
root=tkinter.Tk()
root.call("expr", "$tk_patchLevel")
tkinter
, tkinter.ttk
のウィジェットを使いこなすためには、それぞれのウィジェットの説明書を読む必要があります。 tkinter
,tkinter.ttk
は Tcl/Tk
のために用意された部品をPythonから使えるようにして実現されています。場合によっては使いたい部品の説明書がtkinter
向けには用意されていないかもしれませんが、Tcl/Tk
向けの説明書は必ず存在します。幸いなことに、Tcl/Tkでの Tk ウィジェットの説明をpythonでの tkinter
での使い方に読み替えることができます。
まず、Tcl/Tkのウィジェット名はtkinterの同名のクラスに読み替えます。
# ウィジェットの生成
button .fred -fg red -text "hi there"
=====> fred=Button(fg="red", text="Hi There")
button .panel.fred =====> fred = Button(panel)
インスタンスメソッドの実行はメンバ関数の呼び出しに読み替える。
.fred invoke =====> fred.invoke()
pack, grid, placeは関数呼び出しに読み替える。
pack .fred =====> fred.pack()
wm,winfo 呼び出しはWdigetのメソッドに読み替える。
wm title . "win title" ===> root.wm_title("win title")
winfo name .btn ====> btn.winfo_name()
注:
TclのarrayはPythonでいうところの辞書型データ(連想配列). Pythonのarrayとは全く別物なので、注意。
上記のwebでも
The general state of Tk documentation (outside the Tcl-oriented reference documentation, which is excellent) is unfortunately not at a high point these days. This is particularly true for developers using Tk from languages other than Tcl or working on multiple platforms.
と言っています。 これらのTcl/Tkドキュメントを python/tkinter のプログラム開発で参照する必要が出てくることも考えられます。
Tkの ウィジェットをウィンドウの中に配置するのには、grid
, pack
, place
という配置マネージャーが標準で用意されています。pack
は簡単に使えて便利ですが、複雑な配置を設定するには、ちょっと工夫が必要です。pack
は縦一列あるいは横一列にウィジェットを並べる場合にはとても便利です。一方、grid
は2次元の格子状にウィジェットを配置するのに適しています。 (最近のTutorialではgrid
がまず使われいます。)
pack
,grid
ではウィジェットのサイズを自動的に調整してくれる機能があります。
place
はウィジェットの表示する領域内での位置を正確にしていすることができます。この三つの配置マネージャーの中では、最大の自由度を持っていますが、表示が適切になされるかどうかは、設定次第です。
pack
とgrid
を使い分けて組み合わせることも可能です。(pack
とgrid
の混ぜ合わせができない場合もありますので、これには注意が必要です)
python3の公式ドキュメントのtkinter解説ページではPacker
を中心に説明されていますが、その他の(Tkの解説書を含む)最近の文書ではGrid
を中心に解説しています。 私の印象としては、簡単なアプリケーションの場合は、Packer
が便利ですが、ウィジェットの数が増えてくると、配置のためだけに数多くのFrame
を使う必要があり、管理が大変になります。こういった場合はGrid
の方が便利でしょう。ということで、初めからGrid
を使うように最近は誘導しているのかなと思います。 複雑なウィジェットの配置が必要になった場合は、Notebook
やPanedWindow
などの複合ウィジェットを使うことも考えましょう。
TkのウィジェットはTk
とToplevel
を除き、それが所属するウィジェット(master)をもっています。通常ウィジェットを作成する際の最初の引数として与えられます。省略した場合はデフォルトのrootオブジェクトがmasterになります。ウィジェットのマスターオブジェクトは.master
メンバ変数で確認できます。
(pack
,grid
の呼び出し時にin_
引数でmater
オブジェクトを指定することもできます。)
Grid
やPack
では、同じオブジェクトをmaster
とするウィジェットを、与えられた条件を考慮してウィジェットの位置や大きさを
自動的に調整して、配置してくれます。
Gridでは、ウィジェットを配置する窓/ウィジェットを格子状に分けて考えます。
複数の格子にまたがるウィジェットを配置することができます(columnspan
, rowspan
)。
* .grid(row, column, rawsspan, columnspan, sticky)
* row, column: 行と列を0から始まる番号で指定します。
* rawsspan, columnspan:縦横の占める列数/行数を指定します。
* sticky: ウィジェットがそれを含む枠に対して小さい時の配置についてのパラメータ(N,S, E, W)
Packでは、同じ窓に配置するウィジェットをpack呼び出しの順に詰めていきます。希望する配置を実現するために必要に応じてFrameウィジェットを組み合わせていきます。 Gridでも必要に応じて中間的なFrame
ウィジェットをつかうことができます。
* .pack(side, anchor, expand, fill)
* side: マスターのウィジェットに埋め込んでいく向きを指定します。(tkintre.TOP,tkintre.BOTTOM, tkintre.LEFT,tkintre.RIGHT)
* anchor: ウィジェットを配置する区画の位置を指定します。( n, ne, e, se, s, sw, w, nw, or center)
* expand:ウィジェットのサイズを調整するか否か(True False)
* fill: サイズを変更する向き(None, tkintre.X, tkintre.Y, tkintre.BOTH)
masterに対する位置をピクセル単位で指定できます。全てのウィジェットの配置を完全に制御することができます。 反面、一部のウィジェットの大きさを変更した場合や、さまざまな環境で窓自体の大きさが変わる環境でも適切に配置するためには、工夫が必要です。
高度なウィジェットの配置は、Grid/Packerを複雑に組み合わせるよりも、paned Window, Notebookなどのウィジェットを 使うことを考えましょう。
TkDocsには次のような記述があります。
We'll go into more detail in a later chapter, but grid was introduced several years after Tk became popular. Before that, an older geometry manager named pack was most commonly used. It's equally powerful but much harder to use, making it onerous to create layouts that look appealing today. Unfortunately, much of the example Tk code and documentation out there uses pack instead of grid (a good clue to how current it is). The widespread use of pack is a leading reason that so many Tk user interfaces look terrible. Start new code with grid, and upgrade old code when you can.
ということで、これから Tk を学ばれる方はGrid
だけを使うのが良さそうです。
次の例のように、Grid
とPack
のどちらでも、目指す配置を実現することはできます。
画面構成をあらかじめ決定して作成するには、Grid
の方が便利でしょう。Pack
は融通が効きますが、望む配置を実現するためには、表示されないFrame
ウィジェットをを使うことになりがちです。
%%file HelloWorldTkGrid.py
#!python3
# -*- coding:utf-8 -*-
import tkinter, tkinter.ttk as ttk
def hello(root):
def print_hello():
print("Hello","World.")
btn=ttk.Button(root, text="Push me!", command=print_hello)
qbtn=ttk.Button(root, text="Quit", command=root.destroy)
#
btn.grid(row=0, column=0)
qbtn.grid(row=0, column=1)
tk=tkinter.Tk()
hello(tk)
tk.mainloop()
Overwriting HelloWorldTkGrid.py
!python3 HelloWorldTkGrid.py
2022-02-09 17:13:11.155 Python[29649:3168051] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
%%file HelloWorldTkPack.py
#!python3
# -*- coding:utf-8 -*-
import tkinter, tkinter.ttk as ttk
from tkinter import RIGHT,LEFT,TOP,BOTTOM
def hello(root):
def print_hello():
print("Hello","World.")
btn=ttk.Button(root, text="Push me!", command=print_hello)
qbtn=ttk.Button(root, text="Quit", command=root.destroy)
#
btn.pack(side=LEFT)
qbtn.pack(side=LEFT)
tk=tkinter.Tk()
hello(tk)
tk.mainloop()
Overwriting HelloWorldTkPack.py
!python3 HelloWorldTkPack.py
2022-02-09 17:13:12.987 Python[29652:3168078] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
Tk
クラスとToplevel
クラスはWm(Window manager)クラスを継承していて、デスクトップ上に独立した窓として表示されます。(その他のウィジェットはTkあるいは他のウィジェットの中に表示されます。)
tkinter.Tk
とtkinter.Toplevel
は どちらも画面上に独立した窓を開きます。ではこの「二つのものは同じか? 」というとそうではありません。
Toplevelはウィジェットの一つででもあります。
したがって上位にTkのマスターが存在します。異なるTkウィジェットは窓を閉じても、他のTkウィジェットに影響しません。
通常、一つのアプリケーションにはroot
(従って、Tk()
呼び出し)は一つだけです。root
の他に新たな窓が必要な場合にはToplevel()
を呼び出します。
この場合には、root
のウィンドウを閉じると、アプリケーション全体が停止することを忘れないようにしましょう。
%%file HelloWorldApp.py
#!python3
# -*- coding:utf-8 -*-
# 公式ドキュメント中の”簡単な Hello World プログラム”
import tkinter as tk, tkinter.ttk as ttk
class Application(tk.Frame):
def __init__(self, master=None,msg="Hi there, everyone!"):
super().__init__(master)
self.master = master
self.msg=msg
self.grid()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello World\n(click me)"
self.hi_there["command"] = self.say_hi
self.quit = tk.Button(self, text="QUIT", fg="red",font="Times 36 bold",
command=self.master.destroy)
self.hi_there.pack(side="top")
self.quit.pack(side="bottom")
def say_hi(self):
print(self.msg)
root = tk.Tk()
root.title("Root")
app = Application(master=root)
top= tk.Toplevel(master=root)
top.title("Toplevel")
oapp=Application(master=top)
oroot= tk.Tk()
oroot.title("Other Tk")
more=Application(master=oroot)
root.mainloop()
Overwriting HelloWorldApp.py
!python3 HelloWorldApp.py
2022-02-09 17:13:14.350 Python[29655:3168103] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
%gui tk
#!python3
# -*- coding:utf-8 -*-
# 公式ドキュメント中の”簡単な Hello World プログラム”
import tkinter as tk, tkinter.ttk as ttk
class Application(tk.Frame):
def __init__(self, master=None,msg="Hi there, everyone!"):
super().__init__(master)
self.master = master
self.msg=msg
self.grid()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello World\n(click me)"
self.hi_there["command"] = self.say_hi
self.quit = tk.Button(self, text="QUIT", fg="red",font="Times 36 bold",
command=self.master.destroy)
self.hi_there.pack(side="top")
self.quit.pack(side="bottom")
def say_hi(self):
print(self.msg)
root = tk.Tk()
root.title("Root")
app = Application(master=root)
top= tk.Toplevel(master=root)
top.title("Toplevel")
oapp=Application(master=top)
oroot= tk.Tk()
oroot.title("Other Tk")
more=Application(master=oroot)
Tk()
で作成されたroot
を大元の親に持つオブジェクトは,root
に対応する窓を閉じると同時に消えてしまいます。
TkはTcl/Tkインタプリタと結び付けられています。
複数のウィジェットを組み合わせて作った表示画面を部品化(ウィジェット化)することで、 これらの部品を多数並べた表示画面を簡単に作れるようになります。 これは、新しいウィジェットを クラス として定義することで実現できます。
複数の部品を組み合わせたウィジェットを作る場合は、Frame
ウィジェットを基に作ることがよくあります。
(このように、別の部品を基にしてして、新しい部品をつくることを、クラスの継承 (inheritance) と呼びます。)
クラス(設計図)からオブジェクト(部品, 実体)を作る際には、初期化メンバ関数.__init__
が呼び出されます。
初期化メンバ関数.__init__
の最初の引数は、これから作成されるオブジェクトその物と決まっています。
Pythonではこの引数を慣例的にself
とすることが多いです。しかし、this
でも良いし、me
などでも構いません。
(しかし、色々変わるのは長期的にみるとプログラムの読みやすさを若干とはいえ損なうということも考えておく必要があります。)
初期化メンバ関数の中では、このクラスが持つ、メンバ変数 (上記の例では、self.msg
, self.btn
, self.qbtn
)を初期化しておきます。
pythonではクラスのオブジェクトの実体化は、クラス名を関数であるかのように呼び出すことで実現されます。
これによって、オブジェクトの実体が作成され、.__init__
関数による初期化が実行されたのち、
そのオブジェクトがこの関数呼び出しの結果として返されます。
obj=HellolWorldApp(tk)
%%file HelloWorldTkApp.py
#!python3
# -*- coding:utf-8 -*-
import tkinter, tkinter.ttk as ttk
from tkinter import RIGHT,LEFT,TOP,BOTTOM, N,S,W,E
class HellolWorldApp(ttk.Frame): # ttk.Frameを親クラスにして新しいクラスHelloWorldAppを定義します(継承)。
def __init__(self, master=None, msg="Hello World."):
self.__msg=msg # self.__msg is a private member variable.
#外枠を作ります。superは親クラスのメソッドを呼び出すことを指示しています。
super().__init__(master, padding="6p", relief=tkinter.RIDGE)
# このウィジェット(Frame)の内部の部品を作ります。
self.btn =ttk.Button(self, text="Push me!", command=self.say_hello)
self.qbtn=ttk.Button(self, text="Quit", command=master.destroy)
#内部の部品を配置します。ここではgridを使って配置します。packをつかっても
self.btn.grid(row=0)
self.qbtn.grid(row=1)
def say_hello(self):
print(self.__msg)
if __name__ == "__main__":
tk=tkinter.Tk()
#部品を作り、配置します。
HellolWorldApp(tk).grid() #オブジェクトを作成し、.grid()メソッドを呼び出して画面に表示します。
HellolWorldApp(tk,msg="Good bye!").grid() #オブジェクトを作成し、.grid()メソッドを呼び出して画面に表示します。
app=HellolWorldApp(tk)
try:
print(app.__msg)
except:
print("privete msg:",app._HellolWorldApp__msg)
tk.mainloop()
Overwriting HelloWorldTkApp.py
!python3 HelloWorldTkApp.py
2022-02-09 17:13:17.274 Python[29659:3168133] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState privete msg: Hello World.
一度ウィジェットをクラス化(部品化)すれば、同じような動作をする部品を複数作成することは容易にできるようになります。 rootウィンドウとToplevelの ウィンドウにそれぞれHelloWorldAppのウィジェトを作成します。
%%file HelloWorldTkApp_test.py
from HelloWorldTkApp import HellolWorldApp
import tkinter as tk
root=tk.Tk() #rootウィンドウを作ります。
HellolWorldApp(root).grid() #オブジェクトを作成し、.grid()メソッドをよびだしています。
top=tk.Toplevel() #独立した窓をもう一つ作成。
#この窓にHelloWorldTkAppをメッセージを指定して作成します。
HellolWorldApp(top,
msg="Welcome to the python/tkinter world."
).pack()
root.title("Tk Root") #それぞれの窓のタイトルを指定します。
top.title("Top Level")
root.mainloop() # tkinterアプリケーションが動作を始めます。
Overwriting HelloWorldTkApp_test.py
!python3 HelloWorldTkApp_test.py
2022-02-09 17:13:19.746 Python[29662:3168169] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
GUIアプリケーションを作成する際には、複数のウィジェットの値を連携させる必要がしばしば出てきます。たとえば、スライダー
ウィジェットを操作すると、
それに対応したText
あるいはEntry
ウィジェットの値が変化する。ラジオボタンの状態に応じて、別のウィジェットの見栄え(色、形、画像など)を変更
するといった具合です。 Tkにはこのような場合のプログラムを簡単にしてくれる、Tk Variable
が用意されています。
Tkな特徴的な機能の一つにTk Variable
があります。 Variable
には四つのサブクラス(子クラス)が定義されています。
BooleanVar, DoubleVar, IntVar, StringVar
これらのVariable
クラスから作られたオブジェクトは、それぞれ、 ブール値(True
あるいはFalse
)、浮動小数点数、整数、文字列 のデータ型に対応しています。
Variable
は複数のウィジェットが、プログラムの実行中に変化する(動的)共通のデータを持っているときに有効です。 Variable
を使うことで、Tkではキー入力やマウスクリックのエベントを明示的に.bind
で処理する必要が軽減されています。
Tk Variable
を使って、 複数のウィジェットを連携させてみます。
%%file Variable_sample.py
import tkinter, tkinter.ttk as ttk
from tkinter import Tk, Toplevel, Text
from tkinter import Variable, BooleanVar, IntVar, StringVar, DoubleVar
from tkinter.ttk import Entry, Label, Scale, Progressbar
root=tkinter.Tk()
style = ttk.Style()
style.theme_use('aqua')
tv=StringVar()
tv.set("Enter string")
e=Entry(textvariable=tv)
l=Label(textvariable=tv)
dv=DoubleVar()
sc= Scale(variable=dv,to=1024)
sc1= Scale(variable=dv,to=1024)
dl= Label(textvariable=dv, anchor="w")
pb= Progressbar(variable=dv, maximum=1024)
l.grid()
e.grid()
dl.grid()
sc.grid()
pb.grid()
sc1.grid()
root.mainloop()
Overwriting Variable_sample.py
!python3 Variable_sample.py
2022-02-09 17:13:23.707 Python[29668:3168254] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
%%file HelloWorldTkApp_tes2t.py
from HelloWorldTkApp import HellolWorldApp
import tkinter
tk=tkinter.Tk()
top=tkinter.Toplevel()
HellolWorldApp(tk).grid() #オブジェクトを作成し、.grid()メソッドをよびだしています。
HellolWorldApp(top,
msg="Welcome to the python/tkinter world."
).pack()
tk2=tkinter.Tk()
HellolWorldApp(
tk2, msg="Other Tk.").pack()
tk.title("Tk")
top.title("Top Level")
tk2.title("Other Tk")
tk.mainloop()
Overwriting HelloWorldTkApp_tes2t.py
Canvas
ウィジェット¶Canvas
ウィジェットを使うと、幾何学的な要素(直線、円、多角形、曲線)を画面上に自由に配置して描画することができます。
次に見るように、matplotlib
のグラフもこのtkinter.Canvas
ウィジェットに書き込む形でtkinter
アプリケーションに埋め込まれます。
以下にCanvas
ウィジェットを使ったプログラムを示します。Canvas
内に円(楕円)、アーク(円周の一部)をいくつか配置しています。
%%file TkAnpanman.py
import tkinter as tk, tkinter.ttk as ttk
from tkinter import Canvas, Tk
WIDTH, HEIGHT = 400, 420
class AnpanMan(Canvas):
def __init__(self,root, width=WIDTH, height=HEIGHT):
super().__init__(root,width=width,height=height) # in python3
def draw(self):
輪郭=self.create_oval(200-180, 210-180, 200+180, 210+180, width=5, fill="bisque")
左眉=self.create_arc(125-50, 150-50, 125+50, 150+50, extent=180, start=0, style='arc',width=5)
左目=self.create_oval(125-20, 170-20, 125+20, 170+20, fill="black",width=5)
右眉=self.create_arc(275-50, 150-50, 275+50, 150+50, extent=180 , start=0, style='arc',width=5)
右目=self.create_oval(275-20, 170-20, 275+20, 170+20, fill='black', width=5)
鼻=self.create_oval(200-40, 250-40, 200+40, 250+40, fill='red', width=1)
左頬=self.create_arc(90-40, 250-40, 90+40, 250+40, extent=180, start=-90, style='arc',width=5)
右頬=self.create_arc(310-40, 250-40, 310+40, 250+40, extent=180, start=90, style='arc',width=5)
口=self.create_arc(200-60, 305-60, 200+60, 305+60, extent=-180, start=0,
style='chord', fill="DeepPink", width=5)
if __name__ == "__main__":
root=Tk()
app=AnpanMan(root)
app.draw()
app.grid()
root.mainloop()
Overwriting TkAnpanman.py
!python3 TkAnpanman.py
2022-02-09 17:13:27.159 Python[29672:3168304] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
%gui tk
from TkAnpanman import AnpanMan, Tk
if __name__ == "__main__":
root=Tk()
app=AnpanMan(root)
app.draw()
app.grid()
root.mainloop()
Canvas
の要素¶Canvas
の内部には、以下の要素を作成できます。作成には、canvas.create_xxx
メソッドを使います。(xxx
はそれぞれの要素の型名がはいります。)
bezier
or raw
)。矢印(arrow
)もつけられます。style
にはpieslice
(default), arc
,chord
のいずれかを指定します。おおむねmatplotlib
のグラフをtkinter
のアプリケーションに埋め込むための準備が整ったので、その説明を始めます。
matplotlibはグラフを作成する仕組み(backend)を複数用意しています。Tk
のアプリケーションにmatplotlibのグラフを埋め込むために、Tkをつかったbackend TkAgg
を使います。 TkAgg
ではグラフをtkinter.Canvas
ウィジェットの中に書き込みます。このCanvas
ウィジェットをアプリケーションのウィジェット中に配置すれば、matplotlib
のグラフがtkinter
のアプリケーションの中に埋め込まれたことになります。
まずmatplotlib backend
について、簡単に説明します。
matplotlibのbackedはディスプレイへの描画を担当するライブラリ(Gtk,Qt,Wxなど)と高品位な2Dグラフィックスを生成するライブラリ(AggおよびCairo)の組み合わせを選択できます。
- interactive backends:
GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo,
QtAgg, QtCairo, Qt5Agg, Qt5Cairo,
TkAgg, TkCairo,
WX, WXAgg, WXCairo,
MacOSX, nbAgg, WebAgg
- non-interactive backends:
agg, cairo, pdf, pgf, ps, svg, template
nbAgg
はIPython/Jupyterのnotebook用backendです。webAgg
は ウェブブラウザにグラフを出力します。
Agg
とCairo
はいずれも高品位な2Dグラフィックスを生成するライブラリです。Cairo
はmatplotlib
とは別にインストールする必要があります。
https://www.tutorialspoint.com/matplotlib-backend-differences-between-agg-and-cairo によれば、Agg
とCairo
の違いは、次の
表のようになっています。
Renderer | File types | Description |
---|---|---|
AGG | Png | Raster graphics − high-quality images using the Anti-Grain Geometry engine |
Cairo | png, ps, pdf, svg | Raster or vector graphics − using the Cairo library |
Tk
をbackendの描画エンジンとして使う場合にもTkAgg
およびTkCairo
を使うことができます。
matplotlib
のFigureCanvasTkAgg
関数を使い、matplotlib
のFigureオブジェクトを、tkinter
のCanvas
ウィジェットに結びつけます。
matplot側ではこのFigure
オブジェクトに描画すると、tkinter
のCanvas
の中にグラフが現れると言うわけです。
サンプルプログラム(下記)の
self.fig=Figure(figsize=(16,9), dpi=72)
self.canvas=FigureCanvasTkAgg(self.fig, master=self) # A tk.DrawingArea.
でこれが実現されています。
このプログラムでは、askopenfilename
やshowinfo
などTk/tkinter
が提供するダイアログ(アプリケーションとは別の窓を開いてユーザーとのインタラクションを
行う)を利用しています。 tkinter.messagebox
,tkinter.filedialog
,tkinter.simpledialog
によく使われるダイアログが用意されています。
これらのダイアログを使うことで、開発の効率が上がるでしょう。また、これらのダイアログのもとになっているクラスを継承することで、独自のダイアログを作成することもできます。
messagebox
の関数¶askokcancel(title=None, message=None, **options)
Ask if operation should proceed; return true if the answer is ok
askquestion(title=None, message=None, **options)
Ask a question
askretrycancel(title=None, message=None, **options)
Ask if operation should be retried; return true if the answer is yes
askyesno(title=None, message=None, **options)
Ask a question; return true if the answer is yes
askyesnocancel(title=None, message=None, **options)
Ask a question; return true if the answer is yes, None if cancelled.
showerror(title=None, message=None, **options)
Show an error message
showinfo(title=None, message=None, **options)
Show an info message
showwarning(title=None, message=None, **options)
Show a warning message
filedialog
の関数¶askdirectory(**options)
Ask for a directory, and return the file name
askopenfile(mode='r', **options)
Ask for a filename to open, and returned the opened file
askopenfilename(**options)
Ask for a filename to open
askopenfilenames(**options)
Ask for multiple filenames to open
Returns a list of filenames or empty list if
cancel button selected
askopenfiles(mode='r', **options)
Ask for multiple filenames and return the open file
objects
returns a list of open file objects or an empty list if
cancel selected
asksaveasfile(mode='w', **options)
Ask for a filename to save as, and returned the opened file
asksaveasfilename(**options)
Ask for a filename to save as
simpledialog
の関数¶askfloat(title, prompt, **kw)
get a float from the user
Arguments:
title -- the dialog title
prompt -- the label text
**kw -- see SimpleDialog class
Return value is a float
askinteger(title, prompt, **kw)
get an integer from the user
Arguments:
title -- the dialog title
prompt -- the label text
**kw -- see SimpleDialog class
Return value is an integer
askstring(title, prompt, **kw)
get a string from the user
Arguments:
title -- the dialog title
prompt -- the label text
**kw -- see SimpleDialog class
Return value is a string
%%file Plt2PngTk.py
#!python3
# -*- coding:utf-8 -*-
import tkinter, tkinter.ttk as ttk
from tkinter import Frame
from tkinter.ttk import Button, Entry
from tkinter.filedialog import askopenfile,askopenfilename
from tkinter.messagebox import showinfo
from tkinter import N,S,E,W, X, Y, BOTH
import os
import matplotlib
matplotlib.use("macosx")
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
# from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
from plt2png import *
class Plt2Graph(Frame):
def __init__(self,master=None):
super(Plt2Graph, self).__init__(master) #親から継承した __init__関数を実行。python3ではsuper().__init_(master)で良い。
self.master.title("plt to png")
self.setup() # 内部のウィジェットをつくり、
self.grid() # 配置します。
def setup(self): # 内部のウィジェットを作ります。
self.pltfnvar=tkinter.StringVar() #StringVarを使って、描画する.pltファイルを管理します。
self.pltfn=Entry(self, textvariable=self.pltfnvar) # EntryウィジェットとStringVarを接続しておきます。
self.selectbtn=Button(self,text="選択", command=self.choosefile) #ファイルダイアログを使ってファイルを選択するためのボタン
#
self.fig=Figure(figsize=(16,9), dpi=72,layout='tight')
self.canvas=FigureCanvasTkAgg(self.fig, master=self) # A tk.DrawingArea.
#self.canvas.draw()
self.drawbtn=Button(self,text="グラフ作成",
command=self.draw, state=["disabled"]) #初期状態ではボタンを押せない
self.savebtn=Button(self,text="グラフ保存",
command=self.savefig, state=["disabled"]) #初期状態ではボタンを押せない
self.quitbtn=Button(self,text="終了",
command=self.master.quit)
def grid(self,*args,**env): # Grid manageで内部のウィジェットを配置します。
super(Plt2Graph, self).grid(*args,**env)
self.columnconfigure(0, weight=1)
self.pltfn.grid(row=0, column=0, sticky=(N,S,E,W))
self.selectbtn.grid(row=0,column=1, sticky=(N,S,W,E))
self.canvas.get_tk_widget().grid(row=1, column=0,
rowspan=2, sticky=(N,S,E,W))
self.drawbtn.grid(row=1, column=1,sticky=(N,E,W))
self.savebtn.grid(row=2, column=1,sticky=(N,E,W))
self.quitbtn.grid(row=3, column=1,sticky=(S,E,W))
self.rowconfigure(2, weight=1)
def draw(self): # drawbtnが押された時の動作
fn=self.pltfnvar.get() # String Variable の現在の値をファイル名として使います。
if fn:
plt2png(fn, fig=self.fig) # self.figに`.plt`ファイルの中身を描画する。
else:
print("\a")
self.canvas.draw() # FigureにリンクしているCanvasウィジェットを更新する。
self.savebtn.state(["!disabled"]) # グラフを更新したので、保存できるようにする。
def choosefile(self): # selectbtn が押された時の動作
infl=askopenfilename(filetypes=[("gnuplot","plt")]) #表示するデータファイル(.plt)をダイアログを使って選択する。
self.pltfnvar.set(infl) # 選択結果のStringVarに保存する。
self.fig.clf() #Figureをクリア
self.savebtn.state(["disabled"]) # グラフを更新するまで、保存できない。
if infl:
self.drawbtn.state(["!disabled"]) # ファイル名が決まったので、グラフを更新できる。
else:
self.drawbtn.state(["disabled"]) # ファイル名が決まったので、グラフを更新できる。
self.canvas.draw() #画面更新
def savefig(self): # savebtnが押された時の動作
fn,ext=os.path.splitext(self.pltfnvar.get())
ofn=os.path.extsep.join([fn,"png"])
self.fig.savefig(ofn)
showinfo(message="Saved to {}.".format(ofn),) # 保存したファイルの名前をダイアログに表示する。
if __name__ == "__main__":
root=tkinter.Tk()
app=Plt2Graph(root)
app.mainloop()
Overwriting Plt2PngTk.py
!python3 Plt2PngTk.py
2022-02-09 17:13:40.282 Python[29678:3168401] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/org.python.python.savedState
macos 12のシステムに含まれているpython(/usr/bin/python)では、
Python 3.8.2 (default, Feb 25 2021, 09:38:33)
[Clang 12.0.5 (clang-1205.0.22.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tkinter
>>> b=tkinter.Button()
DEPRECATION WARNING: The system version of Tk is deprecated and may be removed in a future release. Please don't rely on it. Set TK_SILENCE_DEPRECATION=1 to suppress this warning.
>>> 2021-12-08 12:36:44.446 Python[4866:46928] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /var/folders/3h/lqzhtkt17vv47gbtv470wcp40000gn/T/com.apple.python3.savedState
とのメッセージが出力されます。Python.orgから別途インストールしたpython(/usr/local/bin/pythonなど)ではこのメッセージは現れません。しかし、Tkそのものがシステムからサポートされなくなった場合、こちらのバージョンでの動作が維持されるかどうかは、不明です。Tk本体もシステムとは別にインストールするなどの手間は増えることになるでしょう。
そういえば、「AppleはmacOS 12 Montereyからスクリプト言語「PHP」のバンドルを終了するようです。」は実際に実行され、macos 12ではシステムからPHPはなくなりました。これに伴い、pukiwikiも、そのままでは、使えなくなってしまいました。 (現在はmoimoinを中心に使っているので、問題ないのですが。)
Appleは Webkitを使っており、そのWebkitにはpythonインタフェースもあるらしいのです。しかし、macos上で動作するwebkit用のpythonモジュールが見つかっていません。もう少し色々調査が必要です。pythonでelectronを使ったアプリケーションをかけるといいいなとか思うのですが。
An example from the matplot home page.
%matplotlib inline
import matplotlib
matplotlib.get_backend()
'module://matplotlib_inline.backend_inline'
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
# setup Tk
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
# setup figure with mpl
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
ax = fig.add_subplot()
line, = ax.plot(t, 2 * np.sin(2 * np.pi * t))
ax.set_xlabel("time [s]")
ax.set_ylabel("f(t)")
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
# connect mpl event loop with tk event loop
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.quit)
def update_frequency(new_val):
# retrieve frequency
f = float(new_val)
# update data
y = 2 * np.sin(2 * np.pi * f * t)
line.set_data(t, y)
# required to update canvas and attached toolbar!
canvas.draw()
slider_update = tkinter.Scale(root,
from_=1, to=5, orient=tkinter.HORIZONTAL,
command=update_frequency, label="Frequency [Hz]")
# Packing order is important. Widgets are processed sequentially and if there
# is no space left, because the window is too small, they are not displayed.
# The canvas is rather flexible in its size, so we pack it last which makes
# sure the UI controls are displayed as long as possible.
#button_quit.pack(side=tkinter.BOTTOM)
#slider_update.pack(side=tkinter.BOTTOM)
#toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
#canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
button_quit.grid(column=0,row=3,sticky="ew")
slider_update.grid(column=0,row=2,sticky="ew")
toolbar.grid(column=0,row=1,sticky="ew")
canvas.get_tk_widget().grid(column=0,row=0,sticky="nesw")
# tkinter.mainloop()
%gui tk
import tkinter
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import numpy as np
# setup Tk
root = tkinter.Tk()
root.wm_title("Embedding in Tk")
# setup figure with mpl
fig = Figure(figsize=(5, 4), dpi=100)
t = np.arange(0, 3, .01)
ax = fig.add_subplot()
line, = ax.plot(t, 2 * np.sin(2 * np.pi * t))
ax.set_xlabel("time [s]")
ax.set_ylabel("f(t)")
canvas = FigureCanvasTkAgg(fig, master=root) # A tk.DrawingArea.
canvas.draw()
# pack_toolbar=False will make it easier to use a layout manager later on.
toolbar = NavigationToolbar2Tk(canvas, root, pack_toolbar=False)
toolbar.update()
# connect mpl event loop with tk event loop
canvas.mpl_connect(
"key_press_event", lambda event: print(f"you pressed {event.key}"))
canvas.mpl_connect("key_press_event", key_press_handler)
button_quit = tkinter.Button(master=root, text="Quit", command=root.quit)
def update_frequency(new_val):
# retrieve frequency
f = float(new_val)
# update data
y = 2 * np.sin(2 * np.pi * f * t)
line.set_data(t, y)
# required to update canvas and attached toolbar!
canvas.draw()
slider_update = tkinter.Scale(root,
from_=1, to=5, orient=tkinter.HORIZONTAL,
command=update_frequency, label="Frequency [Hz]")
# Packing order is important. Widgets are processed sequentially and if there
# is no space left, because the window is too small, they are not displayed.
# The canvas is rather flexible in its size, so we pack it last which makes
# sure the UI controls are displayed as long as possible.
button_quit.grid(column=1,row=2)
slider_update.grid(column=0, row=2)
toolbar.grid(column=0,row=1, columnspan=2)
canvas.get_tk_widget().grid(
column=0,row=0,columnspan=2
)
# tkinter.mainloop()
%gui tk
from tkinter import *
from tkinter import ttk
root = Tk()
content = ttk.Frame(root)
frame = ttk.Frame(content, borderwidth=5, relief="ridge", width=200, height=100)
namelbl = ttk.Label(content, text="Name")
name = ttk.Entry(content)
onevar = BooleanVar(value=True)
twovar = BooleanVar(value=False)
threevar = BooleanVar(value=True)
one = ttk.Checkbutton(content, text="One", variable=onevar, onvalue=True)
two = ttk.Checkbutton(content, text="Two", variable=twovar, onvalue=True)
three = ttk.Checkbutton(content, text="Three", variable=threevar, onvalue=True)
ok = ttk.Button(content, text="Okay")
cancel = ttk.Button(content, text="Cancel")
content.grid(column=0, row=0)
frame.grid(column=0, row=0, columnspan=3, rowspan=2)
namelbl.grid(column=3, row=0, columnspan=2)
name.grid(column=3, row=1, columnspan=2)
one.grid(column=0, row=3)
two.grid(column=1, row=3)
three.grid(column=2, row=3)
ok.grid(column=3, row=3)
cancel.grid(column=4, row=3)
#root.mainloop()
https://www.tcl.tk/man/tcl/TkCmd/cursors.html
man n cursors
でも確認できます。
https://www.tcl.tk/man/tcl/TkCmd/colors.html
man n colors
でも確認できます。