Python 入門講座 第9/10回: 仰せのままに(as you wish)。

本日の目標

前回の.pltファイルからグラフを作成するプログラムに下図のようなユーザーインタフェースを作成する。

  1. ファイルダイアログを使って入力となる.pltファイルを選択
  2. グラフ作成ボタンをおしてグラフを表示
  3. 作成されたグラフをファイルに保存する。

作成するGUI window

本日の講座の概要

  1. PythonでGUIアプリケーションをつくるには?
  2. tkinterとは
    1. 簡単な例
    2. tkinterの構成
    3. tkinter/ttkのウィジェット
  3. class を使いこなす。
  4. matplotlib をtkinterに埋め込む

PythonでGUIアプリケーションをつくるには

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 が利用可能です。

Tkinterとtkinter

Tkウィジェットのためのモジュールは、python2ではTkinterと呼ばれていました。python3への移行にさいして、モジュールの構成などが見直され、 Tkのためのモジュールはttkinterとなりました。その後、tkinterはpython2にもバックポートされました(ただし別途にインストールが必要です)。

つまり、tkinterを使うようにすれば、python3/python2の両方で動作するPythonプログラムを作れるということです。

GUI builderについて

Tkの画面/窓を画面上で設計するためのツールもいくつか存在するようです。PAGEはそのようなツールの一つです(GUI作成教育ビデオ)。Python3で動作します。公式にサポートされているのは、LinuxとWindowsですが、macosでも動作するようです。

追記:PySimpleGUIというTk,Qt,Gtk,Wxなどを使ったユーザインタフェースを簡単に使うためのモジュールも出てきています。単純なアプリケーション作成には便利そうですが、規模が大きくなってきた時に耐えられるのか?という心配があります。また、標準ライブラリではないため、継続性が心配です。

Tcl/Tk:名前の由来

Wikipediaからの引用

なお、Tcl 言語の名前は「ツールコマンド言語」を意味する英語「tool command language」に由来し、Tk の名前は「ツールキット」を意味する英語「toolkit」に由来する。

ウィジェット(widget)

"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.

と記載されています。

tkinterとは

”Hello World”: グラフィカルユーザーインタフェース版

まずは、画面に表示されたボタンを押すと、端末に "Hello World"と表示するプログラムをtkinterを使って作ってみました。

画面

この画面 は tkinterの三つの部品(ウィジェット)からできています。 二つのボタンとそれらが収められている窓(tk)です。

widgets in Hello world GUI

上のボタン(Push Me!)を押すと、何かが起こります。下のボタン(Quit)を押すと、プログラムは終了します。

実際のプログラムをみてみましょう。

mainloopと callback関数

Tkなどのグラフィカルユーザーインターフェースを持つプログラムでは、 ユーザーからのマウス、キーボードなどの入力や、その他の装置からの通知(例えば、EPICSのCA monitor エベント)を 監視する mainloop が動作しています。

mainloopはこれらのエベントが発生すると、このエベントを待っている部品(ウィジェット)の指定された コールバック関数を実行します。 上記の例では、Buttonを作成した時に指定した command=print_hello などのprint_helloroot.quitがコールバック関数です。

Tk main loop

commandに引数を渡すことはできないの?

(講座中に出た質問に対するお答えとして)

ボタンウィジェットのcommand引数に渡す関数には引数がありません。ボタンを作るごとにcallback関数を作ると、同じようなcallback関数を複数作ることになってしまいます。異なる部分をパラメータ化して、callback関数を共通化することができれば便利です。

これをPythonで実現するには:

  1. 必要な情報を取り込んだ新しいボタンクラスを定義し、そのメンバ関数をcommandに指定する。
  2. 関数内関数を定義して、既定引数としてパラメータを渡す。
  3. 同様に、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

ウィジェットには.bindというメソッドも用意されており、マウスのクリック/リリースやキーボードのキー押下などのエベントのそれぞれに対して、callback関数を定義することができます。このcallback関数には、callback関数を呼び出す要因となったエベントを表すエベントオブジェクトが引数として渡されます。

