見出し画像

Tkinterやってみる(10-1)

前回

では、クラスを使わない書き方⇒クラスを使う書き方
という書き換えをやってみました。行数に関しては53行が65行にと、かえって増えてしまっています。まあ、ちょっとくらい行数が増えても、それ自体は別にいいのです。それによって、把握しやすくなっているならば・・。
 ただ、期待していたメリットは得られたのか・・。
 今回は「クラス利用の意味」についての自己反省過程のメモです。

クラスを使う意義

 自分なりの理解では、クラスの利用は
・「全体」の動作にまんべんなく気をくばる必要性から解放される。完璧に動く部分に切り分けて、それらの組み合わせでアプリを構築できる
・・・という点が大事なのだろうと思っています。
 そうすると、単に「クラスの仕組み(文法)を使って書いてみた」と言うだけでは意味がない。複数の独立したクラスの間で、深く相互依存しないようにうまく組み合わせる工夫がいるんでしょうね。

 昔、GoFの「デザインパターン」の訳本を買ったことがあります。当時はピンとこなかった。・・・そして、引っ越しの時に処分しちゃった。
 今思うとおそらく、今ちょうど気になっているあたりのノウハウが詰まっていたのじゃないかなと思います。本を処分したのはもったいなかった。

前回の「クラスを使った書き方」のコード:自分で駄目だし

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk

class App(tk.Tk):

    def __init__(self, title, size):

        # main window
        super().__init__()
        self.title(title)
        self.geometry(f"{size[0]}x{size[1]}")

        # widgets
        self.buttonframe = mybuttonframe(self, width = 500, height = 300).pack()

        # 画像読み込み
        img1 = ImageTk.PhotoImage(Image.open("maji_ss.jpg"))
        img2 = ImageTk.PhotoImage(Image.open("aho_ss.jpg"))
        self.imgs = [img1, img2]

        # 画像用ラベル
        self.label = ttk.Label(self, image="")
        self.label.pack(anchor='w')

        self.mainloop()

    def on_enter(self,event):
        config(bg = "lightblue")   #ボタン色変える
        buttontext = event.widget.cget("text")
        buttonvalue = int(buttontext)
        
        #3の倍数⇒アホ
        if (buttonvalue % 3) == 0 : 
            self.label.config(image=self.imgs[1])

        #3を含む⇒アホ
        elif buttontext.find("3") > -1:
            self.label.config(image = self.imgs[1])
        
        #その他⇒まじめ
        else :
            self.label.config(image = self.imgs[0])

    def on_leave(self, event):
        event.widget.config(bg = "SystemButtonFace")   #ボタン色戻す
        self.label.config(image = "")  #画像消す
        
class mybuttonframe(ttk.Frame):
    def __init__(self, parent, width, height):
        super().__init__(parent, width=width, height =height)

        # ボタンを作成して配置
        for i in range(100):
            button = tk.Button(self, text=str(i + 1))
            row = i // 10
            column = i % 10
            button.grid(row=row, column=column)

            button.bind("<Enter>", parent.on_enter)
            button.bind("<Leave>", parent.on_leave)

        self.pack(anchor='w')

App("3のギャグ_クラス使用",(700,550))

これで動いたは動いたのですが、どうも気持ち悪い。
 マウスポインタがかかったとき、外れた時で、ボタンの色を変えるような処理はフレーム側でやること、
 ボタンの上にマウスポインタが来た場合、
 「3」に関わる数字の時には、絵をアホにする
 それ以外は絵を真面目顔にする。
というのはこのアプリの特徴だから、Appクラスの中で書くべきだろうけど

buttontext = event.widget.cget("text") 

でボタン面の文字列取得する処理、とかは、Appクラスの側でやることじゃなくて、「ボタンフレーム」のクラスの中で完結していて欲しい。
 あと、mybuttonframeクラスの中で

            button.bind("<Enter>", parent.on_enter)
            button.bind("<Leave>", parent.on_leave)

こういうのやるのはスジが悪い気がしてきました。
 というか、見た目動いたとしても、間違っていますね。<Enter><Leave>だからon_enterだ、on_leaveだというのは、「ボタン」から見た話で、parentであるアプリ持つメソッド名として、というのは駄目ですね。

「ボタンがいっぱいある」部品から再設計

mybuttonframe・・名前が悪いな。
ボタンが100個あるフレーム(再利用、汎用化は今のところ考えない。⇒10×10で100個というのは固定。)ということで
 Button100frameという名前にします。機能は
「ボタンの上にマウスポインタが来た時<Enter>」
・自身でボタンの色を変える("lightblue"にする)
・マウスポインタが乗ったことを、ボタン番号と共にアプリに通知。

「ボタンの上からマウスポインタが去った時<Leave>」
・自身でボタンの色を変える(""SystemButtonFace""にする)
・マウスポインタが離れたことを、アプリに通知。
こういう機能で完結させることを想定。

電卓アプリを参考にする

その前に、ウインドウの中にたくさんボタンがある・・といえば電卓アプリですよね。参考にしたい。Copilot先生におねだりしちゃいます。

コード例をください
import tkinter as tk

class CalculatorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("電卓アプリ")
        
        self.display = tk.Entry(root, width=16, font=('Arial', 24), bd=10, insertwidth=2, justify='right')
        self.display.grid(row=0, column=0, columnspan=4)
        
        self.create_buttons()
    
    def create_buttons(self):
        button_texts = [
            '7', '8', '9', '/',
            '4', '5', '6', '*',
            '1', '2', '3', '-',
            '0', 'C', '=', '+'
        ]
        
        buttons = []
        for i, text in enumerate(button_texts):
            button = tk.Button(self.root, text=text, padx=20, pady=20, font=('Arial', 18),
                               command=lambda t=text: self.on_button_click(t))
            buttons.append(button)
            row = (i // 4) + 1
            col = i % 4
            button.grid(row=row, column=col)
    
    def on_button_click(self, char):
        if char == 'C':
            self.display.delete(0, tk.END)
        elif char == '=':
            try:
                result = eval(self.display.get())
                self.display.delete(0, tk.END)
                self.display.insert(tk.END, str(result))
            except Exception as e:
                self.display.delete(0, tk.END)
                self.display.insert(tk.END, "Error")
        else:
            self.display.insert(tk.END, char)

if __name__ == "__main__":
    root = tk.Tk()
    app = CalculatorApp(root)
    root.mainloop()

すごい、短い。50行もない。これで実行すると

完璧に動作して実用的。ああ、なんだか打ちのめされた気分です(笑)

この記事が気に入ったらサポートをしてみませんか?