naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

tkinterで、簡易電卓を作るシリーズ③見た目を整える

プログラミング関連 Python Tkinter 約59日前
2017年8月20日13:45
今回は、見た目をカスタマイズしていきます。

Tk8.5で、Tk のテーマ付きウィジェットであるTtkが導入されました。
今まで from tkinter import ttk としていましたが、このttkでアクセスできるのが上記のテーマ付きウィジェットです。
これらのウィジェットを使うことでルック&フィールが今風になり、テーマやスタイルといった機能を利用することで見た目を便利にカスタマイズすることができます。

まず、以下のようなシンプルなプログラムを作ります。
from tkinter import *
from tkinter import ttk

root = Tk()

frame = ttk.Frame(root)
frame.grid(column=0, row=0, sticky=(N, S, E, W))

# 1行目
ttk.Label(frame, text='姓').grid(column=0, row=0)
ttk.Entry(frame).grid(column=1, row=0)

# 2行目
ttk.Label(frame, text='名').grid(column=0, row=1)
ttk.Entry(frame).grid(column=1, row=1)

# 3行目
ttk.Label(frame, text='国').grid(column=0, row=2)
country_combo = ttk.Combobox(frame)
country_combo['values'] = ('日本', 'アメリカ', '中国', 'イギリス', 'ロシア')
country_combo['state'] = 'readonly'
country_combo.current(0)
country_combo.grid(column=1, row=2)

# frame内の全てのウィジェットに下記padding
for child in frame.winfo_children():
    child.grid_configure(padx=5, pady=5)

root.mainloop()




これを実行すると、以下のような画面です。



これにテーマを設定していきましょう。
その前に、現在のテーマは以下のように確認できます。
style = ttk.Style()
current_theme = style.theme_use()
print(current_theme)


現在のテーマはdefaultのようです。
default


利用可能な全てのテーマを取得してみましょう。
style = ttk.Style()
all_theme = style.theme_names()
print(all_theme)


これは各プラットフォームによって、増えたり減ったりします。
('clam', 'alt', 'default', 'classic')



サンプルプログラムに、テーマを設定してみましょう。
以下のように追記し、classicテーマを利用してみます。
style = ttk.Style()
style.theme_use('classic')

root.mainloop()


すると、以下のようになりました。


前に比べると、ちょっと違いますね。サンプルプログラムがシンプルなせいで、ちょっと実感が少なめです。



試しに、サンプルプログラムを拡張し、テーマを自在に切り替えるようにしましょう。
from tkinter import *
from tkinter import ttk


def change_theme(event):
    """テーマを変更する."""
    selected_theme = combo.get()
    style.theme_use(selected_theme)


root = Tk()

# 全てのテーマ
style = ttk.Style()
all_themes = style.theme_names()

frame = ttk.Frame(root)
frame.grid(column=0, row=0, sticky=(N, S, E, W))

# 1行目
ttk.Label(frame, text='姓').grid(column=0, row=0)
ttk.Entry(frame).grid(column=1, row=0)

# 2行目
ttk.Label(frame, text='名').grid(column=0, row=1)
ttk.Entry(frame).grid(column=1, row=1)

# 3行目
ttk.Label(frame, text='テーマ').grid(column=0, row=2)
combo = ttk.Combobox(frame)
combo['values'] = all_themes
combo['state'] = 'readonly'
combo.grid(column=1, row=2)
combo.bind('<<ComboboxSelected>>', change_theme)

# frame内の全てのウィジェットに下記padding
for child in frame.winfo_children():
    child.grid_configure(padx=5, pady=5)

root.mainloop()


ちゃんと変化していますね。






①で、以下のようなことを書きました。


やろうと思えば、Tk()で作成したトップレベルのウィジェットに直接ボタンを配置することもできます。
実際、ちょっとした小物ならこれで十分なこともあります。
しかし、このトップレベルのウィジェットはttkのテーマ、スタイル機能をサポートしていません。
なので、ある程度キレイな見た目にしようと思うと、ttk.Frame などにボタンなどを配置することになります。
これがどういう影響があるかは、③で少し触れます。



Tk()で作られるトップレベルウィジェットがテーマをサポートしないというのは、以下のようにすると分かりやすいです。
Frameの作成部分を変更します。各ウィジェットは、ttk.Frameではなくroot = Tk()に直接配置されるようになります。
#frame = ttk.Frame(root)
#frame.grid(column=0, row=0, sticky=(N, S, E, W))
frame = root


この状態で起動し、テーマを変えてみましょう。ハッキリと分かるのは「clam」なんかですね。
本来clamはフレーム自体の色なんかも少しだけ変えるのですが、frame=root にすると色が変わりません。

上がrootに直接置いた場合、下がttk.Frameを介して置いた場合