部品の実体(ウィジェット)を作る

btn=ttk.Button(root, text="Push me!", command=print_hello)

ここから、プログラムの中を見ていきます。

この文でbtnに代入されているのはどんなデータなのでしょう?

というように、tkinter.ttk.Button クラスのオブジェクト(object)であることがわかります。 では、クラスオブジェクトとはなんでしょうか?

部品(ウィジェット)は クラス

クラス(class)とオブジェクト(object)(インスタンス,instance, とも)はオブジェクト志向プログラム(OOP:Object Oriented Program)と呼ばれる方法で中心となる考え方です。プログラムをデータとそのデータに特有な処理をセットにしたソフトウェア部品を組み合わせるように作る考え方がオブジェクト志向プログラムと呼ばれます。関数は与えられたデータに対する処理を表しますが、データと処理を同時にもっているのがオブジェクトという考え方です。

文字型のデータはその一部だけを取り出したり、複数のデータを結合するなど特有の機能が用意されています。このような考え方を発展させて、問題にあったデータとそれを取り扱うアルゴリズムを纏めて、新しいデータ型を生む出す仕組みが、クラスとオブジェクだと考えていいでしょう。

先ほどのtkinterのプログラム例では二つのオブジェクト(btnqbtn)が作られました。 これらのオブジェクトでは、ラベルはそれぞれ異なりますし、押した時の動作も異なりますが、その他は同じような働きをしています。つまり、これらの部品がオブジェクト(表示窓の中に使われる部品、オブジェクトということで、 ウィジェット(Widget=window+object)と呼ばれます)。 ttk.Buttonは、これらのオブジェクトがもつ同じ性質(同じクラス)につけられた名前(クラス名)です。

クラスオブジェクトの関係は次のよう書き表されます。

HelloWoldTk.pyでは

です。

Tk/TTk のウィジェット

tkiniterで使える Tkのウィジェットには以下のものがあります。       

テーマ付きウィジェット(ttk)としてさらに、

といったウィジェットが定義されています。

余談:ラジオボタン

Googleで"Radio button 画像"と検索してみても、でてくるのは計算機上のユーザーインタフェースの画像ばかりでした。 Wikipediaにあったラジオボタンの画像がこちら。(このラジオボタンは四角いですけれど)

ラジオボタン

ラジオボタンを、"正しくは"Radial Button"と信じている人も既に出ているもようです。 (実物のラジオボタンを見たことなければ、そういう人が出てくるのも不思議ではないですが。)

本来の話に戻りましょう。

root=tkinter.Tk()

この文もtkinter.Tkクラスのオブジェクトを作成し、それを変数rootに割り当てています。

dirt(root)

とすることで、root オブジェクトが持つメンバ変数や、メソッドのリストを見ることができます。

help(root)

でより詳細な説明が端末に出力されます。情報量が多すぎて、一気に読むのは難しいでしょう。まあ、全てを一気に理解しなくても、tkinterのアプリケーションを作ることはできます。

ttk モジュールのクラス

tkinter, tkinter.ttkには グラフィカルユーザインタフェースを作成するための部品(クラス)が あらかじめ用意されています。 次の図は tkinter.ttkモジュールで定義されているクラスの関係を示した図です。

これらの部品(ウィジェット)をTk()の窓の中に配置し、それらの部品がユーザーの操作に対して、どう反応するかを指定していくのが、グラフィカルユーザインタフェースのプログラムの中心作業となります。 

ttkはThemed Tkの名前が示すように、styleの集合体であるthemeを変えることで、tkkに基づいたウィジェットを使ったアプリケーションの見映えを簡単に変えることができるようになります。

tkinterのクラス図はこちら

Tcl/Tk -> python/tkinter

python tkinterを利用しているとき、その背後ではTcl/Tkのインタプリタが動作しています。 このインタプリタを使って、pythonからTcl/Tkのプログラム文を実行できます。また、Tcl/Tkインタプリタ内の変数の値を読み出したり、設定することも可能です。

