PythonのGUIツールtkinterチートシート

# はじめに
[Pythonでテニスの動画解析ツールを自作してみた](https://qiita.com/otakoma/items/083ccad0c81a79bef049) に触発されて、tkinterで[バスケの動画解析ツール](https://note.mu/nanako_ut/n/n7dbb05e96fbe)を自作しました。
その際に調べたtkinterについてまとめます。

# ウィンドウ、フレーム、ウィジェットの関連

![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/898d5225-f856-74f4-5f1d-337cb55a2954.png)

# 全ソース
https://github.com/nanako-ut/tkinter-cheatSheet

# 基本

以下を基本とし、create_widgets中に追記をしていきます。

```python
import sys
import os
import numpy as np
import tkinter as tk

import tkinter.ttk as ttk
from PIL import Image, ImageTk
from tkinter import messagebox as tkMessageBox
from tkinter import filedialog as tkFileDialog


# アプリケーション（GUI）クラス
class Application(tk.Frame):
    DEBUG_LOG = True
    def __init__(self, master=None):
        super().__init__(master)
        self.pack()

        self.create_widgets()
        
    def create_widgets(self):
        print('DEBUG:----{}----'.format(sys._getframe().f_code.co_name)) if self.DEBUG_LOG else ""

# 実行
root = tk.Tk()        
myapp = Application(master=root)
myapp.master.title("My Application") # タイトル
myapp.master.geometry("1000x500") # ウィンドウの幅と高さピクセル単位で指定（width x height）

myapp.mainloop()
```

# 部品を配置する入れ物（コンテナ、フレーム）を作成する

## PaneWindow

PanedWindowは、画面を任意の数で分割できるウィジェット。区切られた各々はサッシで区切られており、サッシはマウスで移動可能。

### ウィジェット作成・配置

```python

    def create_widgets(self):
        # ペインウィンドウ
        # PanedWindow
        ##  orient : 配置（vertical or horizontal）
        ##  bg : 枠線の色
        # pack
        ##  expand ：可変（True or False(固定)
        ##  fill : スペースが空いている場合の動き（tk.BOTH　縦横に広がる）
        ##  side ：　配置する際にどの方向からつめていくか（side or top ・・・）
        pw_main = tk.PanedWindow(self.master, orient='horizontal')
        pw_main.pack(expand=True, fill = tk.BOTH, side="left")
        
        pw_left = tk.PanedWindow(pw_main, bg="cyan", orient='vertical')
        pw_main.add(pw_left)
        pw_right = tk.PanedWindow(pw_main, bg="yellow", orient='vertical')
        pw_main.add(pw_right)

```
### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/dd1bf375-4be4-8faf-8875-b57f0785a407.png)

## Frame
ボタンや入力フィールド等複数の部品をまとめるもの

### ウィジェット作成・配置

```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # フレーム
        ##  bd ：ボーダーの幅
        ##  relief ：フレームの枠の形
        fm_select = tk.Frame(pw_left, bd=2, relief="ridge")
        pw_left.add(fm_select)
```

### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/3dc1593e-00ab-0713-46f1-d5a7fdc9d9c1.png)

# ラベル
## Label

### ウィジェット作成・配置

```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # ラベル
        ## padx , pady ：外側の横、縦の隙間
        label_fpath = tk.Label(fm_select, text="ファイルパス(入力)", width=20)
        ## ラベルを配置
        label_fpath.grid(row=0, column=0, padx=2, pady=2)
```

### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/a09d4e40-0fa2-6add-4136-bcd5f49472aa.png)

# 1行入力
## Entry

### ウィジェット作成・配置
```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # 1行入力
        ##  justify：文字寄せ（center or left or right）
        ##  sticky：スペースが空いている場合の動き（tk.W + tk.E 縦横に広がる）
        entry_fpath = tk.Entry(fm_select, justify="left", width=50)
        entry_fpath.grid(row=0, column=1, sticky=tk.W + tk.E,padx=2, pady=2)
```

### 削除・追加        
```python
        ## 削除
        entry_fpath.delete( 0, tk.END ) 
        ## 先頭行に値を設定
        entry_fpath.insert( 0, "input your file path..." ) 
```
### 値を取得        
        
```python
        ## 値を取得
        print('Entryの初期値を出力：{}'.format(entry_fpath.get()))
```
### 実行結果

![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/b4fcda8a-38d2-0b4a-a817-657f1d8928f9.png)

# 複数行入力
## Text
### ウィジェット作成・配置
```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # ラベル
        label_fpaths = tk.Label(fm_select, text="ファイルパス(複数)", width=20)
        label_fpaths.grid(row=1, column=0, padx=2, pady=2)

        # 複数行入力
        ## witdh ：入力する文字数
        ## wrap：長い行の折り返し方法（tk.CHAR: 文字単位で折り返す or tk.NONE: 折り返ししない or ・・・）
        selected_files = tk.Text(fm_select, height=10, width=50, wrap=tk.CHAR)
        selected_files.grid(row=1, column=1, padx=2, pady=2)

```
### 削除・追加
```python
        ## 全行削除
        selected_files.delete( '1.0', tk.END ) 
        
        ## 値を設定
        selected_files.insert( tk.END, "No.1 file path\n" ) 
        selected_files.insert( tk.END, "No.2 file path\n" ) 

```
### 値を取得
```python
        ## 全行取得        
        print(selected_files.get('1.0', tk.END ))
```
### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/5afa0938-3b33-9d9e-e4d8-2a4597bc5a9c.png)

# ボタン
## Button

### ウィジェット作成・配置
ボタンを押した時に実行する処理は、「command = メソッド名」で指定

```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # ボタン
        ## columnspan ：　何列に渡って配置するか
        ## rowspan ：　何行に渡って配置するか
        btn_select_file = tk.Button(fm_select, text="ファイル選択", command=self.select_file) 
        btn_select_file.grid(row=2, column=1, sticky=tk.W + tk.E, padx=2, pady=2)

    def select_file(self):
        print('select_file...')

```
### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/d473103c-5caf-5cfe-2719-f5ff85c67a61.png)

## Buttonの処理（イベントに引数を渡す）
commandで指定する関数をlambda式で記述

### ウィジェット作成・配置
```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # ボタン
        # ボタンイベントに引数を渡す
        fm_btns = tk.Frame(pw_left, bd=2, relief="ridge")
        fm_btns.pack(side="top")
        pw_left.add(fm_btns)
        
        btn_tool_1 = tk.Button(fm_btns, text="ボタン（１）", command=lambda:self.btn_ivent("test1"), width=20) 
        btn_tool_1.grid(row=3, column=0, sticky=tk.W + tk.E, padx=2, pady=10)

        btn_tool_2 = tk.Button(fm_btns, text="ボタン（２）", command=lambda:self.btn_ivent("test2"), width=20) 
        btn_tool_2.grid(row=3, column=1, sticky=tk.W + tk.E, padx=2, pady=10)

    def btn_ivent(self, msg):
        print(msg)

```
### 実行結果
ボタン（１）を押すと、引数で渡した「test1」が出力されて、
ボタン（２）を押すと、引数で渡した「test2」が出力される。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/9483ad3d-c926-5e7d-45b5-d52b9e9e65df.png)


