TkinterでGUIアプリケーション開発

## GUIとは
GUIはGraphical User Interface の略で、多くのPCやスマホで利用されている視覚的に操作できるアプリ/ソフトのことをいいます。
これに対立する概念としてCUI(Character User Interface)があり、コマンドプロンプトやpowershell(windows)、Mac/Linuxのterminal上で動くアプリケーションがこれにあたります。

## PythonでGUI
pythonにもGUIアプリケーションを作るためのライブラリがいくつか存在します。その中で今回はTkinterをとりあげます。
Tkinterはそれなりに古く、資料が豊富にあり、そもそもpythonを入れると一緒に入っているので別途インストールする必要がなく手軽に始めやすいという利点があります(日本語を使うのはあまり向いてないっぽいけど)。

## Tkinter
Tkinterとは端的に言うとTcl/Tkのpython用ラッパーです。
つまり何なんだと言われると、<b>Tcl</b>とは80年代に使われていたスクリプト言語で、<b>Tk</b>とはそのGUItoolkitです。今ではあまり聞かない言語ですが、windows・Mac・Linuxで現役で動作する上、Tkはtclごとpythonに組み込まれるなど陰で生き続けています。
このtcl/tkをpythonの文法で使えるようにしたのがTkinterとなります。

# TkinterでのGUIアプリケーション開発
### 環境
python3.7を使用しています。
MacOS catalina 10.15.3で実行しています。

### 前提知識
Tkinterではボタンやウィンドウは全てwidgetと呼ばれ、これらを組み合わせることでGUIを構成します。

## 1. 最も簡単なTkinter app

```python:
from tkinter import *

root = Tk()

root.mainloop()
```
 上記を実行してみましょう。以下のような真っ白なウィンドウが表示されたと思います。
<img width="211" alt="スクリーンショット 2020-03-20 8.56.28.png (77.2 kB)" src="https://img.esa.io/uploads/production/attachments/14023/2020/03/20/69393/ff6d287f-05e8-4962-bd80-61a298587515.png">

