【Python/tkinter】アナログ時計の作り方

アナログ時計の作り方の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Python で tkinter を利用した「アナログ時計」の作り方について解説していきたいと思います。

出来上がりは下のアニメのようになります。

アナログ時計アプリの出来上がりを示すアニメ

シンプルだね…

なんかめちゃめちゃ簡単に作れそうだけど…

シンプルなのは間違いないけど、

実際作ってみると結構学べることは多いよ!

例えば、針は線で表現するんだけど、その線の座標はどうやって求めるか分かる?

わからん!数学は苦手…

確かに数学は使うけど、考え方さえ分かれば線の座標も簡単に求めることができるよ!

で、この考え方は画像処理などいろんな分野でも使えるから覚えておいた方がいい!

わかった…

とりあえず解説聞いてみるよ!

アナログ時計の作り方

ではアナログ時計の作り方について解説していきたいと思います。

このアナログ時計を作る上でのポイントは下記の5つになると思いますので、これらについて一つ一つ解説していきたいと思います。

  • 時計はキャンバス上に作成する
  • 現在時刻を取得する
  • 針を描画する
  • 針を進める
  • after を利用して針を進める

時計はキャンバス上に作成する

これは皆さん予想されてるかと思いますが、tkinter を利用する場合、アナログ時計はキャンバス上に作成していきます。

アナログ時計を作る上では “針” を表現するための線を描画する必要がありますので、他のウィジェットでアナログ時計を作るのは難しいと思います。

キャンバスの作り方については下記ページで解説していますので、詳しく知りたい方は是非読んでみてください。

tkinterのキャンバスの作り方解説ページアイキャッチ Tkinterの使い方:キャンバスウィジェットの作り方

スポンサーリンク

現在時刻を取得する

アナログ時計では現在時刻に合わせて針が動作します。そのため、アナログ時計をプログラミングするためには、まず “現在時刻を取得” する必要があります。

その時刻の取得の仕方ですが、実は私も詳しくありません…。日本時間を取得するのが難しいんですよね…。

ということで、現在時刻の取得の仕方については検索して調べ、最終的には下記ページを参考にさせていただきました。

https://qiita.com/papi_tokei/items/43b1d15a6694f576486c

結論としては、下記のように処理を行えば、日本時間の時刻の時・分・秒を取得することができます。

時刻を取得する
# タイムゾーンの情報を作成
JST = timezone(timedelta(hours=9))

# タイムゾーンに合わせた現在時刻を取得
now = datetime.now(tz=JST)

# nowのhour,minute,secondから現在時刻の時・分・秒を取得する
return now.hour, now.minute, now.second

各処理の詳細は上記ページを参考にしていただければと思います。

いきなり 9 っていう数字が出てくるけど、これは何?
UTC(協定世界時)と JST(日本標準時)の時差だよ

プログラミングで利用する時刻を取得する関数では UTC の時刻を返却することが多いんだ

今回は JST、要は日本での時刻を取得したかったので、その時差を考慮して時刻を取得するように設定しているんだよ

針を描画する

アナログ時計をアプリで表現するためには秒針・分針・時針の3つの針を表現する必要があります。

描画する必要のある針を示した図

針は create_line で描画する

tkinterCanvas クラスにおいては線を描画するメソッドが用意されているので、線の描画により針を表現していきたいと思います。

具体的に使用するメソッドは create_line になります。

create_line
def create_line(x1, y1, x2, y2, ...., xn, yn, option, ....)

Canvas クラスのオブジェクトに create_line を実行することで、そのオブジェクトに対応するキャンバスに線を描画することができます。

create_line メソッドを含む、キャンバスへの図形の描画については下記ページで詳細をまとめていますので、詳しく知りたい方はこのページを読んだ後にでも下記ページを読んでみてください。

tkinterキャンバスに図形を描画する方法解説ページのアイキャッチ Tkinterの使い方:Canvasクラスで図形を描画する

create_line には2点の座標の指定が必要

