今回は Python で tkinter を用いた「デジタル時計」の作り方について解説していきたいと思います。
出来上がりは下のアニメのようになります。
ポイントは、単に時間を文字列として表示するわけではなくて、よく見かける実際のデジタル時計のように、数字を7セグ風に表示するところです。
もしかしたら文字を7セグ風に表示してくれるフォントがあるかもしれませんが、今回は実際に7セグをキャンバス上に描画し、時刻に合わせて各セグの色を変更させることで、このデジタル時計を作成していきたいと思います。
ちなみに、時刻の取得方法や時刻の表示の定期的な更新を after
メソッドを利用して行う点などはアナログ時計と同じです。ですので、この辺りについてはこのページでは説明をせず、”7セグで時刻を表示する” ところに焦点を当てて、デジタル時計の作り方について解説していきたいと思います。
時刻の取得方法や時刻の表示の定期的な更新方法については、下記ページの「アナログ時計」の作り方の解説の中で説明していますので、これらについて知りたい方は下記ページを読んでいただければと思います。
【Python/tkinter】アナログ時計の作り方では早速、デジタル時計の作り方について解説していきたいと思います。
Contents
キャンバスを作成する
まずは、時刻をセグ表示するためのキャンバスを作成していきます。
今回は8つのキャンバスを作成したいと思います。
つまり、時刻の各数字をセグ表示する6つのキャンバスと、時刻の時と分、分と秒を区切るコロン(:)を表示する2つのキャンバスを作成します。
キャンバスを作成するためには、tkinter.Canvas()
を実行します。このキャンバスの作り方の詳細については下記ページで解説していますので、詳しく知りたい方はこちらを参照していただければと思います。
これらのキャンバスは、デジタル時計のように見えるように、”横方向” に並べて配置します。
今回は、横方向にキャンバスを並べるために、pack
メソッドを使って配置していきたいと思います。pack
メソッドは上から下に向かってウィジェットを配置するイメージが強いと思いますが、pack
メソッド実行時に side=tkinter.LEFT
オプションを指定してやれば、左から右に向かってウィジェットを配置することができます。
# canvasはtkinter.Canvas()の戻り値
canvas.pack(side=tkinter.LEFT)
ただし、pack
メソッドを実行したウィジェットから順に左から配置されていく点に注意が必要です。
また、数字を表すキャンバスでは、時刻の一桁分の数字に対するセグ表示のみを行います。
例えば時刻が 12:34:56
である場合、各キャンバスでは左から順に 1
、2
、3
、4
、5
、6
の数字をセグ表示します(コロンを表示するキャンバスは飛ばす)。
キャンバス上にセグを描画する
続いて、数字をセグ表示するキャンバスそれぞれに対してセグを描画していきます。
セグは下の図のような “六角形” を描画することで表現することができます。
六角形を描画する際には、tkinter の Canvas
クラスの create_polygon
を利用します。
def create_polygon(x1, y1, x2, y2, .... , xn, yn, option, ....)
この create_polygon
を実行することで、引数に指定した座標 (x1
, y1
) 〜 座標 (xn
, yn
) を頂点とする多角形を描画することができます。
今回は “六角形” を描画するので、create_polygon
には座標 (x1
, y1
) 〜 座標 (x6
, y6
) の6つの頂点の座標を指定して実行することになります。
で、この create_polygon
を1回実行することで描画できるのは六角形1つなので、7つのセグを描画するためには、描画する位置に合わせて指定する座標を変更しながら7回 create_polygon
を実行する必要があります。
さらに、数字を表すキャンバスが6つあるので、それぞれのキャンバスに対して上記を繰り返す必要があります(つまり合計42回 create_polygon
を実行する)。
じゃあ具体的に create_polygon
に指定する座標の値はどう計算すれば良いのかというと、これについては複雑なので、作り方の解説の最後のセグの座標を計算するで別途説明させていただきたいと思います。
また、create_polygon
実行時にオプションを指定することにより、描画する多角形の見た目の変更等を行うことが可能です。下記ページでこれらのオプションについても解説していますので、興味のある方はぜひ読んでみてください(他の図形を描画する方法と合わせて解説しています)。
スポンサーリンク
時刻に合わせてセグを点灯させる
セグが描画できれば時刻をセグ表示する準備が整ったことになります。
あとは、取得した時刻に合わせて描画したセグを点灯 or 消灯させれば良いです。
セグの点灯と消灯
各セグの点灯と消灯は、描画したセグ(つまり六角形)の塗りつぶし色を変更することで実現することができます。
tkinter の Canvas
クラスには itemconfig
メソッドが用意されており、オプションをキーワード指定してこのメソッドを実行することで、描画済みの図形の各種設定を変更することが可能です。
# canvasはtkinter.Canvas()の戻り値
canvas.itemconfig(tagOrId, option, ...)
図形描画時に実行するメソッド(例えば create_polygon
や create_oval
など)では、引数にオプションを指定することで描画する図形の設定(線の太さや塗りつぶし色など)を行うことができます。
上記の itemconfig
は、すでに描画した図形の設定を後から変更するためのメソッドになります。
itemconfig
メソッドを実行するのは、設定を変更したい図形を描画しているキャンバスのオブジェクトです。要は、tkinter.Canvas()
の戻り値です。
itemconfig
メソッドの第1引数に指定するのは図形の ID or タグ名になります。例えば描画した多角形の ID を指定する場合は、create_polygon
の戻り値を指定することになります。
さらに、以降の引数で変更したいオプションをキーワード指定します。図形の塗りつぶし色を変更するためには、itemconfig
メソッドに fill
オプションを指定して実行します。
例えば下記のように itemconfig
メソッドを実行すれば、canvas
に描画された図形 seg
の塗りつぶし色を "orange"
に変更することが可能です。
canvas.itemconfig(seg, fill="orange")
ですので、点灯させる位置のセグの色と消灯させる位置のセグの色を別のものにし、上記のように itemconfig
を実行して塗りつぶし色を変更することで、各セグの点灯・消灯を表現することが可能です。
ただし、itemconfig
を実行するためには、セグの描画先のキャンバスのオブジェクトと、さらにそのキャンバスに描画したセグの ID or タグ名が必要になります。
ですので、キャンバス作成時やセグの描画時にこれらの情報を覚えておくようにしましょう(複数の情報を覚えておく必要があるので、リストなどで覚えておくと良いと思います)。
今回紹介した itemconfig
のように、描画済みの図形を操作するようなメソッドは他にもたくさんあります。下記ページで各メソッドの紹介をしていますので、興味のある方はぜひ読んでみてください。
数字に応じた点灯と消灯の切り替え
7セグで数字を表示するためには、数字に応じて各位置のセグの点灯 or 消灯を上手く切り替えてやる必要があります。
これを行うためには、0
〜 9
の各数字をセグ表示する際に、各セグを点灯 or 消灯のどちらに切り替えるかのリストを用意しておくのが良いと思います。
ここでまず、以降の説明しやすくするために、7セグの各セグに下図のようにセグ 0
〜 セグ 6
の名前を割り振りたいと思います。
この時、例えば数字の 3
を7セグで表示する場合、下記のように各セグを点灯・消灯させることになります。
- セグ
0
:点灯 - セグ
1
:消灯 - セグ
2
:点灯 - セグ
3
:点灯 - セグ
4
:消灯 - セグ
5
:点灯 - セグ
6
:点灯
上記の点灯・消灯に基づいて各セグの色を切り替えた場合、下図のようになります(点灯はオレンジ、消灯は灰色で色を切り替えています)。
数字の 3
のように見えますね!
つまり、上記のような点灯 or 消灯の情報をリストとして保持しておけば、後はそのリストを参照しながら itemconfig
メソッドで図形に塗る色を切り替えることで、数字に合わせたセグの点灯・消灯を行うことができます。
例えば下記は、数字 3
をセグ表示する時に参照するリストの一例になります。
# 上, 上左, 上右, 中, 下左, 下右, 下
on_off_info = [True, False, True, True, False, True, True]
各要素は先頭から順にそれぞれセグ 0
からセグ 6
に対応しており、セグを点灯する場合は True
を、消灯する場合は False
を示すようになっています。
例えばリスト segs
にセグ 0
からセグ 6
を表す六角形の ID or タグ名が格納されているとすれば、数字の 3
は、先ほど示した on_off_info
を用いて下記のような for
ループでセグ表示することができます(canvas
は塗りつぶし色を変更したいセグが描画されているキャンバスオブジェクトです)。
for seg, is_on in zip(segs, on_off_info):
if is_on:
color = "orange"
else:
color = "gray"
# セグを表現する多角形の色を変更
canvas.itemconfig(seg, fill=color)
また、上記のリストは各数字に対して用意しておく必要があるので、デジタル時計を起動する前に、下記のような数字 0
〜 9
に対して各セグを点灯するか消灯するかの情報をまとめた二次元リストを用意しておくのが良いと思います。
ON_OFF_INFOS = [
# 上, 上左, 上右, 中, 下左, 下右, 下
[True, True, True, False, True, True, True], # 0
[False, False, True, False, False, True, False], # 1
[True, False, True, True, True, False, True], # 2
[True, False, True, True, False, True, True], # 3
[False, True, True, True, False, True, False], # 4
[True, True, False, True, False, True, True], # 5
[True, True, False, True, True, True, True], # 6
[True, True, True, False, False, True, False], # 7
[True, True, True, True, True, True, True], # 8
[True, True, True, True, False, True, True] # 9
キャンバスを作成するで説明したように、キャンバスには各数字を1桁ずつ独立してセグ表示するようにしているため、用意する情報は0
〜 9
の1桁の数字に対するものだけで良いです。
あとは上記の二次元リストからセグ表示したい数字に対応したリストを取得し(数字 num
をセグ表示するのであれば ON_OFF_INFOS[num]
で取得)、さらに、そのリストの True
or False
に応じて塗りつぶす色を切り替えれば、各数字に対応したセグ表示を行うことができます。
このスクリプト例は下記のようになります(canvas
は塗りつぶし色を変更したいセグが描画されているキャンバスオブジェクトです)。
for seg, is_on in zip(segs, ON_OFF_INFOS[num]):
if is_on:
color = "orange"
else:
color = "gray"
# セグを表現する多角形の色を変更
canvas.itemconfig(seg, fill=color)
スポンサーリンク
セグの座標を計算する
以上の説明に基づいてプログラミングすることで、セグさえ描画すれば数字をセグ表示できるようになると思います。
ただ、まだセグ描画時(create_polygon
実行時)に必要になる座標の計算の仕方を説明していなかったので、ここでその仕方を説明したいと思います。
割とゴリ押しで座標を求めていますので、もっと綺麗に座標を決める方法をご存知の方がおられましたら、Twitter で教えていただけると幸いです。
座標の決め方の方針
今回は、基準となるセグ(以降は基準セグと呼びます)の6つの頂点の座標を決め、その基準セグの回転および移動により7つのセグを描画することを考えたいと思います。
これは、各セグの頂点の座標を個別に決めるのが面倒だからです。
上記の方針でセグの頂点の座標を決めるのであれば、基準セグの頂点の座標さえ決まれば、各セグの頂点の座標は、その基準セグの座標の回転(90度回転)と足し算(移動)を行うだけで求めることができます(多分こっちの方が楽だと思います)。
また、セグ 3
を除く各セグは、キャンバスに接するように配置することを前提にして座標を決めていきたいと思います。さらに、各セグ同士も接するように配置することを前提にして座標を決めていきます。つまり、ここで決めた座標に基づいて各セグを描画すれば下の図のように配置されることになります。
さらにもう一つ、セグの鋭角(下の図の矢印で示す位置の角)の角度は90度であることを前提としたいと思います。
これらの前提があると、基準セグの頂点の座標や、各セグを描画するときの基準セグからの移動量を比較的簡単に求めることができるようになります。
基準セグの頂点の座標
では、まずは基準セグの頂点の座標を求めていきたいと思います。
基準セグの定義
この基準セグは、セグ 3
を “セグの中心がキャンバスの原点 (0
, 0
) になるように移動したセグ” であるとして考えていきたいと思います(キャンバスでは y
方向の正方向が下であることと、原点は左上であることに点に注意してください)。
わざわざセグの中心が原点 (0
, 0
) に移動させる理由は、セグの中心を (0
, 0
) にしておくことで回転後のセグの中心も (0
, 0
) になる&移動する量が計算しやすいからです。
セグ 3
はキャンバスの中心に存在しますので、セグ 3
の中心座標は (キャンバスの幅 / 2
, キャンバスの高さ / 2
) となります。
また、基準セグは “セグ 3
をセグの中心がキャンバスの原点 (0
, 0
) になるように移動したセグ” ですので、まずセグ 3
の各頂点の座標を求め、各座標からキャンバスの中心座標を引いてやる、つまり各座標の x
座標から キャンバスの幅 / 2
を、y
座標から キャンバスの高さ / 2
をそれぞれ引くことで、基準セグの座標を求めることが可能です。
ですので、基準セグの各頂点の座標を求めるために、まずはセグ 3
の各頂点の座標を求めていきたいと思います。
セグ 3
の各頂点の座標
ここからは、キャンバスの幅を canvas_width
、キャンバスの高さを canvas_height
、セグの太さを seg_width
として座標を考えていきたいと思います。
まずはセグ 3
の左中の頂点の座標について考えてみましょう。
ひとまず簡単な y
座標から考えてみましょう。前述のとおり、セグ 3
はちょうどキャンバスの中心に存在するセグになります。なので、セグ 3
の中心座標は (canvas_width / 2
, canvas_height / 2
) ということになります。
さらに、今注目している左中の頂点の y
座標は、セグ 3
の中心座標の y
座標と同じ位置にありますので、セグ 3
の左中の頂点の y
座標は canvas_height / 2
ということになります。
次は x
座標について考えてみましょう!
現在注目している左中の頂点は、キャンバスの左端から “セグの太さの半分” の長さ分右側に離れていることが確認できると思います。キャンバスの左端の x
座標は 0
であり、さらにセグの太さは seg_width
であることを考慮すれば、この頂点の x
座標は seg_width / 2
と考えることができますね!
つまり、セグ 3
の左中の頂点の座標は (seg_width / 2
, canvas_height / 2
) です。
続いて左上の頂点について考えてみます。
y
座標は左中の頂点から seg_width / 2
分だけ上方向にあるので、canvas_height / 2 - seg_width / 2
と求めることができます。x
座標はどうでしょう?
座標の決め方の方針で説明したように、セグの鋭角の角度は90度ですので、下の図の青色部分の三角形は二等辺三角形であることが分かります。
この青色の三角形の縦線の長さは、セグの太さの半分なので seg_width / 2
ということになります。さらに、二等辺三角形ですので、横線の長さも seg_width / 2
ということになります。
したがって、左上の頂点の x
座標は、左中の頂点から seg_width / 2
分だけ右側にあるので、seg_width
と求めることができます。
つまり、セグ 3
の左上の頂点の座標は (seg_width
, canvas_height / 2 - seg_width / 2
) です。
左下も同様に、左中からどの方向にどれだけ離れているかを考えることで求めることが可能ですので、求め方の解説は省略します。
続いて右中の頂点の座標について考えてみましょう。
これもまぁ左中と同様の考え方で求められそうですね!この頂点はキャンバスの右端から seg_width / 2
分だけ左側に離れているので、x
座標は canvas_width - seg_width / 2
と求めることができます。さらに左中の頂点と同様に y
座標は canvas_height / 2
になります。
右下や右上の頂点に関しても、右中の頂点からどの方向にどれだけ離れているかを考えると求めることができると思います。
上記の考え方で求めたセグ 3
の各頂点の座標は下記のようになります。
- 左上:
x
:seg_width
y
:canvas_height / 2 - seg_width / 2
- 左中:
x
:seg_width - 2
y
:canvas_height / 2
- 左下:
x
:seg_width
y
:canvas_height / 2 + seg_width / 2
- 右下:
x
:canvas_width - seg_width
y
:canvas_height / 2 + seg_width / 2
- 右中:
x
:canvas_width - seg_width / 2
y
:canvas_height / 2
- 右上:
x
:canvas_width - seg_width
y
:canvas_height / 2 - seg_width / 2
基準セグの各頂点の座標
セグ 3
の各頂点の座標が求められましたので、次はこの座標から基準セグの座標を求めていきたいと思います。
前述の通り、基準セグの各頂点の x
座標は、セグ 3
の各頂点の x
座標から canvas_width / 2
を引くことで、基準セグの各頂点の y
座標は、セグ 3
の各頂点の y
座標から canvas_height / 2
を引くことでそれぞれ求めることができます。
上記に基づいて求めた基準セグの各頂点の座標は下記のようになります。
- 左上:
x
:- canvas_width / 2 + seg_width
y
:- seg_width / 2
- 左中:
x
:- canvas_width / 2 + seg_width - 2
y
:0
- 左下:
x
:- canvas_width / 2 + seg_width
y
:seg_width / 2
- 右下:
x
:canvas_width / 2 - seg_width
y
:seg_width / 2
- 右中:
x
:canvas_width / 2 - seg_width / 2
y
:0
- 右上:
x
:canvas_width / 2 - seg_width
y
:- seg_width / 2
上記のように求めた各頂点の座標をリスト形式にすると下記のようになります(x
座標と y
座標をそれぞれ別のリストとしています)。
# 基準セグの各頂点のx座標
XS = [
- canvas_width / 2 + seg_width, # 左上
- canvas_width / 2 + seg_width / 2, # 左中
- canvas_width / 2 + seg_width, # 左下
canvas_width / 2 - seg_width, # 右下
canvas_width / 2 - seg_width / 2, # 右中
canvas_width / 2 - seg_width # 右上
]
# 基準セグの各頂点のy座標
YS = [
- seg_width / 2, # 左上
0, # 左中
seg_width / 2, # 左下
seg_width / 2, # 右下
0, # 右中
- seg_width / 2 # 右上
]
この座標のリストを利用すれば、例えば下記のように create_polygon
を実行することでセグを描画することができます。
canvas.create_polygon(
XS[0], YS[0],
XS[1], YS[1],
XS[2], YS[2],
XS[3], YS[3],
XS[4], YS[4],
XS[5], YS[5],
fill="gray",
width=0,
)
ただ、上記で描画できるセグは基準セグ(横長のセグ&中心がキャンバスの座標 (0
, 0
))なので、7セグを描画する際には、事前にこの基準セグを回転(必要な場合のみ)&移動させてから描画することになります。
スポンサーリンク
基準セグの回転
で、基準セグが横長のセグなので、縦長のセグを描画する際には回転を事前に行う必要があります。
具体的には下記のセグを描画する際には、事前に基準セグを回転させておき、それから適切な位置に移動させて描画する必要があります。
- セグ
1
- セグ
2
- セグ
4
- セグ
5
図形の回転のプログラミングと考えると具体的なイメージが湧かないかも知れませんが、要はセグの各頂点の座標を、回転後の頂点の座標に移動させると考えると良いです。
さらに、回転後の座標を (rx
, ry
) とすれば、この rx
と ry
は回転前の座標 (x
, y
) より、下記の式で求めることができます(回転角度は θ
としています)。行列に詳しい方であれば、回転行列と回転前の座標の積であると考えた方がわかりやすいかもしれません。
rx = x * cosθ - y * sinθ
ry = x * sinθ + y * cosθ
今回、回転を行う理由は、横長である基準セグを縦長のセグにするためなので、回転角度は90度になります(270度でも良い)。
上式における θ = 90度
とすれば、cosθ = 0
、sinθ = 1
なので、結局回転後の座標 (rx
, ry
) は下記により求めることができることになります。かなり簡単になりましたね!
rx = - y
ry = x
この回転後の基準セグの各頂点の座標を求める処理は、基準セグの頂点の座標で示した XS
と YS
を用いれば、例えば内包表記を用いて下記により求めることができます(NumPy など用いて求めても良いと思います)。
r_xs = [-n for n in YS]
r_ys = [n for n in XS]
上記により、基準セグが横長から縦長に変化することになります。
基準セグの移動
回転を行えば基準セグと描画するセグとの方向を合わせることができますので、あとは7セグを表現できるように位置を移動させてから描画を行えば良いです。
基準セグの中心座標は (0
, 0
) としていますので(回転しても中心座標は変わらない)、基準セグの各頂点の x
座標と y
座標それぞれに、描画先となるセグの中心の x
座標と y
座標の値を足すことで、その描画先に移動させた時の基準セグの座標を求めることができます。
ただ、中心座標は描画するセグごとに異なりますので、下図の各セグ(セグ 0
〜 セグ 6
)の中心座標がいくつになるのかについて、個別に考える必要があります。
で、この中心座標を考えるのに、基準セグの頂点の座標で用いた canvas_width
と seg_width
と canvas_height
に追加して、セグの長さの値が利用できると便利なので、それを seg_length
として考えていきたいと思います。
さらに、各セグを表す図に補助線を入れ、各線の間の距離を、canvas_width
、seg_width
、seg_length
、canvas_height
を用いて表現すれば下の図のようになります。
この上の図から、各セグの中心座標を読み取ることができると思います。
例えばセグ 2
の中心座標について考えると、x
座標に関しては原点から canvas_width - seg_width / 2
離れた位置に、y
座標に関しては原点から canvas_height / 2 - seg_length / 2
離れた位置に存在するため、セグ 2
の中心座標は (canvas_width - seg_width / 2
, canvas_height / 2 - seg_length / 2
) であると言えます。
こんな感じで考えていくと、各セグの中心座標はそれぞれ下記のようになります。
- セグ
0
:(canvas_width / 2
,seg_width / 2
) - セグ
1
:(seg_width / 2
,canvas_height / 2 - seg_length / 2
) - セグ
2
:(canvas_width - seg_width / 2
,canvas_height / 2 - seg_length / 2
) - セグ
3
:(canvas_width / 2
,canvas_height / 2
) - セグ
4
:(seg_width / 2
,canvas_height / 2 + seg_length / 2
) - セグ
5
:(canvas_width - seg_width / 2
,canvas_height / 2 + seg_length / 2
) - セグ
6
:(canvas_width / 2
,canvas_height - seg_width / 2
)
各セグを描画する際には、上記の情報を参照しながら基準セグのそれぞれの頂点を移動してからセグを描画することになります。
そのため、これらの情報は描画時に参照しやすいよう、下記のようにリスト形式にしておくと良いと思います(各セグを回転するかどうかの情報をリストの先頭に保持するようにしています)。
SEG_DRAW_PARAMS = [
# [回転するか?, 横方向の移動量, 縦方向の移動量]
[False, canvas_width / 2, seg_width / 2],
[True, seg_width / 2, canvas_height / 2 - seg_length / 2],
[True, canvas_width - seg_width / 2, canvas_height / 2 - seg_length / 2],
[False, canvas_width / 2, canvas_height / 2],
[True, seg_width / 2, canvas_height / 2 + seg_length / 2],
[True, canvas_width - seg_width / 2, canvas_height / 2 + seg_length / 2],
[False, canvas_width / 2, canvas_height - seg_width / 2]
]
このリストを利用すれば、キャンバスへの各セグの描画は下記の処理により実現することができます。
# 描画時のパラメータに従ってセグを描画
for draw_param in SEG_DRAW_PARAMS:
is_rotate, x_shift, y_shift = draw_param
if is_rotate:
# 回転必要な場合は、基準セグの頂点の座標をを90度を回転
r_xs = [-n for n in YS]
r_ys = [n for n in XS]
else:
# 回転不要な場合は基準セグの頂点の座標をそのまま使用
r_xs = XS
r_ys = YS
# 基準セグの各頂点を移動
t_xs = [n + x_shift for n in r_xs]
t_ys = [n + y_shift for n in r_ys]
# 移動後の座標に六角形を描画
seg = canvas.create_polygon(
t_xs[0], t_ys[0],
t_xs[1], t_ys[1],
t_xs[2], t_ys[2],
t_xs[3], t_ys[3],
t_xs[4], t_ys[4],
t_xs[5], t_ys[5],
fill="gray",
width=0
)
キャンバスの高さとセグの長さ
ここまでキャンバスの高さとセグの長さをそれぞれ単に canvas_height
、seg_length
と表してきましたが、これらは座標の決め方の方針に基づけば、キャンバスの幅 canvas_width
とセグの太さ seg_width
から一意に求めることができます。
seg_length
と canvas_height
を求めるために、下の図のように補助線を入れて各パラメータの関係性を整理したいと思います。
まずは seg_length
について考えてみましょう。オレンジ色で示した seg_length
に注目すると、seg_length
は canvas_width
から seg_width / 2 * 2
を引いた値であると考えられます。
つまり、seg_length
は canvas_width
と seg_width
によって下記のように求めることができます。
seg_length = canvas_width - seg_width
次は canvas_height
について考えてみましょう。オレンジ色で示した canvas_height
に注目すると、canvas_height
は seg_length * 2
と seg_width / 2 * 2
を足した値であると考えられます。
さらに、seg_length = canvas_width - seg_width
ですので、これを代入して考えれば canvas_height
は下記の式で求めることができることになります。
canvas_height = canvas_width * 2 - seg_width
上記により、canvas_height
と seg_length
が canvas_width
と seg_width
で求めることができることが確認できたと思います。
逆にいうと、これらの関係性を満たさないような canvas_height
と seg_length
を用いてしまうと、今回説明したようにはセグを描画することができなくなってしまいますので注意してください(セグの位置がずれたりする)。
これは、キャンバス作成時に指定するキャンバスの高さ(height
)は自由に設定することができないことを意味するので、キャンバス作成時には特に注意するようにしてください。
スポンサーリンク
デジタル時計のサンプルスクリプト
最後に、デジタル時計のサンプルスクリプトを紹介していきます。
スクリプト
ここまで解説してきた内容に基づいて作成したデジタル時計のサンプルスクリプトは下記のようになります。
# -*- coding: utf-8 -*-
import tkinter
from datetime import datetime, timedelta, timezone
# セグの太さ
SEG_WIDTH = 20
# 数字表示用のキャンバスのサイズの設定
CANVAS_WIDTH_NUMBER = 100
# コロン表示用のキャンバスのサイズ
CANVAS_WIDTH_COLON = 50
# 色の設定
COLOR_BG = "black" # 時計の背景色
COLOR_SEG_ON = "orange" # セグ点灯時の色
COLOR_SEG_OFF = "gray20" # セグ消灯時の色
class ColonCanvas(tkinter.Canvas):
'''コロンを表現する図形を描画するキャンバス'''
canvas_width = CANVAS_WIDTH_COLON
canvas_height = CANVAS_WIDTH_NUMBER * 2 - SEG_WIDTH
def __init__(self, master, **kw):
super().__init__(master, kw)
def draw(self):
'''コロンを長方形として描画する'''
for i in range(2):
# 横方向の中心はキャンバスの中心
center_x = ColonCanvas.canvas_width / 2
# 縦方向の中心はキャンバスの1/3 or 2/3の位置
center_y = (i + 1) * ColonCanvas.canvas_height / 3
# 長方形の確変の長さが20となるように座標を設定
x1 = center_x - 10
y1 = center_y - 10
x2 = center_x + 10
y2 = center_y + 10
# 長方形を描画
self.create_rectangle(
x1, y1, x2, y2,
fill=COLOR_SEG_ON,
width=0
)
class NumberCanvas(tkinter.Canvas):
'''数字を表現するセグを描画するキャンバス'''
# 各種パラメータの設定
canvas_width = CANVAS_WIDTH_NUMBER
seg_width = SEG_WIDTH
canvas_height = canvas_width * 2 - seg_width
seg_length = canvas_width - seg_width
# 数字をセグ表示する際の、各セグの点灯or消灯の情報のリスト
ON_OFF_INFOS = [
# 上, 上左, 上右, 中, 下左, 下右, 下
[True, True, True, False, True, True, True], # 0
[False, False, True, False, False, True, False], # 1
[True, False, True, True, True, False, True], # 2
[True, False, True, True, False, True, True], # 3
[False, True, True, True, False, True, False], # 4
[True, True, False, True, False, True, True], # 5
[True, True, False, True, True, True, True], # 6
[True, True, True, False, False, True, False], # 7
[True, True, True, True, True, True, True], # 8
[True, True, True, True, False, True, True] # 9
]
# 各セグを描画するときの情報のリスト
SEG_DRAW_PARAMS = [
# [回転するか?, 横方向の移動量, 縦方向の移動量]
[False, canvas_width / 2, seg_width / 2],
[True, seg_width / 2, canvas_height / 2 - seg_length / 2],
[True, canvas_width - seg_width / 2,
canvas_height / 2 - seg_length / 2],
[False, canvas_width / 2, canvas_height / 2],
[True, seg_width / 2, canvas_height / 2 + seg_length / 2],
[True, canvas_width - seg_width / 2,
canvas_height / 2 + seg_length / 2],
[False, canvas_width / 2, canvas_height - seg_width / 2]
]
# 基準セグの各頂点のx座標
XS = [
- canvas_width / 2 + seg_width, # 左上
- canvas_width / 2 + seg_width / 2, # 左中
- canvas_width / 2 + seg_width, # 左下
canvas_width / 2 - seg_width, # 右下
canvas_width / 2 - seg_width / 2, # 右中
canvas_width / 2 - seg_width # 右上
]
# 基準セグの各頂点のy座標
YS = [
- seg_width / 2, # 左上
0, # 左中
seg_width / 2, # 左下
seg_width / 2, # 右下
0, # 右中
- seg_width / 2 # 右上
]
def __init__(self, master, **kw):
super().__init__(master, kw)
# 描画した六角形のIDを管理するリスト
self.segs = []
def draw(self):
'''セグを描画する'''
# 描画時のパラメータに従ってセグを描画
for draw_param in NumberCanvas.SEG_DRAW_PARAMS:
is_rotate, x_shift, y_shift = draw_param
if is_rotate:
# 回転必要な場合は、基準セグの頂点の座標をを90度を回転
r_xs = [-n for n in NumberCanvas.YS]
r_ys = [n for n in NumberCanvas.XS]
else:
# 回転不要な場合は基準セグの頂点の座標をそのまま使用
r_xs = NumberCanvas.XS
r_ys = NumberCanvas.YS
# 基準セグの各頂点を移動
t_xs = [n + x_shift for n in r_xs]
t_ys = [n + y_shift for n in r_ys]
# 移動後の座標に六角形を描画
seg = self.create_polygon(
t_xs[0], t_ys[0],
t_xs[1], t_ys[1],
t_xs[2], t_ys[2],
t_xs[3], t_ys[3],
t_xs[4], t_ys[4],
t_xs[5], t_ys[5],
fill=COLOR_SEG_OFF,
width=0,
)
# 描画した六角形のIDをリストに格納
self.segs.append(seg)
def update(self, num):
'''数字numをセグ表示する'''
for seg, is_on in zip(self.segs, NumberCanvas.ON_OFF_INFOS[num]):
if is_on:
# 点灯する場合のセグの色
color = COLOR_SEG_ON
else:
# 消灯する場合のセグの色
color = COLOR_SEG_OFF
# セグを表現する多角形の色を変更
self.itemconfig(seg, fill=color)
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:
'''時計を描画するクラス'''
CANVAS_NUMBER = 1
CANVAS_COLON = 2
def __init__(self, master):
# 各種設定を行なった後に時計の盤面を描画
self.initSetting(master)
self.createClock()
def initSetting(self, master):
'''時計描画に必要な設定を行う'''
# ウィジェットの作成先を設定
self.master = master
# 作成するキャンバスの種類
self.canvas_types = [
Drawer.CANVAS_NUMBER,
Drawer.CANVAS_NUMBER,
Drawer.CANVAS_COLON,
Drawer.CANVAS_NUMBER,
Drawer.CANVAS_NUMBER,
Drawer.CANVAS_COLON,
Drawer.CANVAS_NUMBER,
Drawer.CANVAS_NUMBER
]
self.number_canvases = []
self.colon_canvases = []
def createClock(self):
'''時計の盤面を作成する'''
# 数字のセグ表示用のキャンバスとコロン表示用のキャンバスを作成
for canvas_type in self.canvas_types:
if canvas_type == Drawer.CANVAS_NUMBER:
# 数字のセグ表示用のキャンバスを作成
canvas = NumberCanvas(
self.master,
width=NumberCanvas.canvas_width,
height=NumberCanvas.canvas_height,
bg=COLOR_BG,
highlightthickness=0,
)
self.number_canvases.append(canvas)
else:
# コロン表示用のキャンバスを作成
canvas = ColonCanvas(
self.master,
width=ColonCanvas.canvas_width,
height=ColonCanvas.canvas_height,
bg=COLOR_BG,
highlightthickness=0,
)
self.colon_canvases.append(canvas)
# 左から順番にpackで詰めていく
canvas.pack(side=tkinter.LEFT, padx=10, pady=10)
def draw(self):
'''各キャンバスに描画する'''
for canvas in self.colon_canvases:
# コロンを描画するよう依頼
canvas.draw()
for canvas in self.number_canvases:
# セグを描画するよう依頼
canvas.draw()
def update(self, hour, minute, second):
'''セグ表示を更新する'''
# 時刻を1桁ずつに分割する
nums = []
nums.append(hour // 10)
nums.append(hour % 10)
nums.append(minute // 10)
nums.append(minute % 10)
nums.append(second // 10)
nums.append(second % 10)
for canvas, num in zip(self.number_canvases, nums):
# 各キャンバスに対応する数字numをセグ表示するように依頼
canvas.update(num)
class DigitalClock:
'''デジタル時計を実現するクラス'''
def __init__(self, master):
# after実行用にウィジェットのインスタンスを保持
self.master = master
# 各種クラスのオブジェクトを生成
self.timer = Timer()
self.drawer = Drawer(master)
# コロンとセグを描画する
self.draw()
# 時刻をセグ表示する
self.update()
def draw(self):
'''コロンとセグを描画する'''
self.drawer.draw()
def update(self):
'''時刻をセグ表示を更新する'''
# 時刻を取得し、その時刻に合わせて針を進める
hour, minute, second = self.timer.time()
self.drawer.update(hour, minute, second)
# 1秒後に再度セグ表示を行う
self.master.after(1000, self.update)
if __name__ == "__main__":
app = tkinter.Tk()
# 背景色を設定
app.config(bg=COLOR_BG)
DigitalClock(app)
app.mainloop()
スクリプトの実行
スクリプトを実行すると、下の図のような画面のアプリが起動します。時刻がセグ表示されており、1秒ごとに時刻が更新されることが確認できると思います。
スポンサーリンク
スクリプトの設定
下記部分でスクリプトを設定することが可能です。
# セグの太さ
SEG_WIDTH = 20 # 自由に設定可能
# 数字表示用のキャンバスのサイズの設定
CANVAS_WIDTH_NUMBER = 100 # 自由に設定可能
# コロン表示用のキャンバスのサイズ
CANVAS_WIDTH_COLON = 50 # 自由に設定可能
# 色の設定
COLOR_BG = "white" # 時計の背景色
COLOR_SEG_ON = "blue" # セグ点灯時の色
COLOR_SEG_OFF = "gray90" # セグ消灯時の色
上記の設定を変更することにより、例えばアプリの見た目を下の図のように変更することも可能です。
スクリプトの解説
最後にスクリプトの解説を行なっておきます。
このスクリプトでは下記の5つのクラスを用意していますので、これらの各クラスについて簡単に解説をしていきたいと思います。
ColonCanvas
NumberCanvas
Timer
Drawer
DigitalClock
ColonCanvas
ColonCanvas
は tkinter.Canvas
のサブクラスで、コロンの描画専用のキャンバスとしています。
draw
メソッドを持っており、この draw
メソッドが実行された際にキャンバスへのコロンの描画を行います。
コロンは単に長方形(正方形)で表現しており、親クラスの持つ create_rectangle
メソッドにより描画しています。
また、コロンは1つのキャンバスに2つ描画しており、x
軸方向に関してはキャンバスの真ん中に、y
軸方向に関してはキャンバスの高さの 1 / 3
の位置とキャンバスの高さの 2 / 3
の位置に描画するように描画先の座標を設定しています。
NumberCanvas
NumerCanvas
は tkinter.Canvas
のサブクラスで、セグの描画&セグの点灯専用のキャンバスとしています。このクラスのインスタンス1つが、時刻の1桁分のセグの描画とセグの点灯を行います。
時刻に合わせてセグを点灯させるとセグの座標を計算するで紹介した下記のリストについてはクラスで共通ですので、クラス変数として保持するようにしています。
ON_OFF_INFOS
:数字をセグ表示する際に各セグの情報(点灯する or 点灯しない)を格納したリストXS
およびXY
:基準セグの各頂点の座標を格納したリストSEG_DRAW_PARAMS
:セグの描画情報(回転する or 回転しない・各セグの中心座標)を格納したリスト
NumerCanvas
は draw
メソッドを持っており、この draw
メソッドが実行された際にキャンバスへのセグの描画を行います。
このセグの描画は、キャンバス上にセグを描画するとセグの座標を計算するで解説した内容に基づいて処理を行なっています。といっても、セグの座標を計算するで示した XS
および XY
と SEG_DRAW_PARAMS
から情報を取得しながらセグの描画を行っているだけです。
また、セグの描画には、描画したセグの ID を次に説明する update
メソッドで使用できるように、リスト self.segs
に格納するようにしています。
さらに、NumerCanvas
は update
メソッドを持っており、この update
メソッドが実行された際に、引数 num
で指定される数字に合わせて各セグの点灯と消灯を行います。
この各セグの点灯と消灯は、時刻に合わせてセグを点灯させるで解説した内容に基づいて処理を行なっています。これもリストの ON_OFF_INFOS
から各セグを点灯するかどうかの情報を取得し、その情報に基づいてセグの塗りつぶし色を itemconfig
メソッドで変更しているだけです。また、itemconfig
メソッドの第1引数には、draw
メソッドによって self.segs
に格納された図形の ID を指定しています。
Timer
Timer
は時刻を取得することを役割としたクラスになります。詳しくは下記ページで解説していますので、不明点がありましたら下記ページを参考にしていただければと思います。
Drawer
Drawer
はキャンバスへの操作を役割とするクラスです。
キャンバスの作成・配置は createClock
メソッドで行っています。
キャンバスを作成するで説明したように、時刻の各数字をセグ表示する6つのキャンバスとコロンを表示する2つのキャンバスを作成しています。つまり、NumerCanvas
のインスタンス6つと ColonCanvas
のインスタンス2つを作成しています。
このインスタンス作成時に、何回目のキャンバス作成時にどちらのクラスのインスタンスを作成するかが分かるよう、事前に self.canvas_types
にその情報を格納するようにしています。
また、座標の決め方の方針で説明したように各セグはキャンバスに接するように配置するようにしています。
ですので、キャンバス同士をくっつけて配置してしまうと数字同士もくっついて見栄えが悪いので、離れて配置されるように pack
メソッド際に padx
と pady
を指定するようにしています。
Drawer
クラスは draw
メソッドを持っており、このメソッドでキャンバスへの描画を行うようにしています。具体的には、各キャンバスのクラスのインスタンスに対して draw
メソッドを実行し、セグの描画やコロンの描画を行なっています。
さらに、Drawer
クラスは update
メソッドを持っており、このメソッドで時刻に合わせたセグ表示の更新を行うようにしています。具体的には、NumerCanvas
のインスタンスに対して update
メソッドを実行し、セグ表示の更新を行なっています。
ただ、各インスタンスが担当するのは1つの数字のセグの描画・更新のみですので、事前に時刻を1桁ずつに分割し、1桁ずつセグ表示を更新するように update
メソッドを実行するようにしています。
DigitalClock
DigitalClock
クラスは、Timer
クラスと Drawer
クラスのインスタンスの作成および各インスタンスのメソッドの実行を行います。
基本的にはインスタンスを作成したのちにキャンバスにコロンとセグの描画を行い、そのあとは1秒間隔でセグ表示の更新を行なっているだけです。
まとめ
このページでは、Python で tkinter を利用した「デジタル時計」の作り方の解説を行いました!
結構解説が長くなってしまいましたが、一番伝えたかったことは、”itemconfig
で描画済みの図形の設定を変更できる” というところです。この itemconfig
を利用することで、キャンバス上の図形に動きを持たせるようなことができますので、いろんな場面で活用できると思います。
セグの各頂点の座標の解説に関しては、あまり他の場面で応用できる気はしないですが、セグを描画するような記事が他にあまり見つからなかったことと、こういったちょっと複雑な図形の座標を求める際の参考にはなるかもしれないなぁと思って詳細に解説させていただきました。
ただ結構無理やり座標を求めているので、もっと綺麗に座標は求められるかもしれないですね…。ですが、アプリの見た目としては意図したものになっており、個人的には結構満足してます!
tkinter はこういった身近なものをアプリ化するのにも向いていると思いますので、是非皆さんも身近にあるもののアプリ化に挑戦してみてください!