【Python】ttkbootstrapを使用してGUIアプリ作ってみた！

今回はttkbootstrapについて動画を見ながら勉強したのでその記録を残したいと思います。
まだまだソースコードの方は改善します。
追記※ソースはchatgptにレビューしてもらい改善しました。
ソースの説明も追記してます。

参考にした動画はこちらになります。
## 参考動画
<iframe width="560" height="315" src="https://www.youtube.com/embed/0tM-l_ZsxjU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## 実際に作成した成果物
<strong>①QRコード</strong>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/64860574-d051-fb9d-ae21-bb31b98e60e8.png)


<strong>②勤怠計算</strong>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/4cba8d14-829d-d478-b5f4-39bb27ff6260.png)

<strong>③Windowカラー変更</strong>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/80665655-1e47-bc9f-5092-69be79536cef.png)

<strong>③Tab One</strong>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/dacd4023-678d-1971-5b15-b828b3aa06c2.png)

<strong>③Tab Two</strong>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/58822157-f90e-5af1-8880-2be13a2878a4.png)

### 以下コードの説明
<font color="#FF0000">1. ウィンドウの作成と全体の設定</font>
ttkbootstrapを使用して、ダークなテーマのウィンドウを作成し、サイズは500x400に設定されています。タイトルは「Notebook」と設定されています。

<font color="#FF0000">2. QRコード生成タブ ("QRコード作成")（qr_create()関数）</font>
ユーザーが入力した文字列をQRコードに変換し、PNGファイルとして保存する機能があります。
「URL」と「QRname」の2つの入力ラベルと、それぞれの入力フィールドがあります。
「QRコード生成」というラベルの付いたボタンを押すと、入力された文字列に基づいてQRコードが生成されます。

<font color="#FF0000">3. 勤怠の計算タブ ("勤怠計算")（attendance()関数）</font>
ユーザーが出勤時間、退勤時間、休憩時間を入力することで、労働時間を計算できます。
日付選択にはttkbootstrap.dialogs.Queryboxが使用され、カレンダーから日付を選択できます。
「勤 怠 計 算」と「ク リ ア」と「日 付 選 択」という3つのボタンがあります。

<font color="#FF0000">4. チェックボックスタブ ("Windowカラー変更")（chk_button()関数）</font>
「OK?」と「No?」というテキストを持つ2つのチェックボックスがあります。
チェックされた状態で緑または黄色にスタイリングされます。

<font color="#FF0000">5. 曜日選択コンボボックスタブ ("Tab Two")（conbox()関数）</font>
曜日（月曜日から日曜日まで）を選択するためのコンボボックスがあります。

<font color="#FF0000">6. 日付取得タブ ("Tab Two")（date()関数）</font>
カレンダーから日付を選択できる日付入力ウィジェットがあります。

<font color="#FF0000">7. 言語選択メニュータブ ("Windowカラー変更")（menu_style()関数）</font>
メニューボックスに言語の選択肢（'python', 'java', 'C', 'PHP', 'Ruby', 'JavaScript', 'C++'）があります。
メニュー項目を選択すると、選択された言語が画面に表示されます。

<font color="#FF0000">8. Windowカラー変更 ("Windowカラー変更")（Window_change()関数)</font>
ウィンドウのテーマ（カラースキーム）を変更するためのコンボボックスがあります。
ユーザーがコンボボックスからテーマを選択すると、ウィンドウのテーマが変更されます。

## 詳細設計
### 概要
1. このアプリケーションは、Tkinterを使用してGUIを構築し、ttkbootstrapとqrcodeを利用して、QRコードの生成、勤怠計算、日記の記述、曜日の選択、メニューの選択などの機能を提供します。
### アーキテクチャ
1. クラスとモジュール
    1. `Window`: TkinterのWindowクラスを拡張したクラス。アプリケーションのメインウィンドウを表す。
    1. `Notebook`: ttkbootstrapのNotebookクラスを拡張したクラス。タブを表示し、各タブに対応する機能を提供。
    1. 各機能に対応する関数（qr_create(), attendance(), Tab_one(), Tab_Two(), Tab_Three()）
### モジュール設計
1. QRコード生成画面 (qr_create())
    1. `URL入力欄 (txt_1)、QRコード名入力欄 (txt_2)、QRコード生成ボタン (btn)`
1. 勤怠計算画面 (attendance())
    1. `出勤時間、退勤時間、休憩時間入力欄 (start_value, last_value, rest_value)`
    1. `労働時間表示欄 (total_value)`
    1. `勤怠計算ボタン、クリアボタン、日付選択ボタン (btn)`
    1. `日付表示欄 (my_date_label)`
