tkinter の アイコン をバイナリ(exeファイル)に埋め込む方法(だいたい コピペ でできる)
tkinterのデフォルトのアイコンは羽のようなアイコンですよね。
これを自分で変更することができます。
これ自体はroot.iconbitmapに指定すればいいだけなのですが、pyinstallerなどでexe化したときに、アイコンファイル(.ico)も同じディレクトリにおいておかないとエラーがでてしまいます。
これを回避するためにexeの中にアイコンファイルを埋め込んでしまって、exe単体で動かせるようにしよう!というのこの記事の趣旨です。
ポイントを最初に挙げておくと、以下の2点が重要です。
1、ソースコード内で使用するアイコンのパスをsys._MEIPASS(※1)を通して取得する。
2、pyinstallerコマンドの時に「--add-data」を使用し、アイコンファイル(.ico)を埋め込む
※1 exe起動時に一時的に展開される場所を取得するためのメソッド
この二点ができていれば、簡単にアイコンをバイナリに埋め込むことができます。
他のサイトなどでは「specファイルをいじる必要が…」とか書かれていますが、「--add-data」オプションを使用すれば必要ありません。
以下に例を示します。単純化のためウィンドを表示させているだけのプログラムです。
アイコンファイル(今回はtest_icon.ico)はソースファイルと同じディレクトリに置いています。
import tkinter
import sys
import os
def temp_path(relative_path):
try:
#Retrieve Temp Path
base_path = sys._MEIPASS
except Exception:
#Retrieve Current Path Then Error
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
logo=temp_path('test_icon.ico')
root=tkinter.Tk()
root.geometry('300x300')
root.title('test')
root.iconbitmap(default=logo)
root.mainloop()
root.quit()
temp_path関数でsys._MEIPASSをとおして、アイコンのパスを取得しています。
使い方は
logo=temp_path([アイコンファイル名])
で、戻り値のアイコンファイルのパスがlogo変数にはいります。
temp_path関数についてはコピペで使用できます。
そして、iconbitmapに取得したパス、つまりlogo変数を渡します。
あとはpyinstallerでバイナリ化(exe化)するだけです。
コマンドは
pyinstaller test.py --onefile --noconsole --icon=test_icon.ico --add-data "test_icon.ico;./"
コマンドの実行場所はソースコードとアイコンファイルのあるディレクトリ(フォルダ)です。
test.pyはソースコード、test_icon.icoはアイコンファイル名です。
アイコンファイルの作成方法については以下の記事を参考にしてください。
Python メッセージボックスの作り方(ルートウィンドウの消し方も!)
今回はtkinterを使用して、メッセージボックスの作成を行います。とりあえず、物は試しということで、表示させてみましょう。
説明もかねて複数の種類の画面を連続で表示させます。print(ans)で戻り値を表示させています。
from tkinter import messagebox
#ボタン(マーク)
# OK(i)
ans=messagebox.showinfo("title", "Hello world")
print(ans)
# OK(!)
ans=messagebox.showwarning("title", "Hello world")
print(ans)
# OK(×)
ans=messagebox.showerror("title", "Hello world")
print(ans)
# はい・いいえ(?)
ans=messagebox.askyesno("title", "Hello world")
print(ans)
# はい・いいえ(?)
ans=messagebox.askquestion("title", "Hello world")
print(ans)
# OK・キャンセル(?)
ans=messagebox.askokcancel("title", "Hello world")
print(ans)
# 再試行・キャンセル(!)
ans=messagebox.askretrycancel("title", "Hello world")
print(ans)
表示される画面を表にしてみました。必要に応じて使い分けましょう。
見てわかるように「askyesno」と「askquestion」の違いは戻り値の違いだけです。
最後に今回のコードを実行するとtkinterの仕様で画面が2個表示されたかと思います。これはなぜかというと、tkinterはメイン(親)の画面があって、その子ウィンドとしてメッセージボックスを表示する仕様になっているからです。ほとんどの場合は何か親ウィンドがあって、ボタン押したときにメッセージボックスを表示させると思うのであまり気にしなくてもいいのですが。
もし親ウィンドを表示させたくなければ、以下のように追記してください。
withdraw()だけを追記するように書いているサイトもありますが、私の場合はattributes等もないと消えてくれませんでした。
from tkinter import messagebox,Tk
root=Tk()
root.geometry('300x300')
root.attributes('-topmost',True)
root.withdraw()
#ボタン(マーク)
# OK(i)
ans=messagebox.showinfo("title", "Hello world")
print(ans)
# OK(!)
ans=messagebox.showwarning("title", "Hello world")
print(ans)
# OK(×)
ans=messagebox.showerror("title", "Hello world")
print(ans)
# はい・いいえ(?)
ans=messagebox.askyesno("title", "Hello world")
print(ans)
# はい・いいえ(?)
ans=messagebox.askquestion("title", "Hello world")
print(ans)
# OK・キャンセル(?)
ans=messagebox.askokcancel("title", "Hello world")
print(ans)
# 再試行・キャンセル(!)
ans=messagebox.askretrycancel("title", "Hello world")