針を表現する線を描画するときにポイントになるのが、create_line メソッドの第1引数 〜 第4引数に渡す値です。以降では、この第1引数 〜 第4引数を x1, y1, x2, y2 として表したいと思います。

create_line メソッド実行時には、座標 (x1, y1) の点と座標 (x2, y2) の点を結ぶ直線が描画されることになります。

create_lineメソッドで線を描画する様子を示した図

したがって、針の位置に対応した座標 (x1, y1) と座標 (x2, y2) を求めさえすれば、あとは create_line メソッドによる線の描画でアナログ時計の針を表現することができます。

MEMO

今回は針を描画するために2点の座標のみを create_line メソッドに指定しますが、

3点以上の座標を create_line メソッドに指定することで、折線を描画するようなことも可能です。

座標 (x1, y1) の求め方

一方の点の座標、例えばその座標を (x1, y1) とすれば、(x1, y1) に関しては時計の中心座標を指定してやれば良いです。

x1とy1の求め方を示す図

例えば上の図のように時計をキャンバスの中心に作成するとし、さらにキャンバスの幅を CANVAS_WIDTH、キャンバスの高さを CANVAS_HEIGHT とすれば、x1y1 は下記のように計算することができますね!

x1,y1を求める
x1 = CANVAS_WIDTH / 2
y1 = CANVAS_HEIGHT / 2

ちなみに、キャンバスにおける原点 (0, 0) の座標は、上の図のようにキャンバスの左上端になります。

座標 (x2, y2) の求め方

ちょっと求めるのが難しいのが、他方の点の座標である (x2, y2) の方です。ただ、こちらの座標も三角関数を利用すれば割と簡単に求めることができます。

三角関数…

そんなに拒絶反応示さなくても大丈夫!

Python だと三角関数を実行する関数が用意されているから、

最終的にはその関数を実行すれば良いだけだよ!

今回描画したい線は、下の図のような、座標 (x1, y1) と座標 (x2, y2) の2点を結ぶ線です。

rとθを用いた線の表現

上の図において、rθdxdy というパラメータが登場していますが、これらは下記の意味を持つパラメータになります。

  • r:2点間の距離
  • θ:描画したい線と x 軸とのなす角
  • dxx2x1 の差
  • dyy2y1 の差

ここで三角関数の登場です。上の図において、sin θcos θ の値を求めることを考えると、これらの値は下記のようにして算出することができます。

sinθとcosθの算出
sinθ = dy / r
cosθ = dx / r

ということは、dx と dy は上式の式変形により下記で求めることができることになります。

dxとdyの算出
dy = r * sinθ
dx = r * cosθ

さらに、 dx = x2 - x1 であることと、dy = y2 - y1 であることを考慮すれば、x2 と y2 は下記の式で求めることができると言えます。

x2とy2の算出
x2 = x1 + r * cosθ
y2 = y1 +  r * sinθ

sincos は Python の標準モジュールの math に関数が用意されていますので、それを実行すれば sincos の値を求めることは可能です。

さらに、x1 と y1 は既に座標 (x1, y1) の求め方で求めていますので、要は rθ さえ分かれば x2 と y2 を求めることができそうですね!

じゃあ r は何かというと、今回 create_line メソッドに座標 (x1, y1) と座標 (x2, y2) を指定して描画しようとしているのは時計の針(時針・分針・秒針)なので、要は r は針の長さであると考えられます。

ですので、r は描画したい針の長さを設定してやれば良いです(単位はピクセルで設定する)。

好きな長さで良いの?

基本的には好きな長さを設定すればいいよ!

だけど、キャンバスや時計からはみ出るとデザイン的にイマイチだから、

その点は注意したほうが良いと思うよ!

あとは、各針の長さを “時針 < 分針 < 秒針” となるように設定した方が、アナログ時計っぽさは出るかな!

その一方で、θ は何かというと、これは針の傾きになります。で、アナログ時計の場合、この針の傾きは、秒針であれば “現在時刻の秒”、分針であれば “現在時刻の分”、時針であれば “現在時刻の時” によって変化しますので、これらの時刻の情報によって θ を求めることができます。

