案件でTkinterで特定の処理を一定時間おきに定期的に実行するような処理を実装する必要があったので、その際に得た知見をメモしておきます
Tkinterでの定期実行処理について
Tkinterでの定期実行処理
調べてみたところTkinterでの定期実行処理を実行するには、まず定期的に実行したい処理+それに加えて別に時間をカウントする処理を実装する必要があるみたいです。
簡単なものであればtime.sleep()とfor文を組み合わせるような処理で良いと思いますが、重たい処理になるとtk自体の動作(ボタンを押す/画面を移動させるなど)が重くなりますし、処理中は画面が固まるので、自分が使う分には適当でいいと思いますが、人に使ってもらう場合などに動作性を考えるとあまりいいアプローチではないように思います。
threadingとafter()を使う
こういう時に便利なのがtkinterの「afterメソッド」を使って時間経過を確認し、一定時間が経過したら「threading」で定期実行処理を行うという方法です。
まずafter()についてですが、こちらはtkinterにデフォルトで用意されているメソッドであり、これを実行すると内部で定期実行プロセスが起動し、指定した関数を一定時間ごとに実行してくれます。
jobid = root.after(5000, <関数名>) #5秒ごとに指定した関数を実行
これを終了させる場合はcancel()を使用します
if jobid is not None: root.after_cancel(jobid)
そして、次にthreadですが、これはプログラムの処理性能を上げるためによく使用される並列処理を実装するライブラリでこれを使うことでtkinterでの画面表示処理と別に定期実行処理を行うことができるためtkinterの内部で関数が処理中だと画面が固まる、応答がなくなるという問題点を解消することが可能です。
これを組み合わせることで現在時刻を定期的に取得し、指定した時刻>現在時刻となったタイミングで定期実行させたい関数を実行する、という流れにすると画面を固まらせることなく定期実行処理を行えます。
サンプルプログラム
以下は1分ごとにprint文を表示するプログラムです
import time import datetime import tkinter import threading import configparser import random import traceback import sys import ctypes # ログイン def exe(): loglist.insert(tkinter.END, "処理開始") loglist.insert(tkinter.END, "<やりたい定期実行処理>") print('<やりたい定期実行処理>') loglist.see(tkinter.END) Timer_CallBack() # タイマー処理 def Timer(): global NextDate Now = datetime.datetime.now() LastDate = Now.strftime("%Y/%m/%d-%H:%M") NextDate = Now + datetime.timedelta(minutes=int(1)) time_label.configure(text="最終実行時刻 >> "+LastDate) loglist.insert(tkinter.END, "処理終了 : 次回は{NEXT}に実行...".format(NEXT=NextDate.strftime("%Y/%m/%d-%H:%M"))) loglist.see(tkinter.END) Time_Check() return # ログイン用コールバック関数 def CallBack(): global login_thread login_btn.configure(text="コールバック中...", state="disabled") login_thread = threading.Thread(target=exe) login_thread.start() return #タイムチェック def Time_Check(): global jobid global NextDate global timer_thread NowTime = datetime.datetime.now() print(NowTime) jobid = window.after(5000, Time_Check) if(NowTime > NextDate): NextDate= NowTime + datetime.timedelta(minutes=1) timer_thread = threading.Thread(target=exe) timer_thread.start() if jobid is not None: window.after_cancel(jobid) jobid = None return # タイマー用コールバック関数 def Timer_CallBack(): global timer_thread timer_thread = threading.Thread(target=Timer) timer_thread.start() return # 万が一サブプロセスが残っていた場合は削除する関数 def killThemAll(): for thread in threading.enumerate(): if thread != threading.main_thread(): print(f"kill: {thread.name}") ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.native_id, ctypes.py_object(SystemExit)) def exe_break(): global jobid login_btn.configure(text="定期処理開始", state="normal") loglist.insert(tkinter.END, "処理を停止しました") # メインスレッド以外をプロセスキル killThemAll() print('処理停止') # バックグラウンドで動作しているafterメソッドを停止 if jobid is not None: window.after_cancel(jobid) jobid = None # Window生成 window = tkinter.Tk() window.geometry("500x270") frame = tkinter.Frame() frame.place(x=4,y=5) loglist = tkinter.Listbox(frame, height=15, width=60) loglist.pack() login_btn = tkinter.Button(text="定期処理実行", width=10, command = CallBack) login_btn.place(x=408,y=5) break_btn = tkinter.Button(text="停止", width=10, command = exe_break) break_btn.place(x=408,y=30) time_label = tkinter.Label(text="最終実行時刻 >> ...") time_label.place(x=4,y=249) window.title("AutoLiker") window.mainloop()
<アプリイメージ>
↑みたいな感じで処理とログが表示されます。今回は1分おきですが定期実行の頻度などは23行目のNextdatetimeの加算処理を弄れば変更できます。
関連記事: 【Python】Tkinterで現在時刻を表示する機能を実装する
関連記事:【Python 】Tkinterでの画像表示と一定時間ごとの表示切り替え機能の実装
関連記事:【Python】Pyinstallerでpyファイルを配布用に.exe化する
コメント
[…] 関連記事:【Python】Tkinterで定期実行処理を実装する […]
[…] 関連記事:【Python】Tkinterで定期実行処理を実装する […]
[…] 関連記事:【Python】Tkinterで定期実行処理を実装する […]
[…] 関連記事:【Python】Tkinterで定期実行処理を実装する […]