pythonのtkinterを簡単に使うため、テーブルレイアウトを踏襲したライブラリ

# tkblock 
tkblockというライブラリを作成し、pypiに公開しました。   
tkblockはtkinterのwidgetを簡単に配置できるように、サポートするためのライブラリです。  
ウィジェットを配置する方法は、古くからあるHTML/CSSのテーブルレイアウトの考え方を利用しています。    

## なぜ作ったのか。
tkinterにはwidgetを配置するための関数には、place, grid, packの3つあります。  
けれども、tkinterに慣れていなければ、この3つの関数を区別して使い分けるのは難しいです。  
はじめは、どの関数を使って配置すべきか悩んでしまいます。  
悩んだ後、関数の調べながら、設計したUIの図通りに配置できずに挫折してしまうというのをよく聞ききました。  
また、自分のPCにWindows Form等導入が面倒だった、仕事先でC＃を使えないこともありました。
そこで、windowsやlinux等のOSや環境に気を遣わずに、pythonで簡単にGUIアプリを作ろうと思いました。
結果、tkinterの配置する関数を意識することなく、widgetを配置できるようなライブラリを作りました。  


## how to use
ライブラリの使い方を説明します。
### ライブラリのインストール。
```bash
pip install tkblock
```
でライブラリをインストールしましょう。  

### ライブラリとtkinterのimport。
まずはライブラリのインポートをします。
```python
import tkinter as tk
from tkinter import ttk
from tkblock.block_service import BlockService
```

### rootウィンドウの作成。
インポートしたライブラリを用いて、ルートウィンドウを作成します。  
ルートウィンドウの生成時には、タイトルとテーブル横幅や高さと行と列の数を指定します。
次にウィジェットの生成時にその行数、列数を指定することでウィジェットを配置する。  

```python
root = BlockService.init("test", 10, 20, 600, 400, is_debug=True)
```

tkinterで作成するアプリのタイトルに"test"を  
列数は10  
行数は20
横幅は600  
高さは400
is_debugはデバッグモード化を表しています。
を指定しています。

![readme_root.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/0626f9f7-8095-6e7e-2c7c-325467a985b6.png)

この図が上記のコードで作成されるルートウィンドウになります。

なお、rootをloopさせるために書きコードを実行する必要があります。
```python
root.mainloop()
```

### 専用のFrameを作成します。
Widgetを簡単に配置するために、rootウィンドウの上に専用のFrameを作成します。  
```python
frame = BlockService.create_frame("main")
```
引数は6つありますが、必ず指定しなければいけないのは作成するFrameの名称のみです。  
ここでは、フレーム名はmainとしています。  
オプション引数については、下記の通りになります。  
+ col: フレームの列数です。未指定の場合、配置先のwindowの列数になります。この場合は10  
+ row: フレームの行数です。未指定の場合、配置先のwindowの行数になります。この場合は20  
+ width: フレームの横幅です。未指定の場合、配置先のwindowの横幅になります。この場合は600  
+ height: フレームの高さです。未指定の場合、配置先のwindowの高さになります。この場合は400  
+ root: フレームを配置するwindowです。未指定の場合、配置先はルートwindowになります。この場合はルートwindow  

ちなみに、テーブルレイアウトが開発時に見やすいように、補助線を引くcanvasが内部で作成されています。  
```python
BlockService.place_frame_widget()
```
上記のコードを実行することで補助線を引けます。  
`BlockService.place_frame_widget()`
に関しては次で説明します。  

![readme_frame.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/49302e3d-06c0-ffcc-eb08-fa24aaaa266d.png)

この図で表示されているFrameが、widgetを配置するためのベースとなります

### Widgetを配置します。
今回は一番簡単なラベルを配置します。

![readme_frame_add_layout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/4690b608-b856-dd72-e301-804f947ed589.png)

ここにラベルを配置したいとします。
そのためには下記のように座標指定します。
```python
BlockService.create_label(frame, 3, 6, 2, 4, text="how to use", anchor=tk.CENTER)
```
もしくは
```python
label = ttk.Label(frame, text="how to use", anchor=tk.CENTER)
label.layout = BlockService.layout(3, 6, 2, 4)
```

上記のコードは2つのコードは同じ結果であり、  
Widgetの中央に"how to use"というテキストのラベルをframeに配置します。  
次の引数でlayoutの値を指定しています。  
layoutの値は(列の開始位置、列の終了位置、行の開始位置、行の終了位置)です。  

create_frame以外、用意されている全てのcreate_xxxの必須引数の指定方法は、配置Frame, 列の開始位置、列の終了位置、行の開始位置、行の終了位置となっています。  
オプション引数には一部を除いてtk, ttkにそのまま渡しています。
この場合、text="how to use"はttk.labelに与えています。
create_xxx系が使用しているものに関しては、docstringを確認お願いします。


