今回は tkinter で「ストップウォッチ」アプリを作成していきたいと思います!
「ストップウォッチ」自体は動作や作りがシンプルですので、特に tkinter 初心者の方にオススメの題材だと思います。
Contents
作成する「ストップウォッチ」アプリ
まずはどんなストップウォッチアプリを作るのかを簡単に説明します。
画面の構成は下記のような感じにしようと思います。
スタートボタンを押すと計測が開始されてラベルに計測時間が表示されます。
ラベルの計測時間はストップウォッチっぽく定期的に更新します。
さらに計測中にストップボタンを押すと計測が終了し、ラベルの時間の更新も終了します。
ストップウォッチアプリの画面の開発
どんなアプリを作るかを決めたところで、次はアプリを作っていきましょう!
まずはアプリの画面(UI)から作っていきたいと思います。
先程決めたように、アプリの画面は下の図のようになります。
まずはメインウィンドウを作成し、メインウィンドウの中にラベルとスタートボタン、ストップボタンを作成して配置していきます。
このアプリの画面を作成するスクリプトは下記のようになります。
# -*- coding:utf-8 -*-
import tkinter
# スタートボタンの処理
def start():
pass
# ストップボタンの処理
def stop():
pass
# メインウィンドウ作成
app = tkinter.Tk()
app.title("stop watch")
app.geometry("200x200")
# 時間計測結果表示ラベル
label = tkinter.Label(
app,
text="0.00",
width=6,
font=("", 50, "bold"),
)
label.pack(padx=10, pady=10)
# ストップウォッチのスタートボタン
start_button = tkinter.Button(
app,
text="START",
command=start
)
start_button.pack(pady=5)
# ストップウォッチのストップボタン
stop_button = tkinter.Button(
app,
text="STOP",
command=stop
)
stop_button.pack(pady=5)
# メインループ
app.mainloop()
スクリプトを実行すれば、下の図のようなアプリの画面が表示されると思います。
上記スクリプトでは label
は Label
クラス、start_button
と stop_button
は Button
クラスのインスタンスとして作成し、それぞれを pack
メソッドにより配置しています。
配置については下記ページで解説していますので、配置について詳しく知りたい方はこちらをご覧ください。
Tkinterの使い方:ウィジェットの配置(pack・grid・place)ボタンは配置していますが、現状だとボタンを押しても時間の計測は開始されません。
スタートボタンとストップボタンのインスタンス作成時(tkinter.Button()
実行時)に command
を指定することで、それぞれのボタンが押された時に下記の関数が実行されるようになっています。
- スタートボタン:
start
- ストップボタン:
stop
上記スクリプトでは start
も stop
も空の関数になっているのでボタンを押しても何も動作しません。
次はこの start
や stop
などに処理を記述してストップウォッチの機能面の開発を行なっていきます
スポンサーリンク
ストップウォッチアプリの機能の開発
アプリの画面が出来上がったところで、次はストップウォッチとして動作してくれるように機能を開発していきましょう!
ストップウォッチアプリの機能
ストップウォッチアプリの処理の全体的な流れを図示したものが下図になります。
スタートボタンが押されたら「時間の計測開始」を行い、それ以降は定期的に「ラベルの時間更新」を行います。
最後にストップボタンが押されたら「時間の計測終了」を行います。
つまり、ストップウォッチアプリにおいては、大きく分けて下記の3つの機能が必要ということになります。
- 時間の計測開始
- ラベルの時間更新
- 時間の計測終了
ここからは、これらの3つの機能の開発方法について解説していきたいと思います。特にストップウォッチアプリにおいては「ラベルの時間更新」がポイントとなりますので、この「ラベルの時間更新」から解説していきたいと思います。
ラベルの時間更新
ストップウォッチでは、計測時間(計測開始時点からの時間)が画面に表示され、停止するまでその計測時間が定期的に更新されていきます。
今回作成するアプリで計測時間を表示するのはラベルですので、定期的にラベルに表示する計測時間を更新する必要があります。
その方法について解説していきます。
ラベルの定期的な更新
tkinter でこういった定期的な処理を行うには、after
メソッドが便利です。
after
メソッドに関しては下記ページで解説していますので、こちらを参考にしていただければと思います。
ポイントは、定期的に実行させたい関数の中で、after
メソッドを実行するところです。
after
メソッドで再度その関数を一定時間遅らせて実行するようにしてやることで、その関数が一度実行されると、以降は自動的に定期的に実行されるようになります(定期的に処理が行われる間隔は after
メソッドの引数で指定可能です)。
今回定期的に行いたい処理は「計測時間のラベルへの表示」ですので、下記のように関数(update_time
)を作成することで、「計測時間のラベルへの表示」が定期的に行われるようになります。
after
メソッドの第1引数で INTERVAL
(中身は 10
) を設定していますので約 10ms 間隔で update_time
関数が定期的に実行されるようになります。
# ラベルを更新する間隔[ms]
INTERVAL = 10
# 時間更新関数
def update_time():
# update_time関数を再度INTERVAL[ms]後に実行
after_id = app.after(INTERVAL, update_time)
# 計測時間(elapsed_time_str)を計算
# 計測時間を表示
label.config(text=elapsed_time_str)
elapsed_time_str
は計測時間です。
次はこの計測時間をどのようにして求めるかについて解説していきます。
時間の計測
時間の計測は time
モジュールの time
関数(time.time()
) を利用して行うことができます。
time
関数は「実行した時点の時刻を返却する関数」になります。
ですので、計測を開始するタイミングで time
関数を実行し、以降に実行した time
関数実行時の返却値との差を求めることで、計測開始から経過した時間を計測することができます。
- 時間の計測開始時に
time
関数実行(返却値をstart_time
とする) - 再度
time
関数実行(返却値をnow_time
とする) now_time - start_time
の結果が 1. と 2. の間の時間になる
1. をストップウォッチのスタートボタンが押された時に実行し、2. と 3. をラベルの定期的な更新時に実行してやることで、ラベルにスタートボタンが押されてからの時間を定期的に更新しながら表示することができるようになります。
先ほど示した update_time
関数の中で 2. と .3 の処理を実行するようにしたものが下記のようになります。
# 時間更新関数
def update_time():
global start_time
global app, label
global after_id
# update_time関数を再度INTERVAL[ms]後に実行
after_id = app.after(INTERVAL, update_time)
# 現在の時刻を取得
now_time = time.time()
# 現在の時刻と計測開始時刻の差から計測時間計算
elapsed_time = now_time - start_time
# 表示したい形式に変換(小数点第2位までに変換)
elapsed_time_str = '{:.2f}'.format(elapsed_time)
# 計測時間を表示
label.config(text=elapsed_time_str)
この update_time
関数が1度実行されれば、後に説明する after_cancel
メソッドが実行されるまで、start_time
を取得時点からの経過時間が定期的にラベルに更新されながら表示されるようになります。
スポンサーリンク
時間の計測開始
「ラベルの時間更新」を行う update_time
関数が出来上がれば、ストップウォッチアプリはほぼ出来上がったようなものです。
残っている主な処理は下記の2つになります。
- 「ラベルの時間更新」を開始する
- 「ラベルの時間更新」を終了させる
前者をストップウォッチのスタートボタンクリック時に行い、スタートボタンクリック時に「時間の計測開始」が行えるようにします。
といっても「ラベルの時間更新」の開始は、単に update_time
関数を実行すれば良いだけです。
ただし、update_time
関数実行前には「時間の計測開始」時点の時刻(start_time
)を設定しておくことも必要です。
時間の計測開始を行う start
関数は下記のようになります。
# スタートボタンの処理
def start():
global app
global start_time
global after_id
# 計測開始時刻を取得
start_time = time.time()
# update_timeをINTERVAL[ms] 後に実行
after_id = app.after(INTERVAL, update_time)
update_time
関数を after
メソッドから実行していますが、直接実行しても良いです。
start
関数はストップウォッチアプリの画面の開発のスクリプトでスタートボタン(start_button
)クリック時に実行されるように command
設定が行われています。
ですので、スタートボタンクリック時に上記 start
関数が実行され、時間の計測・ラベルの時間の更新が開始されます。
時間の計測終了
時間の計測終了時(ストップウォッチのストップボタンクリック時)には下記の後者の処理を行います。
- 「ラベルの時間更新」を開始する
- 「ラベルの時間更新」を終了させる
update_time
関数は after
メソッドにより定期的に実行され、終了処理を行わないとアプリが終了するまで延々とラベルの時間更新が行われることになります。
ですので、時間の計測終了ができるように、その定期的な処理の終了処理を行います。
定期的な処理は after
メソッドが実行されることにより実現していますので、この after
メソッドをキャンセルしてやることで、定期的な処理を終了させることができます。
after
メソッドのキャンセルは after_cancel
メソッドの実行により行うことができます。
詳細は下記ページで解説していますので詳しく知りたい方は読んでみてください。
Tkinterの使い方:after で処理を「遅らせて」or 処理を「定期的」に実行するここまで紹介してきた udpate_time
関数と start
関数では after
メソッドの返却値を after_id
に参照させていますので、after_cancel
の引数にはこの after_id
を指定すれば良いです。
時間の計測終了を行う stop
関数は下記のようになります。
# ストップボタンの処理
def stop():
global after_id
# update_time関数の呼び出しをキャンセル
app.after_cancel(after_id)
stop
関数はストップウォッチアプリの画面の開発のスクリプトでストップボタン(stop_button
)クリック時に実行されるように command
設定が行われています。
ですので、ストップボタンクリック時に上記 stop
関数が実行され、時間の計測・ラベルの時間の更新が終了されます。
ストップウォッチアプリのサンプルスクリプト
ここまでストップウォッチアプリの画面や各機能を別々に開発してきました。これら全てを合わせたストップウォッチアプリのサンプルスクリプトは下記のようになります。
# -*- coding:utf-8 -*-
import tkinter
import time
# ラベルを更新する間隔[ms]
INTERVAL = 10
# 計測開始時刻
start_time = 0
# 時間計測中フラグ
start_flag = False
# afterメソッドのID
after_id = 0
# 時間更新関数
def update_time():
global start_time
global app, label
global after_id
# update_time関数を再度INTERVAL[ms]後に実行
after_id = app.after(INTERVAL, update_time)
# 現在の時刻を取得
now_time = time.time()
# 現在の時刻と計測開始時刻の差から計測時間計算
elapsed_time = now_time - start_time
# 表示したい形式に変換(小数点第2位までに変換)
elapsed_time_str = '{:.2f}'.format(elapsed_time)
# 計測時間を表示
label.config(text=elapsed_time_str)
# スタートボタンの処理
def start():
global app
global start_flag
global start_time
global after_id
# 計測中でなければ時間計測開始
if not start_flag:
# 計測中フラグをON
start_flag = True
# 計測開始時刻を取得
start_time = time.time()
# update_timeをINTERVAL[ms] 後に実行
after_id = app.after(INTERVAL, update_time)
# ストップボタンの処理
def stop():
global start_flag
global after_id
# 計測中の場合は計測処理を停止
if start_flag:
# update_time関数の呼び出しをキャンセル
app.after_cancel(after_id)
# 計測中フラグをオフ
start_flag = False
# メインウィンドウ作成
app = tkinter.Tk()
app.title("stop watch")
app.geometry("200x200")
# 時間計測結果表示ラベル
label = tkinter.Label(
app,
text="0.00",
width=6,
font=("", 50, "bold"),
)
label.pack(padx=10, pady=10)
# ストップウォッチのスタートボタン
start_button = tkinter.Button(
app,
text="START",
command=start
)
start_button.pack(pady=5)
# ストップウォッチのストップボタン
stop_button = tkinter.Button(
app,
text="STOP",
command=stop
)
stop_button.pack(pady=5)
# メインループ
app.mainloop()
start
関数と stop
関数に時間計測中かどうかを start_flag
で判断する処理を加えていますが、それ以外は前述したものと同じになると思います。
実行すると下の図のような画面のアプリが起動し、
スタートボタンをクリックすることで時間の計測が開始され、
ストップボタンをクリックすることで時間の計測が終了します。
再度スタートボタンをクリックすれば、再び 0 秒から時間の計測が再開されます。
スポンサーリンク
after
メソッドだけではダメな理由
このスクリプトを読んで「after
メソッドで 10ms 毎に定期的にラベルの時間を更新しているのだから、わざわざ time
関数使わなくても update_time
関数の実行回数から時間が計測できるのでは?」と思う方もいるかもしれません。
めちゃめちゃいい気付きだと思います。が、after
メソッドだけでは時間がずれてしまいます。
time
関数で時間を計測する場合と、time
関数は利用せず after
メソッドだけで時間を計測する場合の両方の計測時間を表示するようにしたスクリプトが下記のようになります。
# -*- coding:utf-8 -*-
import tkinter
import time
# ラベルを更新する間隔[ms]
INTERVAL = 10
# 計測開始時刻
start_time = 0
# 時間計測中フラグ
start_flag = False
# afterメソッドのID
after_id = 0
# update_timeの実行回数
count = 0
# 時間更新関数
def update_time():
global start_time
global app, label, label_after
global after_id
global count
# update_time関数を再度INTERVAL[ms]後に実行
after_id = app.after(INTERVAL, update_time)
# 現在の時刻を取得
now_time = time.time()
# 現在の時刻と計測開始時刻の差から計測時間計算
elapsed_time = now_time - start_time
# 表示したい形式に変換(小数点第2位までに変換)
elapsed_time_str = '{:.2f}'.format(elapsed_time)
# 計測時間を表示
label.config(text=elapsed_time_str)
# update_time関数の実行回数をカウントアップ
count += 1
# countから計測時間を計算
after_time = count * INTERVAL
# 表示したい形式に変換(小数点第2位までに変換)
after_time_str = '{:.2f}'.format(after_time / 1000)
# afterのみでの計測時間を表示
label_after.config(text=after_time_str)
# スタートボタンの処理
def start():
global app
global start_flag
global start_time
global after_id
global count
# 計測中でなければ時間計測開始
if not start_flag:
# 計測中フラグをON
start_flag = True
# 計測開始時刻を取得
start_time = time.time()
# update_timeの実行回数を初期化
count = 0
# update_timeをINTERVAL[ms] 後に実行
after_id = app.after(INTERVAL, update_time)
# ストップボタンの処理
def stop():
global start_flag
global after_id
# 計測中の場合は計測処理を停止
if start_flag:
# update_time関数の呼び出しをキャンセル
app.after_cancel(after_id)
# 計測中フラグをオフ
start_flag = False
# メインウィンドウ作成
app = tkinter.Tk()
app.title("stop watch")
app.geometry("200x250")
# 時間計測結果表示ラベル
label = tkinter.Label(
app,
text="0.00",
width=6,
font=("", 50, "bold"),
)
label.pack(padx=10, pady=10)
# afterのみでの時間計測結果表示ラベル
label_after = tkinter.Label(
app,
text="0.00",
width=6,
font=("", 50, "bold"),
)
label_after.pack(padx=10, pady=10)
# ストップウォッチのスタートボタン
start_button = tkinter.Button(
app,
text="START",
command=start
)
start_button.pack(pady=5)
# ストップウォッチのストップボタン
stop_button = tkinter.Button(
app,
text="STOP",
command=stop
)
stop_button.pack(pady=5)
# メインループ
app.mainloop()
スクリプトを実行すると下の図のような画面のアプリが表示されます。
上側のラベルには、ストップウォッチアプリのサンプルスクリプトでも紹介した time
関数を利用して時間を計測した計測時間を表示しています。
下側のラベルには、time
関数を利用せず、update_time
関数の実行回数(count
)を after
メソッドにより定期的に update_time
関数を実行する間隔(INTERVAL
、つまり 10ms)で掛け算することで計測した計測時間を表示しています。
スタートボタンを押すと下のようなアニメのように2つのラベルの数字が更新されていきます。
ですが、下側のラベルの時間がちょっとずつ遅れて行ってしまいます。こんな感じで after
メソッドだけだと時間がずれてしまいます。
この理由は下記ページでも解説していますが、after
メソッドで指定した関数が実行されるのは、指定した時間経過した後かつアプリが暇な時です。
ですので、上記スクリプトのように after
メソッドで 10 ms という短い間隔で定期的に処理を実行する場合などは、アプリが忙しくて after
メソッドに指定した update_time
関数が実行されるタイミングが遅れる場合があります。
このタイミングの遅れが積もり積もって、上の図のように time
関数利用時に比べて1秒以上の差が発生することになっています。
アプリが忙しくて update_time
関数の実行が遅れる様子は、アプリのサイズを変更したらしてみると分かりやすいと思います。
サイズ変更している間は after
で指定した update_time
関数が実行されず、ラベルの更新が停止します。
update_time
関数が実行されないと count
もカウントアップされないので、実行されなかった間の時間分、下側のラベルに表示される計測時間がズレることになります。
しかし、time
を利用した計測時間を表示する上側のラベルの時間はズレません。
これは、update_time
関数の実行がたとえ遅れたとしても、その遅れた分 time
関数から返却される時刻も遅れた時刻になるためです。結果的に遅れた分も含めた計測時間を表示することができます。
after
メソッドは定期的に処理を行うのに便利ですが、after
メソッドだけに頼るとこんな感じで定期的な処理の実行タイミングがずれてアプリとして機能しなくなる可能性もあります。
今回 time
関数を利用したように、after
メソッドでの定期処理の実行タイミングがずれることを考慮し、ずれても問題ないように設計することが重要です。もしくは定期処理の実行タイミングが多少ずれても問題ないアプリを作りましょう!
まとめ
このページでは Python で tkinter を利用してストップウォッチアプリを開発する方法の解説・サンプルスクリプトの紹介を行いました。
ストップウォッチアプリは作りや動作がシンプルですので、tkinter 入門者の方にはおすすめの題材だと思います。
特に after
メソッドをしっかり使えるようになるための練習に最適です。
定期的に処理を行うためには after
メソッドが便利です。この after
メソッドは tkinter でアプリ開発を行う上で必須級に重要なメソッドですので、しっかり覚えておきましょう!