Tcl/Tk 変数の読み書き

ptyhonプログラムが接続中のTcl/Tkのインタプリタ中の変数の現在値を, .getvar()メソッドを通じて読み込むことも できます。.setvar()で変数名と新しい値を与えると、その名前をもつTcl/Tkインタプリタの変数の値が変更されます。

 root.getvar( "tk_patchLevel")

Tcl/Tkコマンドの実行

Tkのオブジェクトrootで、root.call("command","arg1",...)を実行することで、Tcl/TkのコマンドをPythonプログラム中から実行できます。

また、Tcl/Tkの文を文字列として、Tcl/Tkのインタプリターに実行させることもできます。

pytho
  root=tkinter.Tk()
  root.call("expr", "$tk_patchLevel")

Tkのコマンドとtkinterの対応

tkinter, tkinter.ttkのウィジェットを使いこなすためには、それぞれのウィジェットの説明書を読む必要があります。 tkinter,tkinter.ttkTcl/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とは全く別物なので、注意。

Tk コマンド と tkinterの対応表

Tk8.6のコマンドとtkinterでそれに対応するクラス/関数/メソッドの一覧表を作成してみました。表のエントリはmanページの項目に対応しており、必ずしもコマンド名とは限りません(変数名や一般的な説明も含まれます)。

Tk-tkinter対応表

Tkのドキュメントについて

上記の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 のプログラム開発で参照する必要が出てくることも考えられます。

GridとPack

Tkの ウィジェットをウィンドウの中に配置するのには、grid, pack, placeという配置マネージャーが標準で用意されています。packは簡単に使えて便利ですが、複雑な配置を設定するには、ちょっと工夫が必要です。packは縦一列あるいは横一列にウィジェットを並べる場合にはとても便利です。一方、gridは2次元の格子状にウィジェットを配置するのに適しています。 (最近のTutorialではgridがまず使われいます。)

pack,gridではウィジェットのサイズを自動的に調整してくれる機能があります。

placeはウィジェットの表示する領域内での位置を正確にしていすることができます。この三つの配置マネージャーの中では、最大の自由度を持っていますが、表示が適切になされるかどうかは、設定次第です。

packgridを使い分けて組み合わせることも可能です。(packgridの混ぜ合わせができない場合もありますので、これには注意が必要です)

python3の公式ドキュメントのtkinter解説ページではPackerを中心に説明されていますが、その他の(Tkの解説書を含む)最近の文書ではGridを中心に解説しています。 私の印象としては、簡単なアプリケーションの場合は、Packerが便利ですが、ウィジェットの数が増えてくると、配置のためだけに数多くのFrameを使う必要があり、管理が大変になります。こういった場合はGridの方が便利でしょう。ということで、初めからGridを使うように最近は誘導しているのかなと思います。 複雑なウィジェットの配置が必要になった場合は、NotebookPanedWindowなどの複合ウィジェットを使うことも考えましょう。

Geometry Manager: GridとPackの基本的な使い方(考え方)

TkのウィジェットはTkToplevelを除き、それが所属するウィジェット(master)をもっています。通常ウィジェットを作成する際の最初の引数として与えられます。省略した場合はデフォルトのrootオブジェクトがmasterになります。ウィジェットのマスターオブジェクトは.masterメンバ変数で確認できます。 (pack,gridの呼び出し時にin_引数でmaterオブジェクトを指定することもできます。)

GridPackでは、同じオブジェクトをmasterとするウィジェットを、与えられた条件を考慮してウィジェットの位置や大きさを 自動的に調整して、配置してくれます。

Grid_Pack

Grid

Gridでは、ウィジェットを配置する窓/ウィジェットを格子状に分けて考えます。 複数の格子にまたがるウィジェットを配置することができます(columnspan, rowspan)。