例えば秒針の傾きであれば、秒針は 60 秒で針が 360 度回転しますので、1 秒間では 360 / 60 度回転することになります。したがって、秒針を描画するときの θ は下記のように求めることができます。

θを求める
# secondは現在時刻の秒
θ = second * 360 / 60 - 90

- 90 しているのは、0 秒の時の角度が -90 度になるようにするためです。

分針の場合は 1 分間で 360 / 60 度、時針の場合は 1 時間で 360 / 12 度回転することを考慮すれば、秒針と同様にして針を描画するときの θ を求めることができます。

MEMO

上の式で、角度 θ を “時計回り” の方向を正方向として求めているところに違和感を覚えた方もいらっしゃると思います

数学でよく用いる座標では角度の正方向は “反時計回り” の方向になりますが、キャンバスで用いる座標では角度の正方向は “時計回り” の方向になります

ですので、上記のようにキャンバスに描画する際に用いる角度 θ は、角度の正方向を “時計回り” の方向として求めることができます

角度の正方向が逆方向になるのは、数学でよく用いる座標とキャンバスで用いる座標とでは、y 軸方向の正方向が逆になるためです

ここまでの内容をまとめると、まず x2y2 は下記の式で求めることができます。

x2とy2の算出
x2 = x1 + r * cos θ
y2 = y1 +  r * sin θ

さらに、上式における rθ は下記を示す値になります。

  • r:描画する針の長さ
  • θ:描画する針の傾き(現在時刻の秒・分・時から算出)

上記により x2y2 を求めることができますので、後は座標 (x1, y1) の求め方で求めた x1 と y1 とともに create_line メソッドの引数に指定すれば、任意の時刻の針を描画することができます。

ただし、math モジュールの sincos 関数に指定する値の単位は “度” ではなく “ラジアン” である点に注意してください。

私たちが日常的に用いる角度の単位は “度” ですが、math モジュールの sincos 関数に指定する値の単位はラジアンです。ですので、sincos 関数を実行する際には、事前に角度をラジアン単位のものに変換する必要があります。

この変換は、math モジュールの radians 関数を利用する or 自身で角度に math.pi180 を掛けて算出することで実行することができます(math.pimath モジュールで定義された円周率)。

ラジアンに変換してからsinとcosを実行
angle = second * 360 / 60 - 90

# angleの単位は度なので、ラジアンに変換してから実行
x2 = x1 + r * cos(math.radians(angle))
y2 = x2 + r * sin(angle * math.pi / 180)

うーん、式は難しそうだけど、

結局は時刻から針の傾きさえ求めれば、

あとは足し算・掛け算・関数の実行をすれば良いだけだしなんとかなるかな…

そうそう!

ちなみに図形を描画したり点の座標を求めたりするときに

この r * cosθ r * sinθ  の計算はよく利用するから、

この式や考え方は覚えておくといいと思うよ!

針を進める

上記の解説に基づいてプログラミングすることで、針を表現する線を描画することはできると思います。

ただ、針は一度描画するだけではダメで、実際のアナログ時計のように時刻に合わせて針を進める必要があります。

ここでは、この時刻に合わせて針を進める際のポイントについて解説していきます。

ん?

単に角度と座標計算し直して、

また線を描画すればいいだけじゃないの?

それだと前に描画した線が残っちゃうよね

時計が針だらけになっちゃうよ…

たしかに…!

座標等の考え方は針を描画するで解説した通りなのですが、時刻が進むたびにそのまま線を描画してしまうと前に書いた線がキャンバス上に残ってしまいます。

さらにこれを繰り返すと時計が針だらけになって今が何時なのか分からなくってしまいます。

これに対する対策としては下記の2つがありますので、ここからはこの2つの対策それぞれについて解説していきたいと思います。

  • 描画した線の座標だけを変更する
  • 前に描画した線を削除してから線を描画する

ちなみにこれらの対策においては Canvas クラスの coords メソッドと delete メソッドを利用します。