# ファイル選択
上記で記述した select_file メソッドを実装。

### ウィジェット作成・配置
jpg、png形式のファイルのみ選択可能なファイル選択ダイアログを表示する

```python
    def select_file(self):
        # 選択可能な拡張子を限定
        fTyp =[("", "*.jpg;*.png")]
        iDir = os.path.abspath(os.path.dirname("__file__"))
        files = tkFileDialog.askopenfilenames(filetypes = fTyp, initialdir = iDir)
```

### 選択したファイル名を一覧表示する

```python
    def select_file(self):
        # 選択可能な拡張子を限定
        fTyp =[("", "*.jpg;*.png")]
        iDir = os.path.abspath(os.path.dirname("__file__"))
        files = tkFileDialog.askopenfilenames(filetypes = fTyp, initialdir = iDir)
        
        # 選択されたファイルパスを表示
        # 前回分を全行削除
        self.selected_files.delete( '1.0', tk.END ) 
        file_list = list(files)
        if(len(file_list) <= 10):
            for i in np.arange(0, len(file_list)):
                self.selected_files.insert("end", "{}\n".format(file_list[i]))                
```
### 実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/128618e8-3b99-1bca-048b-8f90ae55b656.png)

# 画像表示
### ウィジェット作成・配置

```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 上記の続きに追加
        # 画像表示
        # 画面の右側エリアに画像を表示する
        fm_img = tk.Frame(pw_right, bd=2, relief="ridge")
        pw_right.add(fm_img)

        self.panel_img = tk.Label(fm_img)
        self.panel_img.pack()
```

### 画像を表示
ファイル選択ダイアログで選択した画像を１枚表示する

```python
    def select_file(self):
        # 
        # ～ファイル選択・表示部分（上記記載分）を省略～
        #  
        # 上記の続きに追加
                
        # 先頭の画像ファイルを表示する
        self.show_image(file_list[0])
                
    def show_image(self, file_path):
        w,h = 500, 500
        image = Image.open(file_path)
        image = image.resize((w, h), Image.ANTIALIAS)
        self.img = ImageTk.PhotoImage(image)
        self.panel_img.configure(image=self.img)
        self.panel_img.pack()

```

# 画像をクリックした時の処理を追加
### 画像表示ウィジェットにイベントをバインド

```python
    def create_widgets(self):
        # 
        # ～上記記載分を省略～
        #  
        # 画像表示用のウィジェットに以下を追記
        self.panel_img.bind("<Button-1>", self.img_click)

    def img_click(self, ev):
        print('クリックされた位置：x {}、y {}'.format(ev.x, ev.y))
```

### 実行結果

![image.png](https://qiita-image-store.s3.amazonaws.com/0/170139/9be9434b-f3e9-3760-e0eb-9bd626a05e6a.png)

# 参考（Thanks！）
- [Widget を配置しよう](http://www.shido.info/py/tkinter2.html)

- [Tkinter 8.5 reference: a GUI for Python](http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html)

- [Tk を用いたグラフィカルユーザインターフェイス](https://docs.python.jp/3/library/tk.html)

- [Python：処理ファイルをGUIから選択する方法](https://qiita.com/chanmaru/items/1b64aa91dcd45ad91540)