* .grid(row, column, rawsspan, columnspan, sticky)
    * row, column: 行と列を0から始まる番号で指定します。
    * rawsspan, columnspan:縦横の占める列数/行数を指定します。
    * sticky: ウィジェットがそれを含む枠に対して小さい時の配置についてのパラメータ(N,S, E, W)

Pack

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)


Place

masterに対する位置をピクセル単位で指定できます。全てのウィジェットの配置を完全に制御することができます。 反面、一部のウィジェットの大きさを変更した場合や、さまざまな環境で窓自体の大きさが変わる環境でも適切に配置するためには、工夫が必要です。

複合ウィジェット

高度なウィジェットの配置は、Grid/Packerを複雑に組み合わせるよりも、paned Window, Notebookなどのウィジェットを 使うことを考えましょう。

余談:GridとPack

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だけを使うのが良さそうです。

次の例のように、GridPackのどちらでも、目指す配置を実現することはできます。 画面構成をあらかじめ決定して作成するには、Gridの方が便利でしょう。Packは融通が効きますが、望む配置を実現するためには、表示されないFrameウィジェットをを使うことになりがちです。

TkとToplevel

TkクラスとToplevelクラスはWm(Window manager)クラスを継承していて、デスクトップ上に独立した窓として表示されます。(その他のウィジェットはTkあるいは他のウィジェットの中に表示されます。)

tkinter.Tktkinter.Toplevelは どちらも画面上に独立した窓を開きます。ではこの「二つのものは同じか? 」というとそうではありません。 Toplevelはウィジェットの一つででもあります。 したがって上位にTkのマスターが存在します。異なるTkウィジェットは窓を閉じても、他のTkウィジェットに影響しません。

通常、一つのアプリケーションにはroot(従って、Tk()呼び出し)は一つだけです。rootの他に新たな窓が必要な場合にはToplevel()を呼び出します。 この場合には、rootのウィンドウを閉じると、アプリケーション全体が停止することを忘れないようにしましょう。

Tk and Totplevel

Tk()で作成されたrootを大元の親に持つオブジェクトは,rootに対応する窓を閉じると同時に消えてしまいます。

TkとToplevelの関係 TkはTcl/Tkインタプリタと結び付けられています。

新しいWidget部品を作る。

複数のウィジェットを組み合わせて作った表示画面を部品化(ウィジェット化)することで、 これらの部品を多数並べた表示画面を簡単に作れるようになります。 これは、新しいウィジェットクラス として定義することで実現できます。

複数の部品を組み合わせたウィジェットを作る場合は、Frameウィジェットを基に作ることがよくあります。 (このように、別の部品を基にしてして、新しい部品をつくることを、クラスの継承 (inheritance) と呼びます。)

初期化メンバ関数: __init__

クラス(設計図)からオブジェクト(部品, 実体)を作る際には、初期化メンバ関数.__init__が呼び出されます。

初期化メンバ関数.__init__の最初の引数は、これから作成されるオブジェクトその物と決まっています。 Pythonではこの引数を慣例的にselfとすることが多いです。しかし、thisでも良いし、meなどでも構いません。 (しかし、色々変わるのは長期的にみるとプログラムの読みやすさを若干とはいえ損なうということも考えておく必要があります。)

メンバ変数

初期化メンバ関数の中では、このクラスが持つ、メンバ変数 (上記の例では、self.msg, self.btn, self.qbtn)を初期化しておきます。

pythonではクラスのオブジェクトの実体化は、クラス名を関数であるかのように呼び出すことで実現されます。 これによって、オブジェクトの実体が作成され、.__init__関数による初期化が実行されたのち、 そのオブジェクトがこの関数呼び出しの結果として返されます。

obj=HellolWorldApp(tk)

クラス定義/オブジェクト作成/オブジェクト利用

クラスとオブジェクト

一度ウィジェットをクラス化(部品化)すれば、同じような動作をする部品を複数作成することは容易にできるようになります。 rootウィンドウとToplevelの ウィンドウにそれぞれHelloWorldAppのウィジェトを作成します。

