前回作成したPythonのルーチン読み上げツールだが、GUIメニューを作ってそこから起動するような形にしてみた。
thom.hateblo.jp
動機は、将来的にツールを増やしていく場合、Bashスクリプトのダブルクリックで起動するのが面倒だから。机に立てかけたタッチスクリーンでダブルタップするのって結構難しく、よくファイル名の変更になってしまったりドラッグになってしまったりする。
そこで、メニュー画面を常時起動させて、そこでアプリケーションの切り替え操作を完結したいというのが動機。
パク参考にしたのは以下のQA。
www.reddit.com
モジュール構成
画面遷移に使用するのはメニューモジュールが一つと、未登録用のダミーアプリケーション、前回作成したお掃除ルーチンアプリケーションの3つ。他のはcleanup.pyから呼ばれるplaysound.pyがある。前回紹介してなかったのでついでにコードを掲載しておく。
- menu.py
- dummy.py
- cleanup.py
- playsound.py
お掃除ルーチンはメニューに対応させるため単体起動できなくなっている。
画面遷移のイメージ
コード
menu.py
# https://www.reddit.com/r/learnpython/comments/776kd9/tkinterhow_can_i_destroyhide_the_root_window/ import tkinter as tk from cleanup import CleanUpRoutine from dummy import DummyApp class FrameBase(tk.Tk): def __init__(self): tk.Tk.__init__(self) self.geometry("800x480") self.frame = StartPageFrame(self) self.frame.pack(expand=True, fill="both") #self.attributes("-fullscreen", True) def change(self, frame): self.frame.pack_forget() # delete currrent frame self.frame = frame(self) self.frame.pack(expand=True, fill="both") # make new frame def backToStart(self): self.frame.pack_forget() self.frame = StartPageFrame(self) self.frame.pack(expand=True, fill="both") class StartPageFrame(tk.Frame): def __init__(self, master=None, **kwargs): tk.Frame.__init__(self, master, **kwargs) master.title("Start Page") self.grid(column=0, row=0, sticky=tk.NSEW) self.Applist = [ [ [CleanUpRoutine, "Clean up routine"], [DummyApp, "Not Registerd"], [DummyApp, "Not Registerd"] ], [ [DummyApp, "Not Registerd"], [DummyApp, "Not Registerd"], [DummyApp, "Not Registerd"] ] ] lbl = tk.Label(master=self, text ="Start Page", font=("Migu 1M",14)) lbl.grid(column=0,row=0,sticky=tk.NW, padx=10) btn = tk.Button( master = self, text="Close", width = 5, bg = "#dc143c", fg = "#ffffff", command=self.master.destroy) btn.grid(column=2, row=0,sticky=tk.NE) for r in range(1,3): for c in range(3): btn = tk.Button( master=self, wraplength=150, justify=tk.LEFT, text=self.Applist[r-1][c][1], font=("Migu 1M", 16), bg="#e6e6fa", command=self.gotoApp(r-1,c)) btn.grid(column=c, row=r, padx=10, pady=10, sticky=tk.NSEW) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) self.rowconfigure(1, weight=1) self.rowconfigure(2, weight=1) self.master.columnconfigure(0, weight=1) self.master.rowconfigure(0, weight=1) def gotoApp(self,r,c): return lambda :self.master.change(self.Applist[r][c][0]) if __name__=="__main__": app=FrameBase() app.mainloop()
dummy.py
import tkinter as tk class DummyApp(tk.Frame): def __init__(self, master=None, **kwargs): tk.Frame.__init__(self, master, **kwargs) master.title("No application registered.") btn = tk.Button(master=self, text="Back", width=5, bg = "#00a4e4", fg = "#ffffff", command=self.master.backToStart) btn.pack(anchor=tk.NW) lbl = tk.Label(self, text="No application registered here.", height=5, font=("Migu 1M",20)) lbl.pack()
cleanup.py
import tkinter as tk import tkinter.font as font import os import playsound class CleanUpRoutine(tk.Frame): def __init__(self, master=None, **kwargs): tk.Frame.__init__(self, master, **kwargs) self.load_resources() self.create_widgets() self.n = 0 def load_resources(self): self.base = os.path.dirname(os.path.abspath(__file__)) self.icon1 = tk.PhotoImage(file=self.base + '/next.png') self.icon2 = tk.PhotoImage(file=self.base + '/next_disabled.png') def create_widgets(self): #Root Setting self.master.title("Cleanup Routine") #Generate Parts frame1 = tk.Frame(master = self) quit_button = tk.Button( master = frame1, command = self.master.backToStart, width = 5, text = "Back", bg = "#00a4e4", fg = "#ffffff") next_button = tk.Canvas( master = self, width = 128, height = 128) self.imagearea = next_button.create_image( 0, 0, image=self.icon1, anchor=tk.NW) next_button.bind("<Button-1>", self.next_clicked) self.var = tk.StringVar() my_font = font.Font(self,family="Migu 1M",size=20,weight="normal") lbl = tk.Label(self, textvariable=self.var, height=5, font=("Migu 1M",20)) #Layout frame1.pack(fill="x") quit_button.pack(side="left") lbl.pack() next_button.pack() #Localization self.frame1 = frame1 self.quit_button = quit_button self.next_button = next_button self.lbl = lbl def play(self, filenumber): txt = open(self.base + "/wav/CleanupRoutine-" + str(filenumber) + ".txt", 'r', encoding='utf-8') self.var.set(txt.read()) self.master.update() txt.close filename=self.base + "/wav/CleanupRoutine-" + str(filenumber) + ".wav" playsound.playAudio(filename) def next_clicked(self, event): self.next_button.itemconfig(self.imagearea, image = self.icon2) self.master.update() n = self.n self.play(n) n = n + 1 if n > 14: n = 0 self.n = n self.next_button.itemconfig(self.imagearea, image = self.icon1) self.master.update()
playsound.py
import pyaudio import wave def playAudio(fname): CHUNK = 1024 wf = wave.open(fname, 'rb') p = pyaudio.PyAudio() stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), output=True) """ format : ストリームを読み書きするときのデータ型 channels: ステレオかモノラルかの選択 1でモノラル 2でステレオ rate : サンプル周波数 output : 出力モード """ # 1024個読み取り data = wf.readframes(CHUNK) while data != b'': stream.write(data) # ストリームへの書き込み(バイナリ) data = wf.readframes(CHUNK) # ファイルから1024個*2個の stream.stop_stream() stream.close() p.terminate()
ざっくりアプリケーション切り替えの仕組み
Tkinterでは下図のようにメイン画面に直接ボタンやラベル等のGUIパーツを配置してアプリケーションを作成できる。
TkinterにはGUIパーツを纏めるフレームというものが用意されているので、今回は直接GUIを配置せずにアプリケーションのGUIは全てフレームに乗せている。こうすることで、フレームの乗せ換えによってアプリケーション画面の切り替えを実現することができる。
具体的には前回の記事でcleanup.pyのクラスはtk.Tkを継承していたが、今回はtk.Frameから継承させて1枚のフレームとして構成している。
class CleanUpRoutine(tk.Frame):
実際のフレーム切り替えは、menu.pyのFrameBaseクラスのメソッド「change」で行われる。
def change(self, frame): self.frame.pack_forget() # delete currrent frame self.frame = frame(self) self.frame.pack(expand=True, fill="both") # make new frame
インスタンス変数「self.frame」と、仮引数「frame」が同名なのでやや混乱するかもしれないが、これはもともと参考にしたサイトがそうだったたし、pythonのコーディングの流儀みたいなものはまだわかってないので下手にいじらないでおこうと思ったためそのままにした。PEP見ろって話だろうけど、まだ私のPythonコーディングはお試し期間みたいなものなので、そこまで本腰入れるのはもう少し後。