【Python】ttkbootstrapでGUIアプリ制作＆改修

# GUIアプリの概要
以前投稿したアプリを改修したのでそれを共有したいと思います。
以前投稿したアプリは以下の記事になります。

https://qiita.com/kensaka1228/items/3663fc18ed7c893feeee

ttkbootstarpのGoogle検索をしたら二番目にヒットします。
是非合わせてお読み下さい。

## 改良点
ソースコードをクラスで作成
QRコードの作成を自分で指定した場所に作成できるように改修
日記を書けるように改修

## アプリ概要
### QRコード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/2bf43cfc-8c0e-9e9a-38f2-5c5ae525fee4.png)

### 勤怠計算
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/cad93796-d453-48fb-bc1d-3d185af2f5b0.png)

### 日記
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/2247049f-9be5-214f-48af-5cec1f19468d.png)

### Tab Two
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/f2226da2-f475-2893-7269-dcd0b944295d.png)

### Windowsカラー変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/de51020a-4252-c4ef-9a99-6c949f3e572b.png)

## 基本設計書
### 1. 概要
このプログラムは、Tkinterを使用してGUIアプリケーションを作成するためのサンプルです。主な機能としては、QRコードの生成、勤怠計算、日記の保存、日付とウィンドウの色の変更があります。
ttkbootstarapで使用できる機能をまとめて実装しています。
### 2.ユースケース一覧
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/1495fcd6-c52f-4399-9c9e-bf99a80f1547.png)
### 3. 機能要件
①QRコードの生成
②勤怠計算
③日記の保存
④日付の取得
⑤ウィンドウの色の変更
### 3. クラス構造
- Application: GUIアプリケーションのメインクラス
  - qr_create(): QRコード生成タブの作成
  - attendance(): 勤怠計算タブの作成
  - Tab_one(): 日記タブの作成
  - Tab_Two(): タブ2の作成
  - Window_change(): ウィンドウの色変更タブの作成
  - main(): アプリケーションの実行を開始するメソッド
### 4. メソッド仕様
- qr_create():
  - QRコード生成ボタンをクリックすると、入力されたURLからQRコードを生成し、保存する
- attendance():
  - 勤怠計算ボタンをクリックすると、出勤時間、退勤時間、休憩時間から労働時間を計算する
- Tab_one():
  - 保存ボタンをクリックすると、入力された日記をテキストファイルに保存する
- Tab_Two():
  - 日付取得ボタンをクリックすると、選択された日付を表示する
- Window_change():
  - テーマを変更すると、ウィンドウの外観が変更される

### 5. 関数仕様書
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2759758/a40094b4-d3bd-4b9e-bdee-4f841c24ddc3.png)

### ソースコード
```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
from tkinter.filedialog import asksaveasfilename
from ttkbootstrap.dialogs import Messagebox
from tkinter import filedialog
from pathlib import PurePath

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 = Url_text.get()
        img = qrcode.make(S)
        path = asksaveasfilename(initialdir = dir)
        img.save(f'{path}.png')
        print('作成成功！')

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

    Url_lbl = Label(tab_qr, text='URL')
    Url_lbl.place(x=70, y=100)

    Url_text = tk.Entry(tab_qr, width=30)
    Url_text.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()
        # 時間形式を「hh:mm」にする。
        FMT = '%H:%M'
        tdelta = datetime.strptime(last, FMT) - datetime.strptime(start, FMT)
        str_tdelta = str(tdelta)
        # 文字列をスライスして(hh:mm)の形式に変換する。
        tdelta_change = str_tdelta[:5]
        # 勤怠時間を計算する。
        tdelta_1 = datetime.strptime(tdelta_change, FMT) - datetime.strptime(rest, FMT)
        str_tdelta_1 = str(tdelta_1)
        # 文字列をスライスして(hh:mm)の形式に変換する。
        ans_time = str_tdelta_1[:4]
        # 計算結果を表示する。
        total_value.insert(0, str(ans_time))

    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="日付: ", font=("", 10))
    my_date_label.place(x=5, y=10)

    lbl_massage = Label(tab_attendance, text='勤務時間を入力して下さい。', font=("", 15))
    lbl_massage.place(x=145, y=60)
    # フォントは10
    start_lbl = Label(tab_attendance, text='出勤時間', font=("", 10))
    start_lbl.place(x=70, y=100)

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

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

    total_lbl = Label(tab_attendance, text='労働時間', font=("", 10))
    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():
    def daiary():
        mb = Messagebox.okcancel("ファイルを保存しますか？")
        if mb == 'OK':
            s = my_text.get(0.,tk.END) # 入力した文字を最初から最後まで取得
            my_text.delete(0., tk.END) # 保存をしたら削除を行う。

            path = asksaveasfilename(defaultextension='.txt') # テキストファイルを保存する。
            if not path:
                return
            with open(path, 'w', encoding='utf-8') as f:
                f.write(s)
    # Tab One
    tab1 = Frame(notebook)
    notebook.add(tab1, text="日記")

    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", command=daiary)
    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()

```