こういった描画済みの図形を操作するメソッドについては下記ページで解説していますので、他のメソッドも知りたい方は、このページを読み終わった後にでも是非読んでみてください。

tkinterキャンバスの図形の操作方法解説ページのアイキャッチ Tkinterの使い方:Canvasクラスで描画した図形を操作する

描画した線の座標だけを変更する

tkinter の Canvas クラスには、”描画済みの図形の座標だけを変更する” メソッド coords が用意されています。

coords
coords(self, tagOrId, x1, y1, x2, y2)

この coords メソッドを実行することで、引数 tagOrId で指定した線の座標を引数で指定した座標に変更することが可能です。

coordsメソッドの効果を示す図

coords メソッドを実行するのは、座標を変更したい図形が描画されているキャンバスのオブジェクトです。

引数 tagOrId には図形の ID もしくはタグ名を指定します。

create_line 等の図形を描画するメソッドでは、戻り値がその描画した図形の ID となります。また、create_line 等の図形を描画するメソッドでは、tagオプションを指定することで描画する図形にタグ名(名前みたいなもの)を付けることができます。

ですので、coords メソッドの引数 tagOrId には、create_line の戻り値もしくは tagオプションに指定したタグ名のどちらかを指定することになります。

これにより、create_line により描画した線の座標を変更することができます。

例えば上の図では、ID or タグ名が hand である線の座標を (x1, y1) と (x2, y2) に変更する様子を示しています(変更前の座標は  (xo1, yo1) と (xo2, yo2) )。ちなみに hand は秒針などの “針” を英語で表したものになります(手じゃないよ)。

この coords メソッドを利用すれば、針を進める処理は下記のようにして実現することができます。

まず、事前に針を描画するで解説した通りに create_line メソッドを実行して線を描画しておきます。この線の描画を行う際、create_line メソッドの戻り値を変数等に格納して覚えておきます(タグを覚えておいても良いですが、今回は戻り値を利用する前提で説明します)。

針を描画する
# キャンバスはCanvasのオブジェクト
hand = canvas.create_line(
	# 引数は略
)

さらに、針を進める際には針の描画(線の描画)は行わず、描画した線の座標のみを変更します。

具体的には、まず再度時刻を取得し、その時刻から座標 (x1, y1) と座標 (x2, y2) を計算します。ただし、一方の座標(座標 (x1, y1) )は毎回時計の中心の座標になるので、その座標を覚えておけば毎回計算し直す必要はありません。

そして、これらの座標 x1y1x2y2 と最初に針を描画した時に覚えておいた “create_line の戻り値” を引数に指定して coords メソッドを実行します。

針を進める
# キャンバスはCanvasのオブジェクト(handの描画先のオブジェクト)
canvas.coords(hand, x1, x2, y1, y2)

以上により、取得したタイミングの時刻に合わせて針の座標が変更され、”針を進める” を実現することができます。

MEMO

ここでは coords メソッドについて create_line で描画した “線” のみに対して説明しました

しかし、楕円や長方形など、キャンバスに描画した図形であればどの図形であっても、coords メソッドで座標を変更することが可能です

前に描画した線を削除してから線を描画する

tkinter の Canvas クラスには、”描画済みの図形を削除する” メソッド delete が用意されています。

delete
delete(self, tagOrId)

この delete メソッドを実行することで、引数 tagOrId で指定した図形を削除することが可能です。

deleteメソッドの効果を示す図

引数 tagOrId の意味は描画した線の座標だけを変更するで説明したものと同じものになります。すなわち、create_line の戻り値、もしくは create_line 実行時に tag オプションに指定したタグ名、のどちらかを指定します。

この delete メソッドにより、以前に描画した線をキャンバス上から削除することができます。ですので、後は時刻を取得し、さらに針を描画するで解説した通りに再度線を描画すれば、針が進んだように見せることができます。

スポンサーリンク

after を利用して針を進める

針を進めるで解説した通りに処理を実行すれば、その実行した時点の時刻に合わせて針を進めることができます。

