tkinter:アナログ時計のマイクロ秒針の動きが非連続になる
解決したいこと
プログラム上の時計の良い点は、機械的動作の制限がないことと考えています。
そこで、アップルウォッチの文字盤に似せた上で、実際のアナログ時計ではみられないマイクロ秒針または秒点(まずは1秒で1周)を作成してみました。
開始時には滑らかな円運動を描いていますが、やがて動きが緩慢となり、最終的には2、3箇所の座標をずれながら移動するようになります。
おそらく処理速度の遅延が関与していると疑っていますが、これを解決できる方法、さらにはより早く周回させる方法はありますでしょうか。ご教授よろしくお願いします。
発生している問題・エラー
マイクロ秒針・秒点が滑らかに動くには、update関数のafterの引数指定は4が適切と考えていますが、1分以内に動作が緩慢となります。
ソースコード上では、マイクロ秒点で記述おり、マイクロ秒針は三連引用符で囲っています。両方の動作を比較した場合、マイクロ秒点の方が、動作がおかしくなるのが早い印象があります。
なお、引数指定が1000の場合、処理速度の遅延を処理する方法として以下の記述を教えて頂きました。
self.master.after(1000 - microsecond // 1000, self.update)
しかし、本件では引数の数値と処理速度が近似しているため、この手法は使うことができませんでした。
なお、ソースコード上でこの記述をコメントアウトしていますが、これを実行した場合に、引数が単に1000の場合は、マイクロ秒点がわずかずつ進むので、処理速度の遅延の蓄積を視覚化できたのは、面白い現象だと感じました。
該当するソースコード
# -*- coding: utf-8 -*-
import tkinter as tk
import math
from datetime import datetime, timedelta, timezone
# キャンバスのサイズの設定
CANVAS_WIDTH = 400
CANVAS_HEIGHT = CANVAS_WIDTH
CANVAS_SIZE = CANVAS_WIDTH
# 針の長さの設定
LENGTH_HOUR_HAND = CANVAS_SIZE / 2 * 0.5
LENGTH_MINUTE_HAND = CANVAS_SIZE / 2 * 0.7
LENGTH_SECOND_HAND = CANVAS_SIZE / 2 * 0.8
LENGTH_MICROSEC_HAND = CANVAS_SIZE / 2 * 0.9
# 針の色の設定
COLOR_HOUR_HAND = "white"
COLOR_MINUTE_HAND = "white"
COLOR_SECOND_HAND = "red"
COLOR_MICROSEC_HAND = "orange"
# 針の太さの設定
WIDTH_HOUR_HAND = 10
WIDTH_MINUTE_HAND = 8
WIDTH_SECOND_HAND = 2
WIDTH_MICROSEC_HAND = 3
# 時計画面の色設定
BG_COLOR = "Gainsboro"
FG_COLOR = "gray"
# 時計の盤面を表す円の半径の設定
CLOCK_OVAL_RADIUS = CANVAS_SIZE / 2
# 時計の数字の位置の設定(中心からの距離)
DISTANCE_NUMBER = CANVAS_SIZE / 2 * 0.85
class Timer:
JST = timezone(timedelta(hours=9))
@classmethod
def time(cls):
# 時刻の取得
now = datetime.now(tz=cls.JST)
return now.hour, now.minute, now.second, now.microsecond
@classmethod
def time_string(cls):
now_s = datetime.now(tz=cls.JST)
now_st =now_s.strftime('%H:%M:%S')
return now_st
class Drawer:
def __init__(self, master):
self.master = master
# 描画した針のオブジェクトを覚えておくリストを用意
self.hands = []
# 針の色のリストを用意
self.colors = [
COLOR_HOUR_HAND, COLOR_MINUTE_HAND, COLOR_SECOND_HAND, COLOR_MICROSEC_HAND
]
# 針の太さのリストを用意
self.widths = [
WIDTH_HOUR_HAND, WIDTH_MINUTE_HAND, WIDTH_SECOND_HAND, WIDTH_MICROSEC_HAND
]
# 針の長さのリストを用意
self.lengths = [
LENGTH_HOUR_HAND, LENGTH_MINUTE_HAND, LENGTH_SECOND_HAND, LENGTH_MICROSEC_HAND
]
# キャンバスの中心座標を覚えておく
self.center_x = CANVAS_WIDTH / 2
self.center_y = CANVAS_HEIGHT / 2
self.createClock()
def createClock(self):
self.canvas = tk.Canvas(
self.master,
width=CANVAS_WIDTH,
height=CANVAS_HEIGHT,
highlightthickness=0,
)
self.canvas.pack()
# 時計の盤面を表す円を描画する
x1 = self.center_x - CLOCK_OVAL_RADIUS
y1 = self.center_y - CLOCK_OVAL_RADIUS
x2 = self.center_x + CLOCK_OVAL_RADIUS
y2 = self.center_y + CLOCK_OVAL_RADIUS
self.canvas.create_oval(
x1, y1, x2, y2,
fill=BG_COLOR,
width=2,
outline=FG_COLOR
)
# 時計の盤面上に数字を描画する
for hour in range(1, 13):
angle = hour * 360 / 12 - 90
# 描画位置を計算
x1 = self.center_x
y1 = self.center_x
dx = DISTANCE_NUMBER * math.cos(math.radians(angle))
dy = DISTANCE_NUMBER * math.sin(math.radians(angle))
x2 = x1 + dx
y2 = y1 + dy
self.canvas.create_text(
x2, y2,
font=("Times New Roman", 40),
fill="white",
text=str(hour)
)
#装飾
x1 = self.center_x - CLOCK_OVAL_RADIUS*0.65
y1 = self.center_y - CLOCK_OVAL_RADIUS*0.65
x2 = self.center_x + CLOCK_OVAL_RADIUS*0.65
y2 = self.center_y + CLOCK_OVAL_RADIUS*0.65
self.canvas.create_oval(
x1, y1, x2, y2,
fill="silver",
width=10,
outline="lightgray"
)
# 針を描画する'''
def drawHands(self, hour, minute, second, microsec):
# 各線の傾きの角度を計算指定リストに追加
angles = []
#時針も1分ずつ動かす
hour_minute = (hour*360/12) +(minute*30/60)
angles.append(hour_minute - 90)
angles.append(minute * 360 / 60 - 90)
angles.append(second * 360 / 60 - 90)
angles.append(microsec*360/1000000-90)
# 線の一方の座標をキャンバスの中心とする
x0 = self.center_x
y0 = self.center_y
#リスト要素の最初の二つのみ
for angle, length, width, color in zip(angles[0:2], self.lengths[0:2], self.widths[0:2], self.colors[0:2]):
x1 = x0 + length * math.cos(math.radians(angle))
y1 = y0 + length * math.sin(math.radians(angle))
hand = self.canvas.create_line(
x0, y0, x1, y1,
fill=color,
width=width,
capstyle=tk.ROUND
)
# 描画した線のIDを覚えておくリスト
self.hands.append(hand)
#秒針
x2 = x0 + self.lengths[2] * math.cos(math.radians(angles[2]))
y2 = y0 + self.lengths[2] * math.sin(math.radians(angles[2]))
x_1 = x0 -0.1*(self.lengths[2] * math.cos(math.radians(angles[2])))
y_1 = y0 -0.1*(self.lengths[2] * math.sin(math.radians(angles[2])))
hand2 = self.canvas.create_line(
x_1, y_1, x0, y0, x2, y2,
fill=self.colors[2],
width=self.widths[2],
)
self.hands.append(hand2)
#マイクロセカンド針と点の座標
x3 = x0 + self.lengths[3] * math.cos(math.radians(angles[3]))
y3 = y0 + self.lengths[3] * math.sin(math.radians(angles[3]))
#マイクロセカンド針の場合
"""
hand3 = self.canvas.create_line(
x0, y0, x3, y3,
fill=self.colors[3],
width=self.widths[3],
)
"""
#マイクロセカンド点の場合
hand3 = self.canvas.create_oval(
x3-7, y3-7, x3+7, y3+7,
fill=self.colors[3],
outline="",
)
# 描画した線のIDを覚えておくリスト
self.hands.append(hand3)
#針を表現する線の位置を更新する
def updateHands(self, hour, minute, second, microsec):
angles = []
#angles.append(hour * 360 / 12 - 90)
hour_minute = (hour*360/12) +(minute*30/60)
angles.append(hour_minute - 90)
angles.append(minute * 360 / 60 - 90)
angles.append(second * 360 / 60 - 90)
#マイクロ針は、1秒で1週
angles.append(microsec*360/1000000-90)
# 線の一方の点の座標は常に時計の中心
x0 = self.center_x
y0 = self.center_y
for hand, angle, length in zip(self.hands[0:2], angles[0:2], self.lengths[0:2]):
x1 = x0 + length * math.cos(math.radians(angle))
y1 = y0 + length * math.sin(math.radians(angle))
# coordsメソッドにより描画済みの線の座標を変更する
hand = self.canvas.coords(
hand,
x0, y0, x1, y1
)
#秒針
x2 = x0 + self.lengths[2] * math.cos(math.radians(angles[2]))
y2 = y0 + self.lengths[2] * math.sin(math.radians(angles[2]))
x_1 = x0 -0.1*(self.lengths[2] * math.cos(math.radians(angles[2])))
y_1 = y0 -0.1*(self.lengths[2] * math.sin(math.radians(angles[2])))
hand2 = self.canvas.coords(
self.hands[2],
x_1, y_1, x2, y2
)
#マイクロセカンド点・針
x3 = x0 + self.lengths[3] * math.cos(math.radians(angles[3]))
y3 = y0 + self.lengths[3] * math.sin(math.radians(angles[3]))
"""
hand3 = self.canvas.coords(
self.hands[3],
x0, y0, x3, y3
)
"""
hand3 = self.canvas.coords(
self.hands[3],
x3-7, y3-7, x3+7, y3+7
)
#中心点の装飾
self.canvas.create_oval(
x0-5, y0-5, x0+5, y0+5,
fill="red",
width=2,
outline="black"
)
class AnalogClock:
def __init__(self, master):
self.master = master
self.drawer = Drawer(master)
self.draw()
def draw(self):
hour, minute, second, microsec = Timer.time()
self.drawer.drawHands(hour, minute, second, microsec)
def update(self):
hour, minute, second, microsec = Timer.time()
self.drawer.updateHands(hour, minute, second, microsec)
self.master.after(4, self.update)
#self.master.after(1000, self.update)
#self.master.after(1000 - microsec // 1000, self.update)
if __name__ == "__main__":
app = tk.Tk()
clock = AnalogClock(app)
clock.update()
app.mainloop()