はじめに
tkinter をいじり始めたついでに、もう少しいじってみることにした、ここでは、簡単なカスタマイズ事例を紹介している。
なお、当方の環境は以下の通り。
MacBook Pro 14"
Chip Apple M1 Pro
Memory 16GB
macOS Sequoia 15.1
Python 3.13.0
Tcl/Tk 9.00
matplotlib 3.9.2
(感想)
つい最近、何気なくbrew uodate
とbrew upgrade
を実行したら、tcl/tk
のバージョンが8.6
から9.0
にかわり、tkinter
が動かなくなった。もちろん Python を再インストールしたのだが、
(1) ttkthemes
のテーマが使えなくなる、
(2) Canvas
にmatplotlib
での作図を貼り付けるために必要な、FigureCanvasTkAgg
がインポートできない
という事象に見舞われた。
このため、ここでは、ttk
にビルトインされているテーマ(clam
)を使うことにした。
私のような素人は、バージョン管理に対する考え方が甘く、「なんでも最新を入れておけばいいや」という考えをもちがちであるが、注意が必要であることを思い知らされた。
個人で使用するプログラムであれば、最新版を入れてうまく動けば別に問題ないのだが、インポートできないとか、変なエラーが出るという事態への対応で余分な労力を割くリスク低減のためには、標準機能のみを使うことが有効であると感じた。
作例
作例の画面写真は以下の通り。カスタマイズの色のセンス(黄色背景とか)については、自分の見やすさを優先しているため、ご容赦願いたい。
上記写真では、4つの Python プログラムを同時実行しているが、以下のプログラムで実現している。なお、下記コードではプログラムが終了しないので、ターミナルからctl+c
を打って終了させる。
import subprocess
from tkinter import ttk
s = ttk.Style()
print(s.theme_names())
#('aqua', 'clam', 'alt', 'default', 'classic')
cmd0='python -m tkinter'
cmd1='python py_tk1.py'
cmd2='python py_tk2.py'
cmd3='python py_tk3.py'
subprocess.Popen(cmd0,shell=True)
subprocess.Popen(cmd1,shell=True)
subprocess.Popen(cmd2,shell=True)
subprocess.Popen(cmd3,shell=True)
写真の中身は以下の通り。
- 1番左(
tkinter
):tcl/tk
のバージョンを表示する - 左から2番目(
py_tk1.py
):テーマを何も指定しない場合。カスタマイズもなし。aqua
が適用されている模様。 - 左から3番目(
py_tk2.py
):テーマにclam
を指定。カスタマイズなし。 - 1番右(
py_tk3.py
):clam
を自分でカスタマイズしたもの
上記プログラム py_tk1.py
のコードは以下の通り。main()
関数の3〜6行目(コメントアウトしているところ)をいじれば、py_tk2.py
となる。なお、やっていることは他愛もないことなので、説明は省略する。
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import numpy as np
def end_q():
quit()
def click_1(tab1):
messagebox.showinfo('','Button-1 clicked')
tab1.focus_set()
def click_2(tab1):
messagebox.showinfo('','Button-2 clicked')
tab1.focus_set()
def main():
root =tk.Tk()
root.resizable(False,False)
root.title('py_tk1.py')
#root.title('py_tk2.py')
#style=ttk.Style()
#style.theme_use('clam')
frame0=ttk.Frame(root)
label0=ttk.Label(frame0,text='Test frame')
label0.pack()
nb = ttk.Notebook(frame0)
tab1 = ttk.Frame(nb)
tab2 = ttk.Frame(nb)
tab3 = ttk.Frame(nb)
nb.add(tab1, text='Tab1')
nb.add(tab2, text='Tab2')
nb.add(tab3, text='Tab3')
nb.pack(fill=tk.X)
frame1=ttk.Frame(tab1)
label_t1a=ttk.Label(frame1,text='label_t1a')
label_t1b=ttk.Label(frame1,text='label_t1b')
entry_t1a=ttk.Entry(frame1,justify=tk.CENTER,width=10)
entry_t1b=ttk.Entry(frame1,justify=tk.CENTER,width=10)
entry_t1a.insert(tk.END,'entry_t1a')
entry_t1b.insert(tk.END,'entry_t1b')
button_t1a=ttk.Button(frame1,text='button_t1a',command=lambda:click_1(tab1))
button_t1b=ttk.Button(frame1,text='button_t1b',command=lambda:click_2(tab1))
label_t1a.grid(row=0,column=0,pady=(3,3))
entry_t1a.grid(row=0,column=1,pady=(3,3))
label_t1b.grid(row=1,column=0,pady=(3,3))
entry_t1b.grid(row=1,column=1,pady=(3,3))
button_t1a.grid(row=2,column=0,columnspan=2,pady=(3,3))
button_t1b.grid(row=3,column=0,columnspan=2,pady=(3,3))
frame1.pack(anchor=tk.CENTER)
label_t2a=ttk.Label(tab2,text='This is Tab2')
label_t2a.pack()
label_t3a=ttk.Label(tab3,text='This is Tab3')
label_t3a.pack()
frame_lf=ttk.LabelFrame(frame0,text='LabelFrame',relief=tk.FLAT)
n,m=4,3
lrc=np.empty((n,m),dtype=object)
for i in range(0,n):
for j in range(0,m):
s='lrc[{0},{1}]'.format(i,j)
lrc[i,j]=ttk.Label(frame_lf,text=s,borderwidth=1,relief=tk.SOLID,anchor=tk.CENTER,width=8)
lrc[i,j].grid(row=i,column=j)
frame_lf.pack()
frame0.pack()
q_button=ttk.Button(root,text='Quit',command=end_q)
q_button.pack()
root.mainloop()
if __name__ == '__main__': main()
カスタマイズの説明
見た目をカスタマイズしたpy_tk3.py
について少し解説しておく。
テーマの指定
まず、テーマclam
を指定する。
style=ttk.Style()
style.theme_use('clam')
全体のフォントと色
wedgit
全体のフォントと背景色・前面色(フォントの色)を変更する。Labelframe
のLabel
だけは太字(bold)とする。
fontn='Caribri 14'
fontb='Calibri 14 bold'
bg_base,fg_base='#ffff00','#393939'
style.configure('.',font=(fontn))
style.configure('.',background=bg_base,foreground=fg_base)
style.configure('TLabelframe.Label',font=(fontb))
タブの装飾
タブに表示する文字のフォントを太字とし、非選択時の背景色・前面色をconfigure
で指定する。また選択時の背景色・前面色をmap
を用いて指定する。
style.configure('TNotebook.Tab',
font=(fontb),
background='#dddddd',
foreground='#777777')
style.map('TNotebook.Tab',
foreground=[('selected',fg_base)],
background=[('selected',bg_base)])
ボタンの装飾
ボタンの非選択時の背景色・前面色、見た目(relief)をconfigure
で指定する。また選択時(マウスホバー時及び処理実行時)の背景色・前面色をmap
を用いて指定する。
style.configure('TButton',
background='#eeeeee',
foreground='#393939',
relief=tk.RAISED)
style.map('TButton',
foreground=[('active','#0000ff')],
background=[('active','#00ffff')])
Entryのフォント
なお、下記のように、Entrey
はwedgit
を定義する段階でフォントを指定する必要がある。
entry_t1a=ttk.Entry(frame1,justify=tk.CENTER,width=10,font=(fontn))
カスタマイズしたコード
コード全文は以下の通り。
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import numpy as np
def end_q():
quit()
def click_1(tab1):
messagebox.showinfo('','Button-1 clicked')
tab1.focus_set()
def click_2(tab1):
messagebox.showinfo('','Button-2 clicked')
tab1.focus_set()
def main():
root =tk.Tk()
root.resizable(False,False)
root.title('py_tk3.py')
style=ttk.Style()
style.theme_use('clam')
fontn='Caribri 14'
fontb='Calibri 14 bold'
bg_base,fg_base='#ffff00','#393939'
style.configure('.',font=(fontn))
style.configure('.',background=bg_base,foreground=fg_base)
style.configure('TLabelframe.Label',font=(fontb))
style.configure('TNotebook.Tab',
font=(fontb),
background='#dddddd',
foreground='#777777')
style.map('TNotebook.Tab',
foreground=[('selected',fg_base)],
background=[('selected',bg_base)])
style.configure('TButton',
background='#eeeeee',
foreground='#393939',
relief=tk.RAISED)
style.map('TButton',
foreground=[('active','#0000ff')],
background=[('active','#00ffff')])
frame0=ttk.Frame(root)
label0=ttk.Label(frame0,text='Test frame',font=(fontb))
label0.pack()
nb = ttk.Notebook(frame0)
tab1 = ttk.Frame(nb)
tab2 = ttk.Frame(nb)
tab3 = ttk.Frame(nb)
nb.add(tab1, text='Tab1')
nb.add(tab2, text='Tab2')
nb.add(tab3, text='Tab3')
nb.pack(fill=tk.X)
frame1=ttk.Frame(tab1)
label_t1a=ttk.Label(frame1,text='label_t1a')
label_t1b=ttk.Label(frame1,text='label_t1b')
entry_t1a=ttk.Entry(frame1,justify=tk.CENTER,width=10,font=(fontn))
entry_t1b=ttk.Entry(frame1,justify=tk.CENTER,width=10,font=(fontn))
entry_t1a.insert(tk.END,'entry_t1a')
entry_t1b.insert(tk.END,'entry_t1b')
button_t1a=ttk.Button(frame1,text='button_t1a',command=lambda:click_1(tab1))
button_t1b=ttk.Button(frame1,text='button_t1b',command=lambda:click_2(tab1))
label_t1a.grid(row=0,column=0,pady=(3,3))
entry_t1a.grid(row=0,column=1,pady=(3,3))
label_t1b.grid(row=1,column=0,pady=(3,3))
entry_t1b.grid(row=1,column=1,pady=(3,3))
button_t1a.grid(row=2,column=0,columnspan=2,pady=(3,3))
button_t1b.grid(row=3,column=0,columnspan=2,pady=(3,3))
frame1.pack(anchor=tk.CENTER)
label_t2a=ttk.Label(tab2,text='This is Tab2')
label_t2a.pack()
label_t3a=ttk.Label(tab3,text='This is Tab3')
label_t3a.pack()
frame_lf=ttk.LabelFrame(frame0,text='LabelFrame',relief=tk.FLAT)
n,m=4,3
lrc=np.empty((n,m),dtype=object)
for i in range(0,n):
for j in range(0,m):
s='lrc[{0},{1}]'.format(i,j)
lrc[i,j]=ttk.Label(frame_lf,text=s,borderwidth=1,relief=tk.SOLID,anchor=tk.CENTER,width=8)
lrc[i,j].grid(row=i,column=j)
frame_lf.pack()
frame0.pack()
q_button=ttk.Button(root,text='Quit',command=end_q)
q_button.pack()
root.mainloop()
if __name__ == '__main__': main()
以 上