1. タブ One (Tab_one())
    1. `日記入力欄 (my_text)`
    1. `クリックボタン (my_button)`
1. タブ Two (Tab_Two())
    1.`曜日選択コンボボックス (my_combo)`
    1. `日付選択ボタン (my_date_button)`
    1. `日付表示欄 (my_date_label)`
1. タブ Windowカラー変更 (Window_change())
    1. `チェックボックス (my_check)`
    1. `メニューボタン (my_menu)`
    1. `選択言語表示欄 (my_menu_label)`
    1. `コンボボックス(combo)`
### ユーザーインターフェース設計
1. 各機能ごとに必要なラベル、入力欄、ボタン、表示欄を配置
1. カラースタイルやボタンスタイルはttkbootstrapを使用して設定
### データフロー設計
各機能ごとに必要なデータの流れを示す。例えば、勤怠計算では出勤時間、退勤時間、休憩時間から労働時間を計算。

## 準備
必要なパッケージがあるので以下のコマンドを入力してください。
コマンド入力後にソースコードを実行してください。
```
pip install ttkbootstrap
pip install qrcode
```

## pythonファイルをexe化
pythonファイルをいつでも実行できるようにexe化すると便利になります。
まずは、以下のコマンドでPyinstallerをインストールします。
```
pip install pyinstaller
```

次に作業フォルダを作成します。今回は`C:\text_app`を作成します。
そして以下二つのコマンドを実行してください。

```
cd C:\text_app
pyinstaller ttkbootstrap_text_create.py --onefile --noconsole
```