今回用意しているWidgetを作成する関数については下記一覧となります
```python
 'create_button', 'create_canvas', 'create_checkbutton', 
'create_combobox', 'create_entry', 'create_frame', 
'create_label', 'create_labelframe', 'create_listbox', 
'create_message', 'create_notebook', 'create_progressbar', 
'create_radiobutton', 'create_scale', 'create_scrollbar', 
'create_spinbox', 'create_text', 'create_toplevel', 'create_treeview'
```


このコードを先ほどの  
```python
BlockService.place_frame_widget()
```
の前に置きます。  
place_frame_widgetはroot widdowが所持しているWidgetをすべて配置してくれる関数です。  
そのため、全てのWidget作成およびlayout指定をしてから実行する必要があります。  

![readme_frame_label.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/7cb142c3-90f5-898e-d040-d4afc006a162.png)  

上記の図のように、意図したところに、label Widgetが配置されています。  

## 基本の完成コード。  

これが今までのコードで作成されたコードになります。  

```python
import tkinter as tk
from tkinter import ttk

from tkblock.block_service import BlockService

root = BlockService.init("test", 10, 20, 600, 400, is_debug=True)
frame = BlockService.create_frame("test")
BlockService.create_label(frame, text="how to use", 3, 6, 2, 4, anchor=tk.CENTER)
BlockService.place_frame_widget()
root.mainloop()
```

tkblockライブラリのBlockServiceの  
+ init  
+ create_frame  
+ create_xxx  
+ place_frame_widget  
を使うだけで、簡単にtkinterのWidgetが使えます。  

![readme_widget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/ae6e7dd0-2470-481d-faf1-7bfa3d600b40.png)


この図では、上記の基本的な使い方でWidgetをいくつか配置した図になります。  
ぜひ、いろいろなWidgetを配置して、アプリを作成してみてください。  


## Layoutのpadding設定について
ここから少しだけ細かい設定について説明します。  

まずはpaddingについてです。  
html/cssでもpaddingというものがありますが、その機能に似ています。  

![readme_layout1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/3b04a6a2-8b6b-0723-217a-647d5079701f.png)


図のように、赤い箇所にWidgetを置き、青い線の隙間を設定することができます。  
その設定にはlayoutメソッドにオプション引数を設定することで余白を使用できます。  
(1,1)(2,2)の座標にWidgetを余白を一つのBlockに対して30%設定しています。  

```python
root = BlockService.init("test_pad", 3, 3, 600, 400, is_debug=True)
frame = BlockService.create_frame("test_pad")
BlockService._create_label(frame, text="how to use pad", 1, 2, 1, 2, pad_up=0.3, pad_down=0.3, anchor=tk.CENTER, background="red")
```
このコードを実行すると下記になります。  

![readme_layout2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/f81489f7-7033-3df7-32cd-1474cd9cc0dd.png)


pad_upで上の余白を設定をしています。  
pad_downで下の余白を設定をしています。
0.3というのは一つのブロックの30%を余白にするという値です。  
他にも左のpad_leftや右のpad_rightがあります。  
これらを設定することで微調整が可能となっています。  

![readme_layout3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/5c122c7b-54e3-4c8d-b3b6-a209df63ea25.png)


この図はSampleのコードで作成されています。  
0.1ずつ変更した場合などの使用コードになります。  

## 専用のFrameについて
先ほどまでのコードはWidgetを配置するためのLayoutについての説明でした。  
今回では、tkblockが用意している専用のFrameについてを説明します。  

専用のFrameは今までの説明通り  
```python
frame = BlockService.create_frame(name)
```
で作成することができます。  
このコードではrootウィンドウの上にしかFrameを配置できません。  
Frameは様々なところで使用できると便利なため、オプション引数を用意しました。  
```
col: 新規に作成するFrameのテーブル構成となる、列数
row: 新規に作成するFrameのテーブル構成となる、行数
width: 新規に作成するFrameのテーブル構成となる、横幅
height: 新規に作成するFrameのテーブル構成となる、縦幅
root: フレームを配置する先のWindowもしくはフレームもしくはトップレベル
```
となります。  
rootを指定することで親となるFrame等を設定できます。  
root以外の項目は新規に作成するためのテーブル構成であり、指定されなかった場合は親の設定を引き継ぎます。  
また、このFrameもWidget同様にlayoutメソッドでFrameの配置先を変更できます。  
試しに、rootウィンドウの上に専用のFrameを配置し、その上に専用のFrameを配置してみます。  

![readme_frame1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/412239bf-9be0-1e2d-da93-b6f9f07b8929.png)


