Tkinterは透過色を苦手とするツールで、ウィジェットの透過色はサポートされてません(tk8.6現在)。そのため円形やキャラクターなど複雑な画像を使った場合、余った部分は背景色が付いてきてしまい思ったようにデザインできません。
そこで便利なのがCanvasウィジェットの各メソッドです。
今回は以前作成したキッチンタイマーを、Canvasウィジェットの各メソッドと画像を使っておしゃれに改造してみましょう。
目次
- ウィジェットのオプションを使って画像を表示させた場合
- canvas.create_image()で画像を表示
- canvas.create_text()で文字を表示
- canvas.delete()で表示したものを消す
- tag_bind()で処理を紐づけする
- 完成させてみる
ウィジェットのオプションを使って画像を表示させた場合
まずはボタンウィジェットのオプションを使って画像を表示させた場合です。
元の分ボタンの代わりに、上の丸形のボタンを配置してみます。背景は透過させてあるpng画像です。
# Minボタン self.min_button_img = tk.PhotoImage(file="./img/min_button.png") self.min_button = tk.Button(self.canvas_bg, text="分", image=self.min_button_img) self.min_button.place(x=50, y=110)
このように、画像の周囲が四角く灰色背景になってしまいます。
ボタンの隆起はオプションで消すことができますが、周囲に背景色が入ってしまうのは避けられません。つまりアプリ全体の背景に画像を設定していたとしても、このボタンの周囲には四角く背景色が入ってしまいます。
これでは矩形以外の画像をきれいに配置できません。
判定は四角形だとしても、せめてボタン周囲の背景色を透明にできればいいのですが…。
canvas.create_image()で画像を表示
そこで便利なのがCanvasウィジェットのcreate_imageメソッドです。
指定したCanvasウィジェットの中に画像を表示するためのメソッドで、新たにウィジェットを作成するわけではないため周囲に背景色が表示されることはなく、画像をそのまま表示できます。
# Minボタン self.min_button_img = tk.PhotoImage(file="./img/min_button.png") self.canvas_bg.create_image(50, 110, anchor=tk.NW, image=self.min_button_img, tag="min_button_img")
これなら丸や三角形、もっと複雑な画像も自由に使ってデザインすることができます。
実際にいくつかの画像を使ってタイマーのボタン部分をデザインしてみましょう。
import tkinter as tk # tk.Frameを継承したApplicationクラスを作成 class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) # ウィンドウの設定 master.title("キッチンタイマー") master.geometry("430x280") # タイマーの幅は430x280 # 実行内容 self.pack() self.create_widget() # create_widgetメソッドを定義 def create_widget(self): # 全体の親キャンバス self.canvas_bg = tk.Canvas(self.master, width=430, height=280, bg="#333") self.canvas_bg.pack() # Minボタン self.min_button_img = tk.PhotoImage(file="./img/min_button.png") self.canvas_bg.create_image(20, 210, anchor=tk.NW, image=self.min_button_img, tag="min_button_img") # 秒ボタン self.sec_button_img = tk.PhotoImage(file="./img/sec_button.png") self.canvas_bg.create_image(80, 210, anchor=tk.NW, image=self.sec_button_img, tag="sec_button_img") # リセットボタン self.reset_button_img = tk.PhotoImage(file="./img/reset_button.png") self.canvas_bg.create_image(140, 210, anchor=tk.NW, image=self.reset_button_img, tag="reset_button_img") # スタート/ストップボタン self.start_button_img = tk.PhotoImage(file="./img/start_button.png") self.canvas_bg.create_image(310, 160, anchor=tk.NW, image=self.start_button_img, tag="start_button_img") if __name__ == "__main__": root = tk.Tk() app = Application(master=root) app.mainloop()
随分良くなりました。
以前のコンクリート打ちっぱなしのようなデザインに比べればかなりマシです。
canvas.create_text()で文字を表示
画像だけでなく、文字もラベルウィジェットで表示してしまうと背景色が付いてしまいますが、こちらもCanvasウィジェットのcreate_textメソッドを使えば背景色を入れずに表示できます。
こちらのメソッドは元のタイマーの時点で使用しているのですが、改めて表示してみましょう。
import tkinter as tk import math # tk.Frameを継承したApplicationクラスを作成 class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) # ウィンドウの設定 master.title("キッチンタイマー") master.geometry("430x280") # タイマーの幅は430x280 # 変数定義 self.left_min = 0 self.left_sec = 0 # 実行内容 self.pack() self.create_widget() # create_widgetメソッドを定義 def create_widget(self): # 全体の親キャンバス self.canvas_bg = tk.Canvas(self.master, width=430, height=280, bg="#333") self.canvas_bg.pack() # タイマーの背景画像 self.timer_bg_img = tk.PhotoImage(file="./img/timer_bg.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(0, 0, anchor=tk.NW, image=self.timer_bg_img, tag="timer_bg") # 分の表示 self.canvas_bg.create_text(280, 80, text=str(math.floor(self.left_min)).zfill(2) + ":", font=("MSゴシック体", "80", "bold"), fill="#ffffff", tag="min_text", anchor="e") # 分を表示 # 秒の表示 self.canvas_bg.create_text(280, 80, text=str(math.floor(self.left_sec)).zfill(2), font=("MSゴシック体", "80", "bold"), fill="#ffffff", tag="sec_text", anchor="w") # 秒を表示 # Minボタン self.min_button_img = tk.PhotoImage(file="./img/min_button.png") self.canvas_bg.create_image(30, 210, anchor=tk.NW, image=self.min_button_img, tag="min_button_img") # 秒ボタン self.sec_button_img = tk.PhotoImage(file="./img/sec_button.png") self.canvas_bg.create_image(90, 210, anchor=tk.NW, image=self.sec_button_img, tag="sec_button_img") # リセットボタン self.reset_button_img = tk.PhotoImage(file="./img/reset_button.png") self.canvas_bg.create_image(150, 210, anchor=tk.NW, image=self.reset_button_img, tag="reset_button_img") # スタート/ストップボタン self.start_button_img = tk.PhotoImage(file="./img/start_button.png") self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_img, tag="start_button_img") if __name__ == "__main__": root = tk.Tk() app = Application(master=root) app.mainloop()
フォントの種類などこだわればもっと良くなる気がしますが、とりあえず「余計な背景色を入れずに自由にデザインする」という目的は達成できたのでこれで進めていきます。
canvas.delete()で表示したものを消す
キャンバスウィジェットのメソッドを使って作成した画像やテキストを削除する場合は、キャンバスウィジェットのdeleteメソッドを使います。
画像やテキストの作成(表示)時にオプション引数でtagを指定しておき、消したい時はdeleteメソッドでそのtagを指定して削除します。
self.timer_bg_img = tk.PhotoImage(file="./img/timer_bg.png") # 画像を読み込み # create_imageメソッドで画像を表示する際にtagオプションでtimer_bgというタグを付けておく self.canvas_bg.create_image(0, 0, anchor=tk.NW, image=self.timer_bg_img, tag="timer_bg") self.canvas_bg.delete("timer_bg") # timer_bgタグが付いたものを削除 # create_textメソッドで文字を表示する際にtagオプションでmin_textというタグを付けておく self.canvas_bg.create_text(280, 80, text=str(math.floor(self.left_min)).zfill(2) + ":", font=("MSゴシック体", "80", "bold"), fill="#ffffff", tag="min_text", anchor="e") self.canvas_bg.delete("min_text") # min_textタグが付いたものを削除
tag_bind()で処理を紐づけする
キャンバスウィジェットのメソッドで表示した画像やテキストはウィジェットではないため、ボタンウィジェットのようにcommandオプションで処理を紐づけることができません。
画像やテキストの作成(表示)時にオプション引数でtagを指定しておき、tag_bindメソッドでそのタグに処理を紐づけることができます。
書き方は次のとおりです。
キャンバス名.tag_bind("付けたタグ名", "トリガーとなる操作(イベント)", コールバック関数) # 例 self.canvas_bg.tag_bind("min_button_img", "<ButtonRelease-1>", self.minButton_released)
概ねbindメソッドと同じですので、bindメソッドがイマイチわからない方は次の記事をご参照ください。
【Tkinter】bindメソッドによるトリガーイベントとコールバック関数の紐づけ
完成させてみる
あとは元のキッチンタイマーを作ったときと同じように、処理を作り、各ボタンに紐づけて完成です。
今回作成したキッチンタイマーアプリのソースコードです。
import tkinter as tk import time import math import sys # tk.Frameを継承したApplicationクラスを作成 class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) # ウィンドウの設定 master.title("キッチンタイマー") master.geometry("430x280") # タイマーの幅は430x280 # 変数定義 self.timer_on = False # タイマーの状態 self.start_time = 0 # 開始時間 self.set_time = 0 # セット時間 self.elapsed_time = 0 # 経過時間 self.left_time = 0 # 残り時間 self.left_min = 0 self.left_sec = 0 self.after_id = 0 # after_id変数を定義 # 実行内容 self.pack() self.create_widget() # create_widgetメソッドを定義 def create_widget(self): # 全体の親キャンバス self.canvas_bg = tk.Canvas(self.master, width=430, height=280, bg="#333") self.canvas_bg.pack() # タイマーの背景画像 self.timer_bg_img = tk.PhotoImage(file="./img/timer_bg.png") self.canvas_bg.create_image(0, 0, anchor=tk.NW, image=self.timer_bg_img, tag="timer_bg") # タイマーのテキスト self.update_min_text() # 分の表示更新 self.update_sec_text() # 秒の表示更新 # Minボタン self.min_button_img = tk.PhotoImage(file="./img/min_button.png") self.canvas_bg.create_image(30, 210, anchor=tk.NW, image=self.min_button_img, tag="min_button_img") self.canvas_bg.tag_bind("min_button_img", "<Button-1>", self.min_button_clicked) self.canvas_bg.tag_bind("min_button_img", "<ButtonRelease-1>", self.min_button_released) # secボタン self.sec_button_img = tk.PhotoImage(file="./img/sec_button.png") self.canvas_bg.create_image(90, 210, anchor=tk.NW, image=self.sec_button_img, tag="sec_button_img") self.canvas_bg.tag_bind("sec_button_img", "<Button-1>", self.sec_button_clicked) self.canvas_bg.tag_bind("sec_button_img", "<ButtonRelease-1>", self.sec_button_released) # resetボタン self.reset_button_img = tk.PhotoImage(file="./img/reset_button.png") self.canvas_bg.create_image(150, 210, anchor=tk.NW, image=self.reset_button_img, tag="reset_button_img") self.canvas_bg.tag_bind("reset_button_img", "<Button-1>", self.reset_button_clicked) self.canvas_bg.tag_bind("reset_button_img", "<ButtonRelease-1>", self.reset_button_released) # start/stopボタン self.start_button_img = tk.PhotoImage(file="./img/start_button.png") self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_img, tag="start_button_img") self.canvas_bg.tag_bind("start_button_img", "<Button-1>", self.start_button_clicked) self.canvas_bg.tag_bind("start_button_img", "<ButtonRelease-1>", self.start_button_released) # 各ボタンが押された時の処理 # minボタンを押した時 def min_button_clicked(self, event): if self.left_min < 59: # 最大59分まで self.set_time += 60 # セット時間をプラス self.left_min += 1 self.update_min_text() # 分の表示更新 self.layer_adjustment() # レイヤーの前後調整 # 暗いボタンを表示 self.min_button_shadow_img = tk.PhotoImage(file="./img/min_button_shadow.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(30, 210, anchor=tk.NW, image=self.min_button_shadow_img, tag="min_button_shadow_img") # minボタンを離した時 def min_button_released(self, event): self.canvas_bg.delete("min_button_shadow_img") # 暗いボタンを消去 # secボタンを押した時 def sec_button_clicked(self, event): if self.left_sec < 59: # 最大59秒まで self.set_time += 1 # セット時間をプラス self.left_sec += 1 self.update_sec_text() # 秒の表示更新 self.layer_adjustment() # レイヤーの前後調整 # 暗いボタンを表示 self.sec_button_shadow_img = tk.PhotoImage(file="./img/sec_button_shadow.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(90, 210, anchor=tk.NW, image=self.sec_button_shadow_img, tag="sec_button_shadow_img") # secボタンを離した時 def sec_button_released(self, event): self.canvas_bg.delete("sec_button_shadow_img") # 暗いボタンを消去 # resetボタンを押した時 def reset_button_clicked(self, event): self.set_time = 0 # セット時間をリセット self.left_min = 0 # 残り時間(分)をリセット self.left_sec = 0 # 残り時間(秒)をリセット self.update_min_text() # 分の表示更新 self.update_sec_text() # 秒の表示更新 self.layer_adjustment() # レイヤーの前後調整 # 暗いボタンを表示 self.reset_button_shadow_img = tk.PhotoImage(file="./img/reset_button_shadow.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(150, 210, anchor=tk.NW, image=self.reset_button_shadow_img, tag="reset_button_shadow_img") # resetボタンを離した時 def reset_button_released(self, event): self.canvas_bg.delete("reset_button_shadow_img") # 暗いボタンを消去 # start/stopボタンを押した時 def start_button_clicked(self, event): if self.timer_on == False: # 暗いボタンを表示 self.start_button_shadow_img = tk.PhotoImage(file="./img/start_button_shadow.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_shadow_img, tag="start_button_shadow_img") elif self.timer_on == True: # 暗いボタンを表示 self.start_button_shadow_img = tk.PhotoImage(file="./img/stop_button_shadow.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_shadow_img, tag="start_button_shadow_img") # startボタンを離した時 def start_button_released(self, event): self.canvas_bg.delete("start_button_shadow_img") # 暗いボタンを消去 if self.set_time >= 1: if self.timer_on == False: # ボタン画像を変更 self.canvas_bg.delete("start_button_img") # 画像を消去 self.start_button_img = tk.PhotoImage(file="./img/stop_button.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_img, tag="start_button_img") self.canvas_bg.tag_bind("start_button_img", "<Button-1>", self.start_button_clicked) self.canvas_bg.tag_bind("start_button_img", "<ButtonRelease-1>", self.start_button_released) self.layer_hidden_buttons() # ストップ以外のボタンを隠す # タイマー開始 self.timer_on = True self.start_time =time.time() # 開始時間を代入 self.update_time() # updateTime関数を実行 elif self.timer_on == True: # タイマーストップ self.timer_on = False # ボタン画像を変更 self.canvas_bg.delete("start_button_img") # 画像を消去 self.start_button_img = tk.PhotoImage(file="./img/start_button.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_img, tag="start_button_img") self.canvas_bg.tag_bind("start_button_img", "<Button-1>", self.start_button_clicked) self.canvas_bg.tag_bind("start_button_img", "<ButtonRelease-1>", self.start_button_released) self.layer_adjustment() # レイヤーの前後調整 self.set_time = self.left_time app.after_cancel(self.after_id) # 表示時間の更新処理 def update_time(self): self.elapsed_time = time.time() - self.start_time # 経過時間を計算 self.left_time = self.set_time - self.elapsed_time # 残り時間を計算 self.left_min = self.left_time // 60 # 残り時間(分)を計算 self.left_sec = self.left_time % 60 # 残り時間(秒)を計算 self.update_min_text() # 分の表示更新 self.update_sec_text() # 秒の表示更新 if self.left_time > 0.1: self.after_id = self.after(10, self.update_time) else: self.timer_on = False self.set_time = self.left_time # ボタン画像を変更 self.canvas_bg.delete("start_button_img") # 画像を消去 self.start_button_img = tk.PhotoImage(file="./img/start_button.png") # イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。 self.canvas_bg.create_image(270, 160, anchor=tk.NW, image=self.start_button_img, tag="start_button_img") self.canvas_bg.tag_bind("start_button_img", "<Button-1>", self.start_button_clicked) self.canvas_bg.tag_bind("start_button_img", "<ButtonRelease-1>", self.start_button_released) self.layer_adjustment() # レイヤーの前後調整 # 個別の処理 # 分の表示更新 def update_min_text(self): self.canvas_bg.delete("min_text") # 表示時間(分)を消去 self.canvas_bg.create_text(280, 80, text=str(math.floor(self.left_min)).zfill(2) + ":", font=("MSゴシック体", "80", "bold"), fill="#ffffff", tag="min_text", anchor="e") # 分を表示 self.layer_hidden_buttons() # ストップ以外のボタンを隠す # 秒の表示更新 def update_sec_text(self): self.canvas_bg.delete("sec_text") # 表示時間(秒)を消去 self.canvas_bg.create_text(280, 80, text=str(math.floor(self.left_sec)).zfill(2), font=("MSゴシック体", "80", "bold"), fill="#ffffff", tag="sec_text", anchor="w") # 秒を表示 self.layer_hidden_buttons() # ストップ以外のボタンを隠す # レイヤーの前列後列調整 def layer_adjustment(self): self.canvas_bg.tag_raise("timer_bg") self.canvas_bg.tag_raise("min_text") self.canvas_bg.tag_raise("sec_text") self.canvas_bg.tag_raise("min_button_img") self.canvas_bg.tag_raise("sec_button_img") self.canvas_bg.tag_raise("reset_button_img") self.canvas_bg.tag_raise("start_button_img") self.canvas_bg.tag_raise("start_button_shadow_img") # ストップ以外のボタンを隠す def layer_hidden_buttons(self): self.canvas_bg.tag_raise("timer_bg") self.canvas_bg.tag_raise("min_text") self.canvas_bg.tag_raise("sec_text") self.canvas_bg.tag_raise("start_button_img") self.canvas_bg.tag_raise("start_button_shadow_img") if __name__ == "__main__": root = tk.Tk() app = Application(master=root) app.mainloop()
実際に動かしてみたものがこちらです。
おしゃれ勢からするとまだまだかもしれませんが、以前作ったものよりはだいぶ良くなったのではないでしょうか。
今回の作り方なら画像をそのまま表示に使えるので、もっとおしゃれにしたいという方は背景やディスプレイ、ボタンの画像にこだわればもっともっとおしゃれにすることができます。
シンプルなデザインであればプログラムで作ることもできますが、見た目をおしゃれにしたいのであればやはり画像編集ツールの方が自由度も高く便利です。