テーマだけでなく、後述するスタイルを使うことも当然できません。
基本的には、ttk.Frameを介してウィジェットを配置していくのがベストでしょう。


話を戻して、テーマはユーザーインターフェイス全体の外観を制御しますが、個別のウィジェットの見た目を変更することはできません。そのような場合は、Styleを利用します。

次のサンプルプログラムは、デフォルトのラベルをカスタマイズしている例です。
'TLabel'は、ttk.Labelのデフォルトのスタイルで、それを上書きしている形になります。
from tkinter import *
from tkinter import ttk

root = Tk()

# スタイルの作成
style = ttk.Style()
style.configure(
    'TLabel', font=('Helvetica', 20),
    background='black', foreground='white'
)

frame = ttk.Frame(root)
frame.grid(column=0, row=0, sticky=(N, S, E, W))

# 1行目
ttk.Label(frame, text='姓').grid(column=0, row=0)
ttk.Entry(frame).grid(column=1, row=0)

# 2行目
ttk.Label(frame, text='名').grid(column=0, row=1)
ttk.Entry(frame).grid(column=1, row=1)

# frame内の全てのウィジェットに下記padding
for child in frame.winfo_children():
    child.grid_configure(padx=5, pady=5)

root.mainloop()



ちゃんと全てのラベルが変更されました。



自分で独自のスタイルを定義することもできます。
# スタイルの作成
style = ttk.Style()
style.configure(
    'BW.TLabel', font=('Helvetica', 20),  # BlackWhite.TLabel の略
    background='black', foreground='white'
)
...
...

# style=で、定義したものを設定
ttk.Label(frame, text='姓', style='BW.TLabel').grid(column=0, row=0)
ttk.Entry(frame).grid(column=1, row=0)



実行すると、以下のようになります。設定したラベルだけかわっていますね。



早速、前回作ったプログラムにスタイルを設定しましょう。
from tkinter import *
from tkinter import ttk
 
 
# 2次元配列のとおりに、gridでレイアウトを作成する
LAYOUT = [
    ['7', '8', '9', '/'],
    ['4', '5', '6', '*'],
    ['1', '2', '3', '-'],
    ['0', 'C', '=', '+'],
]
 
 
class CalcApp(ttk.Frame):
    """電卓アプリ."""
 
    def __init__(self, master=None):
        super().__init__(master)
        self.create_style()
        self.create_widgets()

    def create_style(self):
        """ボタン、ラベルのスタイルを変更."""
        style = ttk.Style()

        # ラベルのスタイルを上書き
        style.configure(
            'TLabel', font=('Helvetica', 20),
            background='black', foreground='white',
        )
        # ボタンのスタイルを上書き
        style.configure('TButton', font=('Helvetica', 20))

    def create_widgets(self):
        """ウィジェットの作成."""
        self.display_var = StringVar()
        self.display_var.set('0')  # 初期値を0にする
 
        dispay_label = ttk.Label(self, textvariable=self.display_var)
        dispay_label.grid(column=0, row=0, columnspan=4, sticky=(N, S, E, W))
 
        # レイアウトの作成
        for y, row in enumerate(LAYOUT, 1):
            for x, char in enumerate(row):
                button = ttk.Button(self, text=char)
                button.grid(column=x, row=y, sticky=(N, S, E, W))
                button.bind('<Button-1>', self.calc)
        self.grid(column=0, row=0, sticky=(N, S, E, W))
 
        # 横の引き伸ばし設定
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        self.columnconfigure(2, weight=1)
        self.columnconfigure(3, weight=1)
 
        # 縦の引き伸ばし設定。0番目の結果表示欄だけ、元の大きさのまま
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.rowconfigure(2, weight=1)
        self.rowconfigure(3, weight=1)
        self.rowconfigure(4, weight=1)
 
        # ウィンドウ自体の引き伸ばし設定
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(0, weight=1)
 
    def calc(self, event):
        char = event.widget['text']
        self.display_var.set(char)
 
 
def main():
    root = Tk()
    root.title('簡単電卓')
    CalcApp(root)
    root.mainloop()
 
 
if __name__ == '__main__':
    main()
 



変更箇所は、以下の部分です。
create_styleメソッドを定義し、__init__内で呼び出すようにしました。
スタイルの設定自体は、非常にシンプルです。
    def __init__(self, master=None):
        super().__init__(master)
        self.create_style()
        self.create_widgets()

    def create_style(self):
        """ボタン、ラベルのスタイルを変更."""
        style = ttk.Style()

        # ラベルのスタイルを上書き
        style.configure(
            'TLabel', font=('Helvetica', 20),
            background='black', foreground='white',
        )
        # ボタンのスタイルを上書き
        style.configure('TButton', font=('Helvetica', 20))



実行すると、以下のようになりました。見た目はこれで完成です!