Python: Tkinterを用いたMac用appの作成（タブ・表・TeX数式）（2024.11.03）

## はじめに
Tkinter の使い方に慣れるため、もう一つGUIアプリを作ってみた、テクニック的には以下のものを織り込んだ。

+ タブを使う
+ 表を使う
+ matplotlib 経由で TeX 数式を canvas に描画
+ 計算結果は messagebox に表示

参考にしたサイトは以下の通り。

（タブの使い方）
【Tkinter】【コピペ可】ボタン、タブ、表の使い方をざっくり説明
[https://kazuwo-blog.com/python-tkinter/#index_id10](https://kazuwo-blog.com/python-tkinter/#index_id10)

（matplotlibのcanvasへの埋め込み）
Embedding in Tk
[https://matplotlib.org/3.1.0/gallery/user_interfaces/embedding_in_tk_sgskip.html](https://matplotlib.org/3.1.0/gallery/user_interfaces/embedding_in_tk_sgskip.html)

（matplotlibで余白の調整）
【python】matplotlibで図の余白を調整する
[https://hayataka2049.hatenablog.jp/entry/2018/10/11/030103](https://hayataka2049.hatenablog.jp/entry/2018/10/11/030103)

（表の作成）
⑨ 表（テーブル）の作成【python tkinter sqlite3で家計簿を作る】
[https://memopy.hatenadiary.jp/entry/2017/06/02/230723](https://memopy.hatenadiary.jp/entry/2017/06/02/230723)



## 動作中写真

２つのタブ（Concrete と Rock）のうち、Concrete での計算実行結果画面。
何に出力するか考えたが、このアプリでは計算結果の数値が確認できればいいので、messagebox に出力することとした。
タブの中に入力用の参考として小さな表を表示している。

![IMG_0111.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/c165709c-161f-5886-d9b2-346cff77ae02.jpeg)

使っている計算式を表示したもの。普通に matplotlib での描画コマンドを打ち、canvas に表示させているもの。これを表示すると、メイン画面の左上の x を押しても、画面は閉じるが Python が終了しない事態に見舞われたため、Quit により Python そのものを終了させるようにした。

![IMG_0115.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/1e0e2dcc-dca6-9df9-1810-cb438d57026c.jpeg)

２つのタブ（Concrete と Rock）のうち、Rock での計算実行結果画面。
ここでもタブの中に入力用の参考として小さな表を表示している。
matplotlib での描画後、messagebox に表示されるアイコンが変わってしまっている。icon='info' で明示してもアイコンが変化してしまう。本質に影響はないので未解決のまま。

![IMG_0116.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/6cf4888f-0b10-8458-2210-3078e3eb1efe.jpeg)



## コード
```python
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


def end_q():
    quit()


def plot():
    fsz=11
    xmin,xmax,dx=0, 5, 1
    ymin,ymax,dy=0, 10, 1
    fig = plt.figure(figsize = (3,3),dpi = 100)
    plt.rcParams['font.size']=fsz
    plt.rcParams['font.family']='sans-serif'
    plt.subplots_adjust(left=0.1,right=0.95,bottom=0.1,top=0.95)
    plt.xlim([xmin,xmax])
    plt.ylim([ymin,ymax])
    plt.axis('off')
    ss=[]
    ss.append(r'$\sigma_c$: compressive strength (positive)')
    ss.append(r'$\sigma_t$: tensile strength (positive)')
    ss.append(r'$\phi$: internal friction angle')
    ss.append(r'$c$: cohesion')
    ss.append('For Concrete')
    ss.append(r'$\sigma_t=0.23\cdot (\sigma_c)^{2/3}$')
    ss.append(r'$\phi=\sin^{-1}\left(\frac{\sigma_c-\sigma_t}{\sigma_c+\sigma_t}\right)$')
    ss.append(r'$c=\frac{\sigma_c\cdot \sigma_t}{\cos\phi\cdot (\sigma_c+\sigma_t)}$')
    ss.append('For Rock')
    ss.append(r'$\sigma_c=\frac{2\cdot c\cdot \cos\phi}{1-\sin\phi}$')
    ss.append(r'$\sigma_t=\frac{2\cdot c\cdot \cos\phi}{1+\sin\phi}$')
    xy=np.array([
        [0,9.5-0*0.6],
        [0,9.5-1*0.6],
        [0,9.5-2*0.6],
        [0,9.5-3*0.6],
        [0,6.5-0*1.0], # For Concrete
        [1,6.5-1*1.0],
        [1,6.5-2*1.0],
        [1,6.5-3*1.0],
        [0,6.5-4*1.0], # For Rock
        [1,6.5-5*1.0],
        [1,6.5-6*1.0]
    ])
    for i in range(len(ss)):
        plt.text(xy[i,0],xy[i,1],ss[i],ha='left',va='center',fontsize=fsz)
    return fig


def show_eqs():
    sub_win=tk.Toplevel()
    sub_win.title('Equations')
    fig=plot()
    canvas = FigureCanvasTkAgg(fig,master=sub_win)
    canvas.draw() 
    canvas.get_tk_widget().pack()
    sub_win.mainloop()


def click_t1():
    global entry_t1a
    # Concrete
    sigc=float(entry_t1a.get()) # compressive strength
    sigt=0.23*sigc**(2/3) # tensile strength
    sn=(sigc-sigt)/(sigc+sigt) # sin(phi)
    cs=np.sqrt(1-sn**2) # cos(phi)
    phi=np.degrees(np.arcsin(sn)) # internal friction angle
    cc=(1+sn)/2/cs*sigt # cohesion
    s0='Concrete'
    s1='sigc (MPa)={0:6.3f}'.format(sigc)
    s2='sigt (MPa)={0:6.3f}'.format(sigt)
    s3='cc   (MPa)={0:6.3f}'.format(cc)
    s4='phi  (deg)={0:6.3f}'.format(phi)
    ss=s0+'\n'+s1+'\n'+s2+'\n'+s3+'\n'+s4
    if messagebox.showinfo('Concrete',ss)=='ok': entry_t1a.focus_set()


def click_t2():
    global entry_t2a,entry_t2b
    # Rock
    cc =float(entry_t2a.get()) # cohesion
    phi=float(entry_t2b.get()) # internal friction angle
    sigc=2*cc*np.cos(np.radians(phi))/(1-np.sin(np.radians(phi)))
    sigt=2*cc*np.cos(np.radians(phi))/(1+np.sin(np.radians(phi)))
    s0='Rock'
    s1='cc   (MPa)={0:6.3f}'.format(cc)
    s2='phi  (deg)={0:6.3f}'.format(phi)
    s3='sigc (MPa)={0:6.3f}'.format(sigc)
    s4='sigt (MPa)={0:6.3f}'.format(sigt)
    ss=s0+'\n'+s1+'\n'+s2+'\n'+s3+'\n'+s4
    if messagebox.showinfo('Rock',ss)=='ok': entry_t2a.focus_set()


def ini_t1():
    global entry_t1a
    entry_t1a.delete(0,tk.END)
    entry_t1a.insert(tk.END,'25')


def ini_t2():
    global entry_t2a,entry_t2b
    entry_t2a.delete(0,tk.END)
    entry_t2b.delete(0,tk.END)
    entry_t2a.insert(tk.END,'3.506')
    entry_t2b.insert(tk.END,'58.666')


def main():
    global entry_t1a,entry_t2a,entry_t2b
    root =tk.Tk()
    root.title('Shear strength')

    button_eqs=tk.Button(root,text='Show Equations',command=show_eqs)
    button_eqs.pack()

    nb = ttk.Notebook(root)
    tab1 = tk.Frame(nb)
    tab2 = tk.Frame(nb)
    nb.add(tab1, text='Concrete')
    nb.add(tab2, text='Rock')
    nb.pack()

    label_t1a=tk.Label(tab1,text='sigc (MPa)')
    entry_t1a=tk.Entry(tab1,justify=tk.CENTER,width=7,bg='#000080')
    button_t1a=tk.Button(tab1,text='Calculcation',command=click_t1)
    button_t1b=tk.Button(tab1,text='(Initial value)',command=ini_t1)
    label_t1a.grid(row=0,column=0)
    entry_t1a.grid(row=0,column=1)
    button_t1a.grid(row=1,column=0,columnspan=2)
    button_t1b.grid(row=2,column=0,columnspan=2)
    ini_t1() # initial value

    label_t1c=tk.Label(tab1,text='sigc = cylinder strehgth')
    label_t1c.grid(row=4,column=0,columnspan=2)
    s1='Cube (MPa)'
    s2='Cylinder (MPa)'
    bs=np.array([15,20,25,30,35,40,45,50]) # BS
    en=np.array([12,16,20,25,29,32,35,40]) # EN
    tree = ttk.Treeview(tab1)
    tree['columns']=(1,2)
    tree['show']='headings'
    tree['height']=len(bs)
    tree.column(1,anchor='center',width=80)
    tree.column(2,anchor='center',width=80)
    tree.heading(1,text=s1,anchor='center')
    tree.heading(2,text=s2,anchor='center')
    for i in range(0,len(bs)):
        tree.insert(parent='',index='end',values=(bs[i],en[i]))
    tree.grid(row=5,column=0,columnspan=2)


    label_t2a=tk.Label(tab2,text='cc (MPa)')
    entry_t2a=tk.Entry(tab2,justify=tk.CENTER,width=7,bg='#000080')
    label_t2b=tk.Label(tab2,text='phi (deg)')
    entry_t2b=tk.Entry(tab2,justify=tk.CENTER,width=7,bg='#000080')
    button_t2a=tk.Button(tab2,text='Calculation',command=click_t2)
    button_t2b=tk.Button(tab2,text='(Initial value)',command=ini_t2)
    label_t2a.grid(row=0,column=0)
    entry_t2a.grid(row=0,column=1)
    label_t2b.grid(row=1,column=0)
    entry_t2b.grid(row=1,column=1)
    button_t2a.grid(row=2,column=0,columnspan=2)
    button_t2b.grid(row=3,column=0,columnspan=2)
    ini_t2() # initial value

    label_t2c=tk.Label(tab2,text='Rock Shear Strength\n(Upper limit)')
    label_t2c.grid(row=4,column=0,columnspan=2)
    s1='Class'
    s2='c (MPa)'
    s3='phi (deg)'
    rock=['B','CH','CM','CL','D']
    cc_r=np.array([6.0,4.0,2.4,1.0,0.4])
    ph_r=np.array([65,55,45,10,30])
    tree = ttk.Treeview(tab2)
    tree['columns']=(1,2,3)
    tree['show']='headings'
    tree['height']=len(rock)
    tree.column(1,anchor='center',width=50)
    tree.column(2,anchor='center',width=50)
    tree.column(3,anchor='center',width=50)
    tree.heading(1,text=s1,anchor='center')
    tree.heading(2,text=s2,anchor='center')
    tree.heading(3,text=s3,anchor='center')
    for i in range(0,len(rock)):
        tree.insert(parent='',index='end',values=(rock[i],cc_r[i],ph_r[i]))
    tree.grid(row=5,column=0,columnspan=2)


    q_button=tk.Button(root,text='Quit',command=end_q)
    q_button.pack()

    root.mainloop()


if __name__ == '__main__': main()
```

以　上