なので、あとはこの “針を進める” を定期的に(例えば秒針であれば1秒ごとに)実行すれば、アナログ時計のように時刻に合わせて針がどんどん進むような見た目を表現することができます。

ただし、tkinter で定期的に処理を行う際には、after メソッドを利用する必要があることに注意してください。

例えば、下記のように無限ループの中で sleep を行いながら “針を進める” 処理を行うようなこともできますが、これだと処理が mainloop に戻らないので針を進めたとしてもキャンバスにその結果が反映されません(キャンバスに描画した図形が実際に画面に反映されるのは mainloop に処理が戻った時になります)。

whileでのループ
while True:

	針を進める処理を実行

	# 1秒間スリープ(事前にtimeをimportする必要あり
	time.sleep(1)

after メソッドを利用することで、mainloop に処理を戻しつつ、定期的な処理も実行させることが可能になります。基本的に tkinter でのプログラミングを行う際には、定期的な処理を行う時は after メソッドを利用するのが良いです。

afterでのループ
def update(self):

	針を進める処理を実行

	# 1秒後に再度この関数を呼び出す(masterはアプリのメインウィンドウ)
	self.master.after(1000, self.update)

ここでの解説で登場した mainloopafter は tkinter でのアプリ開発において非常に重要なものになります。下記で詳細に解説していますので、まだご存知ない方・理解が浅い方はぜひ読んでみてください。これらを知っているだけで作成できるアプリの幅が広がりますし、アプリの不具合の多くを防ぐことができるようになります。

tkinterのmainloopの解説ページのアイキャッチ 【Python】tkinterのmainloopについて解説 Tkinterの使い方:after で処理を「遅らせて」or 処理を「定期的」に実行する

また、tkinter を利用したプログラミングにおいても、マルチスレッドを利用した場合は after を利用しなくても while ループにより定期的な処理を実現することが可能です。

このマルチスレッドについては下記ページで解説していますので是非参考にしてください。マルチスレッドはプログラムを同時に平行で実行するために用いるもので、使いこなせると tkinter 利用時以外でも様々な場で役に立つと思います。

tkinterでのマルチスレッドの使い方解説ページのアイキャッチ 【Python/tkinter】tkinterでマルチスレッドを利用する

アナログ時計のサンプルプスクリプト

最後に、ここまで解説してきた内容に基づいて作成したアナログ時計のサンプルスクリプトを紹介したいと思います。

スクリプト

アナログ時計のサンプルスクリプトは下記のようになります。

アナログ時計
# -*- coding: utf-8 -*-
import tkinter
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.6
LENGTH_MINUTE_HAND = CANVAS_SIZE / 2 * 0.7
LENGTH_SECOND_HAND = CANVAS_SIZE / 2 * 0.8

# 針の色の設定
COLOR_HOUR_HAND = "red"
COLOR_MINUTE_HAND = "blue"
COLOR_SECOND_HAND = "green"

# 針の太さの設定
WIDTH_HOUR_HAND = 6
WIDTH_MINUTE_HAND = 4
WIDTH_SECOND_HAND = 2

# 時計の前面と背景の色の設定
BG_COLOR = "white"
FG_COLOR = "gray"

# 時計の盤面を表す円の半径の設定
CLOCK_OVAL_RADIUS = CANVAS_SIZE / 2

# 時計の数字の位置の設定(中心からの距離)
DISTANCE_NUMBER = CANVAS_SIZE / 2 * 0.9


class Timer:
	'''時刻を取得するクラス'''

	def __init__(self):

		# タイムゾーンの設定
		self.JST = timezone(timedelta(hours=9))

	def time(self):

		# 時刻の取得
		now = datetime.now(tz=self.JST)

		# 時・分・秒にわけて返却
		return now.hour, now.minute, now.second


