Tkinter CookBook　～時計～

#Tkinter CookBook　時計の作成

##前提

###1. 実行環境

本ページで記載しているプログラムコードは**Windows10, python3.6**で実行できることを確認しています。

### 2. 必要なpythonモジュール

特に必要ありません。

※python3.6本体に含まれております。

###3. 本投稿の目的

**tkinter**で簡単なデジタル時計を作成します。

##それでは、はじめましょう

```python

import tkinter as tk
import datetime
import time
import threading

class Timer:
    def __init__(self):
        self.root = tk.Tk()
        
        self.label = tk.Label(self.root)
        self.label["font"] = ("Helvetica", 50)
        self.label["bg"] = "green"
        self.label["fg"] = "white"
        self.label.grid(column=0, row=0)

    def changeLabelText(self):
        while True:
            self.label["text"] = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
            time.sleep(1)

if __name__ == "__main__":
    timer = Timer()
    thread = threading.Thread(target=timer.changeLabelText)
    thread.start()
    timer.root.mainloop()
```

実行すると下記のようなウィンドウが起動します。
1秒ごとに画面が更新されるようになっています。

![timer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/b85e2995-8611-b653-bfec-e3e346e45c79.png)

このプログラムが行っていることは

1. **「Label」**オブジェクトを配置
2. **「Label」**オブジェクトの**「text」**属性を1秒ごとに更新

です。

### 1. 「Label」オブジェクトを配置

```python

class Timer:
    def __init__(self):
        self.root = tk.Tk()

        self.label = tk.Label(self.root)
        self.label["font"] = ("Helvetica", 50)
        self.label["bg"] = "green"
        self.label["fg"] = "white"
        self.label.grid(column=0, row=0)
```
クラスオブジェクトとして**「Timer」**を宣言し、**「__init__」**部分でオブジェクトの作成、設定、配置を行っています。

オブジェクトを作る際は、tkinterで実装されているオブジェクト（ここでは**「tk.Label」**）をインスタンス化します。

次にオブジェクトの各属性を設定します。オブジェクトの属性にアクセスする方法はいくつかありますが、私は次のように行っています。

```python

tkinterインスタンス["属性名"] = 属性値
```
設定できる属性は色々あります。今回のプログラムで設定したのは、

- font：表示する文字のフォント
- fg：表示する文字の色
- bg：表示する背景色
- text：表示する文字列

です。

最後に（良く忘れがちですが）作成したオブジェクトを配置しましょう。

*※インスタンス化した時点で配置されると思いがちですが、そんなことはありません。私は過去に何度も作ったはずのオブジェクトが表示されなくて焦ったことがあります。*

配置方法もいくつかありますがここでは、

<p>
<pre>
tkinterインスタンス.grid(column=(横の値), row=(縦の値))
</pre>
</p>

としています。

###2 「Label」オブジェクトの「text」属性を1秒ごとに更新

<p>
<pre>
  def changeLabelText(self):
        while True:
            self.label["text"] = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
</pre>
</p>

クラスのメソッドとして**changeLabelText(self)**を定義、呼び出された際に**self.label**の**text**属性にアクセスし、表示する文字列を更新します。

更新する文字列は現在の日付、時刻です。

**datetime**オブジェクトの**now**メソッドで現在の日付、時刻を取得します。このままだと**datetime**オブジェクト型なので、これを**str**型に変換します。それが**strftime**です。

**strftime**の中にある**%Y**とか**%H**は**datetime**オブジェクトで取得した**「年」**や**「時間」**を表します。

具体例は下記の通りです。

<p>
<pre>
>>> time = datetime.datetime.now()
>>> time
datetime.datetime(2019, 4, 28, 10, 19, 10, 795179)

>>> time.strftime("%Y%m%d")
'20190428'

>>> time.strftime("%Y/%m/%d")
'2019/04/28'

>>> time.strftime("%Y/%m/%d %H:%M:%S")
'2019/04/28 10:19:10'
</pre>
</p>

上で示したように**strftime**は**datetime**オブジェクト型を**str**型に変換する際のフォーマットを指定するメソッドです。変換先が**str**型なので、**%hogehoge**の間に任意の文字列、例えば**「/」**や**「：」**を入れることが可能です。

1秒ごとに更新を行うため、**time.sleep**関数を使用します。

<p>
<pre>
time.sleep(1)
</pre>
</p>

引数として待機させたいだけの時間を指定します。

##実行

実行する際の注意点です。

```python

if __name__ == "__main__":
    timer = Timer()
    thread = threading.Thread(target=timer.changeLabelText)
    thread.start()
    timer.root.mainloop()
```

上記のような記述をpythonファイル内に記載しておけば、コマンドプロンプト、ターミナルから**python pythonファイル名**で実行できます。

一番最初に呼び出された場合<code>__name__</code>が<code>__main__</code>となるためです。
このように記述しておけば、他のプログラムで呼ぶことも可能で、このプログラムだけで呼ぶことも可能となります。

また、**「Label」**オブジェクトの**text**属性を更新する関数の実行には**threading**オブジェクトを使用しています。

これは別のスレッド、別の流れを作成するオブジェクトです。GUIは常にユーザーの入力を待ち受けている、つまりウィンドウが閉じるまでループしている状態です。

その中でtime.sleepのような処理を一時的に停止する処理があると、GUIのループと競合（？）して、ウィンドウが固まってしまいます。

そのため、別のスレッド、別の流れを作成し、そちらで表示する文字列の更新を行っています。

*上の説明は正しいかどうか、自信がないのですが私はそのように解釈しています。*

以上です。