```pythonroo
root = BlockService.init("test_frame", 10, 10, 600, 400, is_debug=True)
frame1 = BlockService.create_frame("test_frame1", col=5, row=5)
frame1.layout = BlockService.layout(1, 9, 1, 9)
frame2 = BlockService.create_frame("test_frame2", col=3, row=3, root=frame1)
frame2.layout = BlockService.layout(1, 3, 2, 4)
BlockService.place_frame_widget()
root.mainloop()
```
このコードでtest_frame1はrootの(1,1)(9,9)の座標に配置しており、  
test_frame2はtest_frame1の(1,2)(3,4)の座標に配置しています。  
また各フレームのテーブル構成も変更されています。  
このように、専用のFrameの上に専用のFrameを配置することができ、Widgetをまとめたいときに便利です。  

![readme_frame2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/73c0d563-b290-a045-95a2-f095dcb6210e.png)

Sampleでは通常のFrameの上に専用のFrameを配置やtoplevelに配置などもしています。   


## スクロールバーの設定について
ここまで、Widgetの配置方法や、  
Widgetを配置するためのFrameの使い方やWidgetを配置するための細かな調整方法について説明しました。  
アプリにはサイズがあり、rootウィンドウやFrameには縦幅や横幅の設定をしていました。  
けれど、指定したサイズでは収まらないときにtkinterはスクロールバーを用意しています。  
スクロールバーはcanvasやlistboxなどのwidgetと合わせて使用します。  
そのため、基本的にはWidgetとスクロールバーは並べて配置をします。  
layoutメソッドを使用することでスクロールバーも配置したい場所に配置できますが、  
今回は、Widgetの右側と下側にスクロールバーを自動的にセットするための使い方を説明します。  

その使い方は、スクロールバーと連携したいWidgetにスクロールバーをセットするだけです。
```python
scrollbar_listbox = tk.Listbox(frame)
scrollbar_listbox.scrollbar = BlockService.scrollbar(frame, y_enable=True)
```
今回はlistboxのscrollbarという属性にScrollbarをセットしています。  
y軸にセットすることで上下のスクロールバーを設定できます。  
他にもx軸にセットすることで左右のスクロールバーを設定できます。  

スクロールバーのサイズは17px固定となっており、Widgetのサイズから17pxを削り、配置するようにしています。  
そのため、スクロールバーを使用する場合は、Widgetが少し小さくなります。  
スクロールバーのサイズを変更する場合は、sizeというオプション引数に設定することで、変更できます。  

![readme_scrollbar1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/00294e68-485f-917d-c4de-93018bdf3c31.png)

上記の図では、30pxの上下左右のスクロールバーを配置しています。
そのコードが下記となります。
```python
root = BlockService.init("test_scrollbar", 5, 5, 600, 400, is_debug=True)
frame = BlockService.create_frame("test_scrollbar")
listbox_list = tuple([str(x) for x in range(0, 100)] + ["aabfsdgadfsgasdfkj;adsfadsj;kjfeijof"])
_, listbox_var = tk.Listbox(frame, 1, 2, 1, 4, str_value=listbox_list)
listbox.scrollbar = BlockService.scrollbar(frame, x_enable=True, y_enable=True, size=30)
BlockService.place_frame_widget()
root.mainloop()
```

scrollbarの基本的な使い方は同じで、自動で配置してほしい場合は、Widgetにscrollbarを紐づけることで配置されます。  
手動で好きなところに配置したい場合は、通常のWidgetを配置するようにlayout指定すれば可能です。  

## Menuバーの使い方
tkinterの使い方と同じなため割愛。  

## おまけ。 wait_processe
"BlockService.wait_processe"関数は処理中の待機画面を描画するための機能です。  
使用例は、Sample配下のtopleve_split_file.pyで使用してます。  
これは、実行中の関数にデコレートするためのデコレータ関数です。  
例えば、button widgetに割り当てている関数を実行すると、待機のダイアログが出力してくれます。  

![wait_process1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1557960/1879be77-bdeb-783d-9f54-1d260aa83edf.png)


使い方は、対象のframeを引数にセットし、デコレートするだけです。  
```python
@wait_processe(BlockService.root)
def _split_file(
```


# まとめ。  
今回はtkinterのWidgetを簡単に配置するためのtkblockというライブラリを作成しました。  
pythonでGUIのアプリを作る必要があり、Widgetの配置等で躓いている人の助けになればいいなと思い、公開しました。  
本格的なアプリを作るのであれば、これを使わない方がいいと思います。  
ちょっとしたアプリがあれば便利かなと思います。  
(ただ、テストをあまりできていないのが気がかりなところ...)  

ちなみに、そのコードはgithubのSampleフォルダにあるので、参考にしてみてもいいかもしれません。  

では、ここまで閲覧ありがとうございました。