class Drawer:
	'''時計を描画するクラス'''

	def __init__(self, master):

		# 各種設定を行なった後に時計の盤面を描画
		self.initSetting(master)
		self.createClock()

	def initSetting(self, master):
		'''時計描画に必要な設定を行う'''

		# ウィジェットの作成先を設定
		self.master = master

		# 描画した針のオブジェクトを覚えておくリストを用意
		self.hands = []

		# 針の色のリストを用意
		self.colors = [
			COLOR_HOUR_HAND, COLOR_MINUTE_HAND, COLOR_SECOND_HAND
		]

		# 針の太さのリストを用意
		self.widths = [
			WIDTH_HOUR_HAND, WIDTH_MINUTE_HAND, WIDTH_SECOND_HAND
		]

		# 針の長さのリストを用意
		self.lengths = [
			LENGTH_HOUR_HAND, LENGTH_MINUTE_HAND, LENGTH_SECOND_HAND
		]

		# キャンバスの中心座標を覚えておく
		self.center_x = CANVAS_WIDTH / 2
		self.center_y = CANVAS_HEIGHT / 2

	def createClock(self):
		'''時計の盤面を作成する'''

		# キャンバスを作成して配置する
		self.canvas = tkinter.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=("", 20),
				fill=FG_COLOR,
				text=str(hour)
			)

	def drawHands(self, hour, minute, second):
		'''針を表現する線を描画する'''

		# 各線の傾きの角度を計算指定リストに追加
		angles = []
		angles.append(hour * 360 / 12 - 90)
		angles.append(minute * 360 / 60 - 90)
		angles.append(second * 360 / 60 - 90)

		# 線の一方の座標をキャンバスの中心とする
		x1 = self.center_x
		y1 = self.center_y

		# initSettingで作成したリストから情報を取得しながら線を描画
		for angle, length, width, color in zip(angles, self.lengths, self.widths, self.colors):

			# 線の他方の座標を計算
			x2 = x1 + length * math.cos(math.radians(angle))
			y2 = y1 + length * math.sin(math.radians(angle))

			hand = self.canvas.create_line(
				x1, y1, x2, y2,
				fill=color,
				width=width
			)

			# 描画した線のIDを覚えておく
			self.hands.append(hand)

	def updateHands(self, hour, minute, second):
		'''針を表現する線の位置を更新する'''

		angles = []
		angles.append(hour * 360 / 12 - 90)
		angles.append(minute * 360 / 60 - 90)
		angles.append(second * 360 / 60 - 90)

		# 線の一方の点の座標は常に時計の中心
		x1 = self.center_x
		y1 = self.center_y

		# handは描画した線のID
		for hand, angle, length in zip(self.hands, angles, self.lengths):

			# 線の他方の点の座標は毎回時刻に合わせて計算する
			x2 = x1 + length * math.cos(math.radians(angle))
			y2 = y1 + length * math.sin(math.radians(angle))

			# coordsメソッドにより描画済みの線の座標を変更する
			hand = self.canvas.coords(
				hand,
				x1, y1, x2, y2
			)


class AnalogClock:
	'''アナログ時計を実現するクラス'''

	def __init__(self, master):

		# after実行用にウィジェットのインスタンスを保持
		self.master = master

		# 各種クラスのオブジェクトを生成
		self.timer = Timer()
		self.drawer = Drawer(master)

		# 針を描画
		self.draw()

		# 1秒後に針を進めるループを開始
		self.master.after(1000, self.update)

	def draw(self):
		'''時計の針を描画する'''

		# 時刻を取得し、その時刻に合わせて針を描画する
		hour, minute, second = self.timer.time()
		self.drawer.drawHands(hour, minute, second)

	def update(self):
		'''時計の針を進める'''

		# 時刻を取得し、その時刻に合わせて針を進める
		hour, minute, second = self.timer.time()
		self.drawer.updateHands(hour, minute, second)

		# 1秒後に再度時計の針を進める
		self.master.after(1000, self.update)


if __name__ == "__main__":
	app = tkinter.Tk()
	AnalogClock(app)
	app.mainloop()

スポンサーリンク

スクリプト実行結果

スクリプトを実行すると下の画面のようなアプリが起動します。

スクリプト実行によって起動するアプリの画面