Tk Variable

GUIアプリケーションを作成する際には、複数のウィジェットの値を連携させる必要がしばしば出てきます。たとえば、スライダーウィジェットを操作すると、 それに対応したTextあるいはEntryウィジェットの値が変化する。ラジオボタンの状態に応じて、別のウィジェットの見栄え(色、形、画像など)を変更 するといった具合です。 Tkにはこのような場合のプログラムを簡単にしてくれる、Tk Variable が用意されています。

Tkな特徴的な機能の一つにTk Variableがあります。 Variableには四つのサブクラス(子クラス)が定義されています。

BooleanVar, DoubleVar, IntVar, StringVar

これらのVariableクラスから作られたオブジェクトは、それぞれ、 ブール値(TrueあるいはFalse)、浮動小数点数、整数、文字列 のデータ型に対応しています。

Variableは複数のウィジェットが、プログラムの実行中に変化する(動的)共通のデータを持っているときに有効です。 Variableを使うことで、Tkではキー入力やマウスクリックのエベントを明示的に.bindで処理する必要が軽減されています。

Tk Variableを使って、 複数のウィジェットを連携させてみます。

Canvas ウィジェット

Canvasウィジェットを使うと、幾何学的な要素(直線、円、多角形、曲線)を画面上に自由に配置して描画することができます。 次に見るように、matplotlibのグラフもこのtkinter.Canvasウィジェットに書き込む形でtkinterアプリケーションに埋め込まれます。

以下にCanvasウィジェットを使ったプログラムを示します。Canvas内に円(楕円)、アーク(円周の一部)をいくつか配置しています。

Canvasの要素

Canvasの内部には、以下の要素を作成できます。作成には、canvas.create_xxxメソッドを使います。(xxxはそれぞれの要素の型名がはいります。)

matplotlibのグラフをTk のアプリケーションに埋め込む。

おおむねmatplotlibのグラフをtkinterのアプリケーションに埋め込むための準備が整ったので、その説明を始めます。

matplotlibはグラフを作成する仕組み(backend)を複数用意しています。Tkのアプリケーションにmatplotlibのグラフを埋め込むために、Tkをつかったbackend TkAggを使います。 TkAggではグラフをtkinter.Canvasウィジェットの中に書き込みます。このCanvasウィジェットをアプリケーションのウィジェット中に配置すれば、matplotlibのグラフがtkinterのアプリケーションの中に埋め込まれたことになります。

matplotlib backend

まず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は ウェブブラウザにグラフを出力します。

AggCairoはいずれも高品位な2Dグラフィックスを生成するライブラリです。Cairomatplotlibとは別にインストールする必要があります。 https://www.tutorialspoint.com/matplotlib-backend-differences-between-agg-and-cairo によれば、AggCairoの違いは、次の 表のようになっています。

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 backends

FigureCanvasTkAgg

matplotlibFigureCanvasTkAgg関数を使い、matplotlibのFigureオブジェクトを、tkinterCanvasウィジェットに結びつけます。 matplot側ではこのFigureオブジェクトに描画すると、tkinterCanvasの中にグラフが現れると言うわけです。

サンプルプログラム(下記)の

        self.fig=Figure(figsize=(16,9), dpi=72)
        self.canvas=FigureCanvasTkAgg(self.fig, master=self)  # A tk.DrawingArea.

でこれが実現されています。

Tk Dialog

このプログラムでは、askopenfilenameshowinfoなど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

余談: macosでのtkinterの運命やいかに?

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を使ったアプリケーションをかけるといいいなとか思うのですが。

matplotlib graph embedded in tk widgets.

An example from the matplot home page.

tkinterのcursor の名前

https://www.tcl.tk/man/tcl/TkCmd/cursors.html

man n cursorsでも確認できます。

tkinter(Tcl/Tk)で使える色名

https://www.tcl.tk/man/tcl/TkCmd/colors.html

man n colorsでも確認できます。