tkinterでPython GUIプログラミング入門
PySimpleGUIは触っているが、その下のレイヤーで動くtkinter自体は触ったことがなかったので、このタイミングで入門してみようと思う。
一通りtkinterを使ってGUIアプリを作れるようになるところまでをこの記事内に書いていく。
長くなると思うので目次を要参照。
目次
- 目次
- 環境
- 参照するドキュメント
- ウィンドウだけを表示する
- テキスト(Label)をウィンドウ上に表示させる
- ウィンドウのサイズを指定
- ウィンドウタイトルを指定する
- ラベル(Label)の内容を更新する
- ラベルの位置を指定する(pack)
- ラベルの位置を指定する(place)
- ラベルの位置を指定する(grid)
- 入力フォームを作る
- 入力フォームの値を取得して、ボタンを押したら入力内容をダイアログに表示する方法
- tkinterで使えるダイアログの種類
- tkinterを使ってログ出力エリアにログを出力する。
- 表示させたログ(ScrolledText)の内容をクリップボードに貼り付ける
- 表示させたログ(ScrolledText)の内容を削除する
- ログの表示がリアルタイムに反映されない時
- tkinter上からSeleniumを起動させる
- tkinterとsqlite3を組み合わせてアプリを作る
環境
自身の試す環境は下記の通り。
- MacBook Air (M1, 2020)
- OSはBigSur
- Python 3.10.1
参照するドキュメント
tkinterについて調べようとしたのだが、困ったことにまとまった公式のドキュメントが非常にわかりにくい or 古くて今でも有効なのかがよく分からなかった。
日本語でまとめられているドキュメントでPython 3系での記述もありのドキュメントを見つけたので、こちらを参照しながら一つずつ試してみようと思う。
(なお、途中で内容はアレンジしたり適宜飛ばしたりしていますのでご了承ください。気になる方は下記の記事もチェケラ!)
https://nnahito.gitbooks.io/tkinter/content/
ウィンドウだけを表示する
シンプルにウィンドウだけを表示する
import tkinter as tk window = tk.Tk() window.mainloop()
テキスト(Label)をウィンドウ上に表示させる
テキスト表示のサンプル
import tkinter as tk window = tk.Tk() label = tk.Label(text="ハローワールド") label.pack() window.mainloop()
一気に画面が小さくなった。。。
ウィンドウのサイズを指定
下記の方法(コメント参照)でウィンドウサイズを指定できる。
import tkinter as tk window = tk.Tk() # windowのサイズを指定 window.geometry("400x300") label = tk.Label(text="ハローワールド") label.pack() window.mainloop()
ウィンドウタイトルを指定する
ウィンドウタイトルの指定方法は下記の通り。
import tkinter as tk window = tk.Tk() # window タイトルを指定 window.title("ハローワールド") # windowのサイズを指定 window.geometry("400x300") label = tk.Label(text="ハローワールド") label.pack() window.mainloop()
ラベル(Label)の内容を更新する
こちらについては別のポストとして書いた。
ここでは概要のみに留めるが、下記のようにすることでテキストを変更することが可能。
label["text"] = "変更後のテキスト"
ラベルの位置を指定する(pack)
ここまで何の説明もなく label.pack()
というように pack
を用いてラベルを表示してきた。
このpack
はラベルをGUI上にいい感じに要素を配置してくれる機能で、細かい指定はどうでもいいからいい感じにパーツを画面上に配置したい、というケースで重宝する。
ただ、この pack
を利用する場合でも、ある程度位置を調整したいケースがある。
pack側では下記のようなオプションが用意されているので、そちらを用いて適宜対応していくような形となる。
anchor
配置可能なスペースに余裕がある場合、対象のパーツをどこに配置するかを指定できる。
例えば指定されたエリアの中で左寄せしたい、などのケースではこのオプションが有効。
指定可能な内容は以下の通り。
tk
内に定数が用意されているのでそちらを参照する形で指定するのが良い。
- tk.CENTER - 中央
- tk.W - 左寄せ
- tk.E - 右寄せ
- tk.N - 上寄せ
- tk.S - 下寄せ
- tk.NW - 左上
- tk.SW - 左下
- tk.NE - 右上
- tk.SE - 右下
expand
親のウィジェットが大きくなった際に、一緒に大きくなるかどうかを指定できる
expand=1
という形で指定できる。
逆にあえて指定しない場合は 0
fill
スペースを埋めて表示するかどうかを指定できる。
例えば横方向に埋めたい場合、fill="x"
と指定すると、親のウィジェットに対して対象のウィジェットが横方向に敷き詰められた状態で表示できる。
利用できるオプションは以下の通り。
fill="none"
- 元のサイズを保持fill="x"
- 横に広がるfill="y"
- 縦に広がるfill="both"
- 縦横に広がる
padding系オプション
CSSで言う、いわゆるmargin・padding系の指定が下記となる。
以下のように隙間は数値で指定する(例では 1
を適用)
padx=1
外側の横の隙間を設定pady=1
外側の縦の隙間を設定ipadx=1
内側の横の隙間を設定ipady=1
内側の縦の隙間を設定
side
使ってみた感じはCSSのfloatのような感じ。
どの方向に詰めていくかを指定できる。
side="top"
- 上から詰めていくside="left"
- 左から詰めていくside="right"
- 右から詰めていくside="bottom"
- 下から詰めていく
packの指定については以上。
これらのオプションを使っても要件を満たせなさそうな場合は、次に書いていくplaceやgridなどを利用していくことになる。
ラベルの位置を指定する(place)
pack
はラベルをいい感じに配置してくれる機能だが、より細かく位置を指定したい場合は place()
を利用するようだ。
import tkinter as tk window = tk.Tk() # window タイトルを指定 window.title("ハローワールド") # windowのサイズを指定 window.geometry("400x300") label = tk.Label(text="ハローワールド") label.place(x=100, y=100) window.mainloop()
数値で指定するというのは実際には難しいので、ここらへんは pack()
を使って適宜対応していくのかどうなのか。
おそらくPySimpleGUIはここらへんの座標関係の処理もいい感じに行っていてくれているのだろう、と想像しながらも次に行く。
ラベルの位置を指定する(grid)
またpack
や place
とは別に grid
というものもある。
これはrow
や column
を用いて位置を指定することができる。
また sticky
を用いることで左に寄せて配置などのようなことが行える。
gridについては下記のポストにまとめたので、こちらを参照してみてください。
入力フォームを作る
次はtkinterで入力フォームを作成する。
import tkinter as tk window = tk.Tk() # window タイトルを指定 window.title("ハローワールド") # windowのサイズを指定 window.geometry("400x300") label = tk.Label(text="あなたの気分を入力してください") label.pack() text_form = tk.Entry() text_form.pack() window.mainloop()
まあまあ
と入力しようと思ったのに、困ったことに日本語入力にならない!
tkinterで日本語入力を行う方法(pyenv環境でも動くことを確認)
調べてみると、どうやらこれはTcl/Tkパッケージによるバグらしい、という記事を見た。
ちなみにこのバグに該当するバージョンが 8.5
のようだが、私の環境はまさにそれだった。
python -c "import tkinter;print(tkinter.TkVersion)" # => 8.5
pyenvでインストールしたPythonやmacOS 10.6以降のユーザでMac版Pythonインストーラを使っている方は該当するそうな。
で、どうすれば良いのか調べてみると下記のページに行き着いた。
結論から先に書くと、Pythonのversionを新しものにすれば、それに付随してついてくるTcl/Tkパッケージも新しいものになるので、新しいPythonを使えばOKとのこと。
私はpyenvを利用してPython環境を構築しているので、下記のようにPythonの 3.10.1
をインストールした。
pyenv install 3.10.1 # 最近私はglobalで設定して使っていますが、環境ごとに分けたいならlocalにしておいてください pyenv global 3.10.1
3.10.1
を入れると、TkVersion
も 8.6
になる。
python -c "import tkinter;print(tkinter.TkVersion)" # => 8.6
これで日本語入力ができない問題は無事に解決。
入力フォームの値を取得して、ボタンを押したら入力内容をダイアログに表示する方法
次はtkinterを使って入力フォームの値を取得し、ボタンを押したらその内容をダイアログに表示する方法。
一気にやることを増やしてしまったが、まあ、より実践的な内容にシフトしていこうと思う。
import tkinter as tk import tkinter.messagebox as tkm window = tk.Tk() # window タイトルを指定 window.title("ハローワールド") # windowのサイズを指定 window.geometry("400x300") # ボタンが押されたらこの関数が呼ばれる def show_msg(): # text_formの値を取得 value = text_form.get() # ダイアログに表示 tkm.showinfo("入力されたテキスト", value) label = tk.Label(text="あなたの気分を入力してください") label.pack() text_form = tk.Entry() text_form.pack() # ボタンを設置(commandに実行時のイベントを設定) button = tk.Button(text="入力値を表示", width=50, command=show_msg) button.pack() window.mainloop()
これでボタンを押すとテキストフォームに入力された値をダイアログに表示することができる。
ボタンに設定したイベント内でそのまま引数を渡したい時
上のやり方だと呼び出されたshow_msg
関数内で入力値を取得しているが、その値を関数に引数として渡したいこともある。
そういうときは下記のように command=lambda
と書いてlambda式を使うことで実現できるようだ。
import tkinter as tk import tkinter.messagebox as tkm window = tk.Tk() # window タイトルを指定 window.title("ハローワールド") # windowのサイズを指定 window.geometry("400x300") # ボタンが押されたらこの関数が呼ばれる def show_msg(value): # ダイアログに表示 tkm.showinfo("入力されたテキスト", value) label = tk.Label(text="あなたの気分を入力してください") label.pack() text_form = tk.Entry() text_form.pack() # ボタンを設置(引数を渡す場合はlambdaを使う) button = tk.Button(text="入力値を表示", width=50, command=lambda: show_msg(text_form.get())) button.pack() window.mainloop()
tkinterで使えるダイアログの種類
ダイアログには様々な種類があるので、そちらを画像とともに載せておく。
下記のようなコードを用意してそれぞれ試した。
import tkinter as tk import tkinter.messagebox as tkm window = tk.Tk() window.title("ダイアログサンプル") window.geometry("400x300") def show_info(): tkm.showinfo("普通のダイアログ", "テストメッセージ") def show_warn(): tkm.showwarning("警告ダイアログ", "テストメッセージ") def show_error(): tkm.showerror("エラーダイアログ", "エラーメッセージ") # YESがクリックされたら戻り値がTrue、NOならFalse def show_askyesno(): print(tkm.askyesno("YES or NO で答えられるダイアログ", "テストメッセージ")) # リトライがクリックされたら戻り値がTrue、キャンセルならFalse def show_askretrycancel(): print(tkm.askretrycancel("Retry or Cancel で答えられるダイアログ", "テストメッセージ")) # Yesがクリックされたら戻り値がyes、Noならno def show_askquestion(): print(tkm.askquestion("OK or NO で答えられるダイアログ", "テストメッセージ")) # OKがクリックされたら戻り値がTrue、CancelならFalse def show_askokcancel(): print(tkm.askokcancel("OK or Cancel で答えられるダイアログ", "テストメッセージ")) info_btn = tk.Button(text="Info", width=20, command=show_info) info_btn.pack() warn_btn = tk.Button(text="Warn", width=20, command=show_warn) warn_btn.pack() error_btn = tk.Button(text="Error", width=20, command=show_error) error_btn.pack() askyesno_btn = tk.Button(text="Yes/No", width=20, command=show_askyesno) askyesno_btn.pack() retry_btn = tk.Button(text="Retry/Cancel", width=20, command=show_askretrycancel) retry_btn.pack() question_btn = tk.Button(text="OK/No", width=20, command=show_askquestion) question_btn.pack() ok_btn = tk.Button(text="OK/Cancel", width=20, command=show_askokcancel) ok_btn.pack() window.mainloop()
普通のダイアログ
警告ダイアログ
エラーダイアログ
Yes/Noダイアログ
Retry/Cancelダイアログ
Yes/Noダイアログ(askquestion)
OK/Cancelダイアログ
tkinterを使ってログ出力エリアにログを出力する。
tkinterのログについては下記のポストがとても参考になった。
こちらの内容を参考にさせていただきつつ、実際にログ出力のサンプルとして書いてみたコードが下記となる。
import queue import logging import signal import tkinter as tk from tkinter.scrolledtext import ScrolledText from tkinter import ttk, VERTICAL, HORIZONTAL, N, S, E, W logger = logging.getLogger(__name__) class QueueHandler(logging.Handler): def __init__(self, log_queue): super().__init__() self.log_queue = log_queue def emit(self, record): self.log_queue.put(record) class ConsoleUi: def __init__(self, frame): self.frame = frame # ScrolledTextウィジェットを作成する self.scrolled_text = ScrolledText(frame, state="disabled", height=12) # gridもpackやplaceと同じようにウィジェットの配置に関する関数 self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) # フォントの設定 self.scrolled_text.configure(font="TkFixedFont") # タグ名に対応するオプションを設定する(例: CRITCALが指定された場合、赤文字の下線ありで表示される) self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orange') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1) # queueを用いてlogging handlerを作成する self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) # ログのフォーマットの設定 formatter = logging.Formatter('%(asctime)s: %(message)s') self.queue_handler.setFormatter(formatter) # handlerをloggerに追加 logger.addHandler(self.queue_handler) # queueからのメッセージをポーリングしていく # after関数を用いて、100msecごとにpoll_log_queueを実施 self.frame.after(100, self.poll_log_queue) def display(self, record): # state="normal"にしないと書き込みが行えない。そのため一時的にnormalを設定 self.scrolled_text.configure(state="normal") msg = self.queue_handler.format(record) # insert関数を用いてscrolled_textにログを渡す。3つめの引数にはタグを渡している(INFO, WARNINGなど) self.scrolled_text.insert(tk.END, record.levelname + ":" + msg + "\n", record.levelname) # ユーザが編集できないように再び"disabled"に戻す self.scrolled_text.configure(state='disabled') # 下にオートスクロール self.scrolled_text.yview(tk.END) def poll_log_queue(self): # 表示するメッセージがキューに存在するかどうかを100msecごとに確認する while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue) class InputLogUi: def __init__(self, frame): self.frame = frame tk.Label(self.frame, text="出力したいログメッセージを入力してください").grid(column=0, row=1, sticky=W) log_form = tk.Entry(self.frame) log_form.grid(column=0, row=2, sticky=W) tk.Button(self.frame, text="DEBUGログ", width=10, command=lambda: self.debug_log(log_form.get())).grid(column=0, row=3, sticky=W) tk.Button(self.frame, text="INFOログ", width=10, command=lambda: self.info_log(log_form.get())).grid(column=0, row=4, sticky=W) tk.Button(self.frame, text="WARNログ", width=10, command=lambda: self.warn_log(log_form.get())).grid(column=0, row=5, sticky=W) tk.Button(self.frame, text="ERRORログ", width=10, command=lambda: self.error_log(log_form.get())).grid(column=0, row=6, sticky=W) tk.Button(self.frame, text="CRITICALログ", width=10, command=lambda: self.critical_log(log_form.get())).grid(column=0, row=7, sticky=W) def debug_log(self, log_msg): level = logging.DEBUG logger.log(level, log_msg) def info_log(self, log_msg): level = logging.INFO logger.log(level, log_msg) def warn_log(self, log_msg): level = logging.WARNING logger.log(level, log_msg) def error_log(self, log_msg): level = logging.ERROR logger.log(level, log_msg) def critical_log(self, log_msg): level = logging.CRITICAL logger.log(level, log_msg) class App: def __init__(self, root): self.root = root root.title("Sample Logging Handler") root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) # PanedWindowを作成 vertical_pane = ttk.PanedWindow(self.root, orient=VERTICAL) vertical_pane.grid(row=0, column=0, sticky="nsew") horizontal_pane = ttk.PanedWindow(vertical_pane, orient=HORIZONTAL) vertical_pane.add(horizontal_pane) # Consoleフレームを作成 console_frame = ttk.Labelframe(horizontal_pane, text="ログコンソール") console_frame.columnconfigure(0, weight=1) console_frame.rowconfigure(0, weight=1) horizontal_pane.add(console_frame, weight=1) input_log_frame = ttk.Labelframe(vertical_pane, text="ログ入力フォーム") vertical_pane.add(input_log_frame, weight=1) self.console = ConsoleUi(console_frame) self.input_log = InputLogUi(input_log_frame) ### アプリの終了に関する処理 # 終了イベント(WM_DELETE_WINDOW)をquit関数に置き換え self.root.protocol('WM_DELETE_WINDOW', self.quit) # ctrl + qでquit関数を呼び出す self.root.bind('<Control-q>', self.quit) # SIGINTシグナルが送られたらquit関数 signal.signal(signal.SIGINT, self.quit) def quit(self, *args): logging.log(logging.INFO, "アプリを終了します") self.root.destroy() def main(): logging.basicConfig(level=logging.DEBUG) root = tk.Tk() app = App(root) app.root.mainloop() if __name__ == "__main__": main()
こちらのコードを実行してみると、下記のようなウィンドウが起動する。
フォームにログメッセージを入力して各ボタンを押下することで、対応したログ出力を再現できる。
ちなみにこのログ出力のサンプルコードを書くにあたって、tkinterに関するその他の機能についても同時に学ぶことができたので、そちらの内容についても書いていこうと思う。
Queueを用いてログ出力を行う
これは上に貼ったポストに書いてあったものだが、スレッド間でデータを共有するためにQueueが利用されている。
class QuereHandler(logging.Handler): def __init__(self, log_queue): super().__init__() self.log_queue = log_queue def emit(self, record): self.log_queue.put(record) ・ ・ ・ # queueを用いてlogging handlerを作成する self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue)
ハンドラー自体はメッセージをキューに入れるだけとなっており、キューからメッセージをポーリングして、ログを出力させるというような設計になっている。
キューからログメッセージを取得して表示させる部分については下記の poll_log_queue
関数で行われている。
def poll_log_queue(self): # 表示するメッセージがキューに存在するかどうかを100msecごとに確認する while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue)
ScrolledTextに書き込みを行う方法
今回ScrolledTextクラスを用いてログを表示させているが、ScrolledTextには state
という状態を管理する属性があり、こちらが normal
のときでしか書き込みを行うことができない。
だが、normal
のままにするとログ表示箇所にユーザが自由に書き込みできてしまうため、普段はユーザが書き込みできないように state=disabled
を指定しておく必要がある。
よって、下記のように書き込み処理を行う間だけ一時的にstateを変更している。
下記はログの出力処理を行うdisplay関数。
def display(self, record): # state="normal"にしないと書き込みが行えない。そのため一時的にnormalを設定 self.scrolled_text.configure(state="normal") msg = self.queue_handler.format(record) # insert関数を用いてscrolled_textにログを渡す。3つめの引数にはタグを渡している(INFO, WARNINGなど) self.scrolled_text.insert(tk.END, record.levelname + ":" + msg + "\n", record.levelname) # ユーザが編集できないように再び"disabled"に戻す self.scrolled_text.configure(state='disabled') # 下にオートスクロール self.scrolled_text.yview(tk.END)
ScrolledTextにタグを設定する方法
また、このScrolledTextにはタグを設定することが可能で、下記のように指定したタグに応じた文字の装飾などを行うことができる。
# タグ名に対応するオプションを設定する(例: CRITCALが指定された場合、赤文字の下線ありで表示される) self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orange') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1)
このように tag_config
関数でタグを設定した場合、scrolled_textに対して出力処理を行う insert
関数内の第3引数に指定したタグを与えれば良い。
# insert関数を用いてscrolled_textにログを渡す。3つめの引数にはタグを渡している(INFO, WARNINGなど) self.scrolled_text.insert(tk.END, record.levelname + ":" + msg + "\n", record.levelname)
afterを用いて継続的に処理を実施
tkinterにはafterメソッドというものが用意されており、こちらも用いることで、指定した処理を一定間隔で定期的に実行することが可能となる。
今回の場合、下記のようなログのポーリング処理で利用している。
# queueからのメッセージをポーリングしていく # after関数を用いて、100msecごとにpoll_log_queueを実施 self.frame.after(100, self.poll_log_queue)
追記:ログ出力のサンプルについて
表示させたログ(ScrolledText)の内容をクリップボードに貼り付ける
上のログサンプルを進化させて、ボタンを押したら表示されているログをクリップボードに貼り付けるようにした。
クリップボードを利用するには、 clipboard_append
を利用すれば良いようだが、ScrolledTextの値については scrolled_text.get()
だけでは取得できない。
こちらについては下記のstack overflowが参考になった。
どうやら下記のように引数を渡すことで値を読み取ることができた。
クリップボードにコピーする部分も含めて下記のように書くことで処理は実現可能。
def copy_clipboard(self): self.scrolled_text.clipboard_append(self.scrolled_text.get("1.0", tk.END)) tkm.showinfo("Copy to clipboard", "Copied log to clipboard.")
実際に上に書いたログ出力サンプルにクリップボードへのコピー機能を足した際のコミットがこちら。
frameを跨いだ場合の関数の渡し方はどうするのが良いのかまだ理解できていないが、ひとまずこれで別フレームで定義したクリップボードコピーボタンに対して、ScrolledTextで表示されている最初から最後までの文章をすべてクリップボードにコピーできるようになった。
表示させたログ(ScrolledText)の内容を削除する
上に続いて、今度はScrolledTextの中身を削除する方法。
再び、上に書いたログ出力サンプルに実際に機能を足したので、そちらのコードをこちらに記載する。
def clear_log(self): if tkm.askyesno("Clear log", "Are you sure you want to delete the log?") is True: self.scrolled_text.configure(state='normal') self.scrolled_text.delete("1.0", tk.END) self.scrolled_text.configure(state='disabled')
このようにscrolled_text.delete("1.0", tk.END)
とし、値を取得するときと同様に範囲を指定する必要があるが、全て取得するのであれば delete("1.0", tk.END)
で問題ない。
なお、ScrolledTextはあくまでログ出力の表示コンソールとして設定しているもののため、普段はユーザが内容を変更できないように self.scrolled_text.configure(state='disabled')
を指定して、編集無効としている。
そのため値の削除の前に、 state='normal'
を指定して値を編集可能な状態にする必要がある。
これは ScrolledTextに書き込みを行う方法
にも書いた内容と同じである。
実際に機能追加した際のコミット内容は下記となる。
ログの表示がリアルタイムに反映されない時
上に書いたログ表示に関する続き。
例えばtkinterからボタンを押して下記のような関数を実行した場合。
def all_log(self, log_msg): log_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] # ここでログ出力を行っているが、これはリアルタイムに反映されない for level in log_levels: self.log(level, log_msg) sleep(1)
この self.log
という関数が実行されると、tkinterのログ出力用の窓にログが表示されていく、という仕組みだとして、この all_log
関数をtkinterのボタン経由でそのまま実行してもログはリアルタイムに表示されない。
ログが表示されるタイミングは all_log
関数がすべて実行され終えたあとに、まとめてログが表示されるような流れとなる。
これはこの関数が実行されている間はGUIの描画処理は行われないためである。
そのため、このような処理でリアルタイムにログの描画も行うためにはこの all_log
関数を別スレッドで実行していくようにする。
具体的には下記のようなコードに書き換えると、この関数自体は別スレッドでの処理となるため、ログ表示に関するGUI側の処理も関数処理と並行して行われるようになる。
import threading ・ ・ ・ def all_log(self, log_msg): def thread_all_log(): log_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] for level in log_levels: self.log(level, log_msg) sleep(1) thread = threading.Thread(target=thread_all_log) thread.start()
なお、実際にこちらの修正はGitHub上でも確認可能となっている。
Add thread · shinshin86/sample-tkinter-log-output@e9b5529 · GitHub
tkinter上からSeleniumを起動させる
上のスレッドの話と関連する話となるが、例えばtkinter上でボタンを押すとSeleniumが起動して何らかのスクレイピング処理が動くものとする。
その際に普通にSeleniumを用いた処理を実行していると、実行している間はGUIが操作不能となる。
これは上にも書いたログの描画と同様に、別の処理の実行中に同時にGUIの描画ができないためとなる。
そのためこのようなケースに置いてもスレッドを別途作成して、Seleniumによる処理を別スレッドでの処理するようにすることで、Seleniumを用いた処理が動いている間もGUIの操作が行えるようになる。
サンプルコード的には上に書いたログ表示のものとそれほど変わらないものとなるので割愛するが、そのうち時間を見つけてこちらに関するサンプルコードも追記するかもしれない。
tkinterとsqlite3を組み合わせてアプリを作る
sqlite3(SQLite)と組み合わせてtkinterでGUIアプリを作ることも可能。
こちらの内容については別ポストとして書いた。