## ソースコード
```python
import tkinter as tk
import ttkbootstrap as tb
import qrcode
from datetime import datetime
from ttkbootstrap import Button, Label, Checkbutton, Combobox, DateEntry, Menu, Menubutton, Notebook, Frame, Window, Style, dialogs

root = Window(themename="darkly")
root.title("Notebook")
root.geometry('500x400')

notebook = Notebook(root, bootstyle="light")
notebook.pack(pady=20)

def qr_create():
    """QRコード生成画面"""
    def btn_click():
        S = txt_1.get()
        qr_name = txt_2.get()
        img = qrcode.make(S)
        img.save(f'{qr_name}.png')

    tab_qr = Frame(notebook)
    notebook.add(tab_qr, text="QRコード作成")

    lbl_1 = Label(tab_qr, text='URL')
    lbl_1.place(x=70, y=50)

    lbl_2 = Label(tab_qr, text='QRname')
    lbl_2.place(x=70, y=100)

    txt_1 = tk.Entry(tab_qr, width=30)
    txt_1.place(x=130, y=50)

    txt_2 = tk.Entry(tab_qr, width=30)
    txt_2.place(x=130, y=100)

    btn = Button(tab_qr, text='QRコード生成', bootstyle="success", command=btn_click)
    btn.place(x=180, y=150)

def attendance():
    """勤怠の計算画面"""
    def btn_click():
        total_value.delete(0, tk.END)
        start = start_value.get()
        last = last_value.get()
        rest = rest_value.get()
        FMT = '%H:%M:%S'
        tdelta = datetime.strptime(last, FMT) - datetime.strptime(start, FMT)
        tdelta_cgange = str(tdelta)
        tdelta_1 = datetime.strptime(tdelta_cgange, FMT) - datetime.strptime(rest, FMT)
        total_value.insert(0, str(tdelta_1))

    def btn_clear():
        start_value.delete(0, tk.END)
        last_value.delete(0, tk.END)
        rest_value.delete(0, tk.END)
        total_value.delete(0, tk.END)

    def thing():
        cal = dialogs.Querybox()
        my_date_label.config(text=f'日付: {cal.get_date()}', bootstyle="inverse success")

    tab_attendance = Frame(notebook)
    notebook.add(tab_attendance, text="勤怠計算")

    my_date_label = Label(tab_attendance, text="日付: ")
    my_date_label.place(x=5, y=10)

    lbl_massage = Label(tab_attendance, text='勤務時間を入力して下さい。')
    lbl_massage.place(x=145, y=60)

    start_lbl = Label(tab_attendance, text='出勤時間')
    start_lbl.place(x=70, y=100)

    last_lbl = Label(tab_attendance, text='退勤時間')
    last_lbl.place(x=70, y=130)

    rest_lbl = Label(tab_attendance, text='休憩時間')
    rest_lbl.place(x=70, y=160)

    total_lbl = Label(tab_attendance, text='労働時間')
    total_lbl.place(x=70, y=190)

    start_value = tk.Entry(tab_attendance, width=30)
    start_value.place(x=145, y=100)

    last_value = tk.Entry(tab_attendance, width=30)
    last_value.place(x=145, y=130)

    rest_value = tk.Entry(tab_attendance, width=30)
    rest_value.place(x=145, y=160)

    total_value = tk.Entry(tab_attendance, width=30)
    total_value.place(x=145, y=190)

    btn = Button(tab_attendance, text='勤 怠 計 算', bootstyle="success", command=btn_click)
    btn.place(x=155, y=230)

    btn = Button(tab_attendance, text='ク リ ア', bootstyle="success", command=btn_clear)
    btn.place(x=155, y=270)

    btn = Button(tab_attendance, text='日 付 選 択', bootstyle="success", command=thing)
    btn.place(x=370, y=10)

def Tab_one():
    # Tab One
    tab1 = Frame(notebook)
    notebook.add(tab1, text="Tab One")

    my_label = Label(tab1, text="日記を書いてね", font=("Helvetica", 18))
    my_label.pack(pady=20)

    my_text = tk.Text(tab1, width=70, height=10)
    my_text.pack(pady=10, padx=10)

    my_button = Button(tab1, text="クリックして", bootstyle="danger outline")
    my_button.pack(pady=20)

def Tab_Two():
    """曜日選択のコンボボックス"""
    tab2 = Frame(notebook)
    notebook.add(tab2, text="Tab Two")

    days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    my_combo = Combobox(tab2, bootstyle="success", values=days)
    my_combo.pack(pady=20)
    my_combo.current(0)
    def date_get():
        my_date_label.config(text=f"日付取得: {my_date.entry.get()}")

    my_date = DateEntry(tab2, firstweekday=0, bootstyle="success")
    my_date.pack(pady=20)

    my_date_button = Button(tab2, text='日付取得', bootstyle="success", command=date_get)
    my_date_button.pack(pady=10)

    my_date_label = Label(tab2, text="日付取得: ")
    my_date_label.pack(pady=10)

def Window_change():
    tab3 = Frame(notebook)
    notebook.add(tab3, text="Windowカラー変更")

    val2 = tk.IntVar()
    my_check = Checkbutton(tab3, bootstyle="success, round-toggle",
                        text="OK?",
                        variable=val2,
                        onvalue=1,
                        offvalue=0)
    my_check.pack(pady=20)

    val3 = tk.IntVar()
    my_check = Checkbutton(tab3, bootstyle="warning, square-toggle",
                        text="No?",
                        variable=val3,
                        onvalue=1,
                        offvalue=0)
    my_check.pack(pady=10)
# メニューラベル(Tab Three)
    def stuff(x):
        my_menu_label.config(text=f'あなたが選んだ言語は:{x}', bootstyle="inverse success")
    
    my_menu_label = tb.Label(tab3, text="あなたが選んだ言語は:")
    my_menu_label.place(x=25, y=20)
    
    my_menu = Menubutton(tab3, bootstyle="success", text="メニュー")
    my_menu.pack(pady=20)

    inside_menu = Menu(my_menu)
    item_ver = tk.StringVar()
    
    for x in ['python', 'java', 'C', 'PHP', 'Ruby', 'JavaScript', 'C++']:
        inside_menu.add_radiobutton(label=x, variable=item_ver, command=lambda x = x:stuff(x))
    my_menu['menu'] = inside_menu
    
    # テーマを変更する関数
    def change_theme(event):
        global style
        theme = combo_var.get()
        style = Style(theme=theme)
    # テーマコンボボックスを作成
    themes = ['cosmo', 'flatly', 'journal', 'litera', 'lumen', 'minty', 'pulse', 'sandstone', 'united', 'yeti',
            'morph', 'simplex', 'cerculean','solar', 'superhero', 'darkly', 'cyborg', 'vapor']
    
    combo_var =tb.StringVar() # コンボボックスにデータをセットする。
    combo = tb.Combobox(tab3, values=themes, textvariable=combo_var)
    combo.set("Windowの色を選択")
    combo.place(x=310, y=10)
    combo.bind("<<ComboboxSelected>>", change_theme)  # コンボボックスの選択イベントにchange_theme関数をバインド

def main():
    qr_create()
    attendance()
    Window_change()
    Tab_one()
    Tab_Two()
    
if __name__ == '__main__':
    main()
    root.mainloop()
```
以上になります。