#### コードの解説
巷のチュートリアルでは`from tkinter import *`のようにwildcard importでtkinterのライブラリを全てimportすることが多いようです。
ただしこのようなimportはあまり推奨はされていないので、きっちりやりたい人は以下の記事を参考に必要なものを個別にimportすることをおすすめします。
参考: [How To Avoid Wildcard Imports](https://stevepython.wordpress.com/2019/01/12/how-to-avoid-wildcard-imports/)

この記事ではめんどくさくなってwildcard importを使ってしまいました。気が向いたら直します。

tkinterでアプリを作る際に絶対に必要なのがTk root widgetで、これを作るのが`root = Tk()`です。先ほどみた真っ白なウィンドウで、この中にボタンやテキストを配置することになります。
tkinterは歴史が長く多くのチュートリアルがありますが、それぞれ様々な流儀で書かれています。中でも大きく2つの派閥に分かれているのがこのwidgetをクラスにするか否かです。個人的には[An Introduction To Tkinter](https://effbot.org/tkinterbook/tkinter-hello-again.htm )に倣い、初めはクラスにせず、後半コードが長くなってからクラスにしたいと思います。

Tkinterのアプリケーションとマウスのクリックなどのイベントを関連づけるのが`root.mainloop()`です。
「イベントがくるのを待ち、イベントが起こる度に何かをする」を繰り返すのでloopとついています。

## 2.widgetを配置する
tkinerのwidgetにはテキストエリア、ラベル、ラジオボタン、プルダウン等色々あります。また、配置する方法にもpack(), grid(), place()の3種類があり、それぞれ違いがあります。

| pack() | grid() | place() |
| :---: | :---: | :---: |
| 一次元的に並べる|2次元的に並べる|前者２つでうまくいかない時に使う|
### ・pack() で配置する
先ほどのコードに4行(とエンコード情報を1行)追加しました。

```python:

# -*- coding: utf-8 -*-
from tkinter import *

root = Tk()

w = Label(text="Enter your name!")
w.pack()
t = Entry()
t.pack()

root.mainloop()
```
これを実行すると以下のようになります。
<img width="208" alt="実行されたプログラム" src="https://img.esa.io/uploads/production/attachments/14023/2020/03/25/69393/75c02615-8caa-4aec-b35e-4f2a15c3a00f.png">

一次元的(縦に一列に並んだ形)にLabel widgetとEntry widgetを配置しています。オラオラアプリならいいですが、わかりにくい気はします。`# -*- coding: utf-8 -*-`を追加したのは、これを書いておかないと日本語での入力ができないからです。なお、これを書いていてもエンターを押すまで入力した文字が表示されないので、マルチバイトの文字が考慮されていないライブラリだと言えます。textとして日本語を使う場合は`u"名前"`のようにuを付けます。

### ・grid() で配置する
pack()で配置する場合、全てのwidgetは縦並びになります。とはいえわかりづらい。例えば、以下のような入力欄の左にラベルが表示されているのに慣れている方がほとんどかと思います。
<img width="255" alt="スクリーンショット 2020-03-25 23.52.01.png (57.0 kB)" src="https://img.esa.io/uploads/production/attachments/14023/2020/03/25/69393/134d668a-c167-40bc-b917-f95e841bcb99.png">
こうした二次元的な配置をする場合、grid()を使用します。
gridはroot widgetを縦横の列に分け、何列目の何行目に置くと言う形でGUIを構成します。
「pack()で配置する」の項で書いたコードを変更して、以下のようなGUIを作成します。

```python
# -*- coding: utf-8 -*-
from tkinter import *

root = Tk()

w = Label(text="Name")
w.grid(row=0, column=0)
t = Entry()
t.grid(row=0, column=1)

w1 = Label(text="adress")
w1.grid(row=1, column=0)
t1 = Entry()
t1.grid(row=1, column=1)

root.mainloop()
```

### ・placeで配置する
基本的にはgrid()で配置している例が多いように思いますが、凝ったことをしたい場合place()を使う必要があります。これはまた今度書きます。

## 3.データをあれやこれやする
もちろんアプリケーションなわけですから、インプットに対して計算結果を返したり、何かをする必要があります。
例えば、「入力されたテキストから何か生成する」とか、「選択されたボタンに応じてアウトプットを変える」とかです。
今回は名前とアドレスを入力し確認ボタンを押すと、下に入力した文字が表示されるGUIアプリケーションを作ってみます。
<img width="252" alt="スクリーンショット 2020-03-26 1.47.45.png (48.9 kB)" src="https://img.esa.io/uploads/production/attachments/14023/2020/03/26/69393/12a35380-3850-4262-b7b7-6fab43cbc773.png">
Confirmボタンを押すと...
<img width="252" alt="スクリーンショット 2020-03-26 1.48.27.png (61.3 kB)" src="https://img.esa.io/uploads/production/attachments/14023/2020/03/26/69393/642c5aa2-61ff-470b-90ad-7c0645475301.png">
ボタンの下に入力情報が表示されました。
これのコードは以下です。

```python
# -*- coding: utf-8 -*-
from tkinter import *

root = Tk()

def Confirm_click():
    input_name = t.get()
    input_adress = t1.get()
    w2.configure(text=input_name)
    w3.configure(text=input_adress)

w = Label(text=u"名前")
w.grid(row=0, column=0)
t = Entry()
t.grid(row=0, column=1)

w1 = Label(text="adress")
w1.grid(row=1, column=0)
t1 = Entry()
t1.grid(row=1, column=1)

conf_button = Button(text="Confirm", command=Confirm_click)
conf_button.grid(row=2, column=1)

w2 = Label(text=" ")
w2.grid(row=3, column=1)

w3 = Label(text=" ")
w3.grid(row=4, column=1)

root.mainloop()
```

少しコードが醜くなってしまいましたが、後でクラスにしてきれいにしますので、ひとまずこれで行きます。

まず目に入るのはいきなり現れた`def Confbutton_click()`だと思いますが、一旦無視してください。ちゃんと説明するので。その下の部分、

```python
w = Label(text=u"名前")
w.grid(row=0, column=0)
t = Entry()
t.grid(row=0, column=1)

w1 = Label(text="adress")
w1.grid(row=1, column=0)
t1 = Entry()
t1.grid(row=1, column=1)
```
は上の<b>grid()で表示する</b>の項のと同じです。
その下に今回はconfirmボタンを追加しました。

```python
conf_button = Button(text="Confirm", command=Confirm_click)
conf_button.grid(row=2, column=1)
```

confirm ボタンに`command=Confirm_click`と先ほど無視してくださいと書いた関数が出てきました。このボタンが押されると先の`Confirm_click()`が実行されるのです。この関数を先に定義したのは、定義しておかなければ`not defined`と言われてしまうからです。(自分の時は少なくともそうだった)
それではconfirm_click関数に飛んで見ましょう。

```python
def Confirm_click():
    input_name = t.get()
    input_adress = t1.get()
    w2.configure(text=input_name)
    w3.configure(text=input_adress)
```

ボタンが押されたあとの動作は、入力された文字列を受け取り、ボタンの下に表示することです。
まず、`get()`メソッドで入力された文字を受け取り、表示するwidgetのtextプロパティを`configure()`メソッドで書き換えます。

`conf_button.grid()`の下にある

```python
w2 = Label(text=" ")
w2.grid(row=3, column=1)

w3 = Label(text=" ")
w3.grid(row=4, column=1)
```

が入力された文字を表示する場所です。

## 4.コードをクラスで書く
上のコードはごちゃごちゃしてきたので、見やすくするためにメインの部分はクラスにしましょう。
すると以下のようになります。
selfについての解説は[Understanding self in Python](https://medium.com/quick-code/understanding-self-in-python-a3704319e5f0)が詳しいです。

```python
# -*- coding: utf-8 -*-
from tkinter import *

class window(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)

        w = Label(text=u"名前")
        w.grid(row=0, column=0)
        self.t = Entry()
        self.t.grid(row=0, column=1)

        w1 = Label(text="adress")
        w1.grid(row=1, column=0)
        self.t1 = Entry()
        self.t1.grid(row=1, column=1)

        self.conf_button = Button(text="Confirm", command=self.Confirm_click)
        self.conf_button.grid(row=2, column=1)

        self.w2 = Label(text="")
        self.w2.grid(row=3, column=1)

        self.w3 = Label(text="")
        self.w3.grid(row=4, column=1)


    def Confirm_click(self):
        input_name = self.t.get()
        input_adress = self.t1.get()
        self.w2.configure(text=input_name)
        self.w3.configure(text=input_adress)

if __name__ == "__main__":
    root = Tk()
    window(root)
    root.mainloop()
```
少なくとも先のコードよりは表示されるwidgetがまとめられ、動作も目でおいやすくなったと思います。