緑色の線が秒針、青色の線が分針、赤色の線が時針をそれぞれ表しています。スクリプトを実行した時間によって針の位置が変化していることが確認できると思います。

また、アプリ起動後に放置しておくことで、1秒ごとに秒針が進み、秒針が12の位置に到達するごとに分針が進むことも確認できると思います(分針が12の位置に到達すれば時針も進みますが、最悪1時間近く待つ必要があるので確認するのは大変かもしれません…)。

スクリプトの設定

スクリプトの先頭部分で大文字の変数に値を格納している部分を変更することで、スクリプトの設定を行うことができます。

例えば針の色や太さ、キャンバスのサイズなどなどが設定可能です。

スクリプトの解説

最後に簡単にスクリプトの解説を行なっています。

まずこのスクリプトでは下記の3つのクラスを用意しています。

  • Timer:時間を取得するクラス
  • Drawer:図形を描画するクラス
  • AnalogClock:アナログ時計を実現するクラス

Timer

Timer クラスでは、現在時刻を取得するで解説した方法に基づいて日本時間の現在時刻の取得を行なっています。

Drawer

Drawer クラスの drawHands では針を描画するで解説した方法に基づいて針を描画する処理を行い、updateHands では針を進めるで解説した方法に基づいて針を進める処理を行なっています(針は coords メソッドにより進めています)。

drawHands でも updateHands でも、ループにより時針・分針・秒針に対して一気に処理を行なっているので若干複雑に見えるかもしれませんが、ループの中身を見れば解説した内容に基づいて処理していることが確認できると思います。

また針を進めるで解説した通り、updateHands で実行している coords メソッドでは、線描画時に実行した create_line の戻り値を引数に指定する必要があります。

そのため、drawHands の中で create_line を実行した後にその戻り値を self.hands というリストに格納するるようにしています。そして、updateHandscoords メソッドを実行する際には、そのリストを参照して引数に指定するようにしています。

他にも self.lengthsself.colorsself.widths というリストを使用していますが、これらは __init__ から実行される initSetting の中で作成されるリストで、各リストの要素には時針・分針・秒針それぞれの描画時の設定値(線の長さ・線の色・線の太さ)が格納されています。これらはスクリプトの設定で説明したように、スクリプトの先頭部分で変更可能です。

また createClock では、アナログ時計の描画先となるキャンバスの作成や、時計の盤面となる円の描画、時計の盤面への数字の描画を行なっています。

各数字の描画位置は、針を描画する座標 (x2, y2) の求め方で説明したのと同じ方法で求めています。

AnalogClock

AnalogClock クラスでは、前述の Timer クラスと Drawer クラスのオブジェクトの作成と、そのオブジェクトへの処理の依頼(時刻の取得の依頼、針の描画の依頼、針を進める依頼)を行なっています。

特に “針を進める依頼” となる updateHands の実行は、after を利用して針を進めるで説明したように after メソッドを用いたループの中で定期的に実行するようにしています(1000 ms ごとに実行)。

スポンサーリンク

まとめ

このページでは、Python で tkinter を利用した「アナログ時計」の作り方を解説しました!

見た目はシンプルですが、針の描画や針を進める際にいろいろな工夫が必要であることを感じ取っていただけたのではないかと思います。

特に、座標 (x2, y2) の求め方で解説した座標の求める際の考え方、つまり三角関数を利用して座標を求める考え方はかなり多くの場面で活用できるものなので、是非この機会に覚えておいていただければと思います!例えば自力で画像を回転する場合もこの考え方を利用すれば簡単にプログラミングすることができます。

また、針を進めるで紹介した coords メソッドや delete メソッドなど、描画済みの図形を後から操作するメソッドを活用することで、キャンバス上の図形に “動き” を持たせるようなことも可能です。前述でも紹介しましたが、これらのメソッドについては下記ページで紹介していますので、興味のある方は是非読んでみてください!

tkinterキャンバスの図形の操作方法解説ページのアイキャッチ Tkinterの使い方:Canvasクラスで描画した図形を操作する

同じカテゴリのページ一覧を表示