Daha iyi bir yol var mı?

Merhaba, komutu oynattığımızda resmin değişmesini sağlamak için aşağıdaki yolu izliyorum acaba daha kısa bir yolu var mı merak ettim bilgilendirirseniz sevinirim.

Komut oynatılmadan önce;
image

Komut oynatıldıktan sonra;
image

import threading
import time
import tkinter as tk
from PIL import Image, ImageTk, ImageOps

def test(e=None):
    print("+"*10)

def test2(e=None):
    print("*"*10)

class Button(tk.Tk):
    def __init__(self,x,y,name,cmd=None,change=None):
        btn = tk.Button(root,text="Test",bg="black",activebackground="black")
        btn.place(x=x,y=y)
        res = Image.open(f"images/{name}.png").resize((50, 50))
        resim = ImageTk.PhotoImage(res)
        btn["image"] = resim
        btn.resim = resim
        if cmd != None:
            btn.bind('<ButtonRelease-1>',lambda event: threading.Thread(target=command,args=(cmd,change,)).start())
        def command(cmd,change=None):
            cmd()
            if change!=None:
                ch_res = Image.open(r"images/güncellendi.png").resize((50, 50))
                ch_resim = ImageTk.PhotoImage(ch_res)
                btn["image"] = ch_resim
                btn.resim = resim
                time.sleep(1)
                btn["image"] = resim
                btn.resim = resim






if __name__ == '__main__':

    root = tk.Tk()
    root.geometry("200x200")
    Button(x=70,y=50,cmd=test,name="Bulunamadı",change=True)
    root.mainloop()

Merhaba, izninizle paylaştığınız koddaki yazım yanlışlarından bahsedeyim.

  1. Button nesnesi, tk.Tk nesnesinden miras alınarak oluşturulmuş ama super class'ın __init__ metodu çağrılmamış.
class Button(tk.Tk):
    def __init__(self, x, y, name, cmd=None, change=None):
        super().__init__()
  1. Button sınıfı için, miras alınması gereken sınıfın tk.Button sınıfı olması gerekiyor.
class Button(tk.Button):
    def __init__(self, x, y, name, cmd=None, change=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
  1. Button sınıfını adeta bir fonksiyon gibi kullanmışsınız, sınıfa ait hiçbir nitelik veya metot tanımlanmamış. command metodu da __init__ metodu içinde tanımlanmış.
  2. time.sleep(1) programda donmaya neden olacağı için Thread nesnesi kullanmışsınız. time.sleep(1)'den sonraki kısmı bir fonksiyon içine alıp, bu fonksiyonu root.after(1000, fonksiyon) şeklinde çağırabilirsiniz. Yani özellikle Thread nesnesi kullanmaya gerek yok.
  3. Sınıfı, birden fazla düğme oluşturmak istediğimizde, oluşturmak istediğimiz bütün düğmeler için aynı işlevleri yerine getirecek şekilde yazmamız gerekiyor. Sizin yazdığınız sınıf, maalesef sadece güncellendi.png resmi için çalışıyor. Böyle dosya isimlerini, kullanıcının en son aşamada girmesi gerekir. Dosya ismini hardcoded yaparsanız, o sınıf başka resimler için çalışamaz.

Ben olsaydım, resmi değişebilir Button sınıfını şu şekilde yazardım:

import tkinter as tk

from PIL.ImageTk import Image, PhotoImage


class ChangeableButton(tk.Button):
    def __init__(self, images, commands, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if any(len(i) != 2 for i in [images, commands]):
            raise IndexError(
                "The length of 'images' and 'commands' "
                "arguments must be 2."
            )
        self.images = [PhotoImage(Image.open(image)) for image in images]
        self.index = 0
        self.configure(
            image=self.images[0],
            command=lambda: self.command(commands),
            borderwidth=0,
            highlightthickness=0,
            activebackground=self["bg"]
        )
        self.pack()

    def command(self, commands):
        if callable(commands[self.index]):
            commands[self.index]()
        if self.index == 1:
            self.index = 0
        else:
            self.index += 1
        self["image"] = self.images[self.index]


def main():
    root = tk.Tk()
    ChangeableButton(
        master=root, 
        images=("power-on.png", "power-off.png"),
        commands=(lambda: print("power on"), lambda: print("power off"))
    )
    ChangeableButton(
        master=root,
        images=("switch-on.png", "switch-off.png"),
        commands=(lambda: print("switch on"), lambda: print("switch off"))
    )
    ChangeableButton(
        master=root,
        images=("start.png", "stop.png"),
        commands=(lambda: print("started"), lambda: print("stopped"))
    )
    root.mainloop()


if __name__ == "__main__":
    main()

1 Beğeni

Cevabınız için teşekkürler, aslında bildiğim kodları kullanarak bu sistemi yazdım ama sizin yaptığınız çok daha kullanışlı olmuş yazım hatalarımdan bahsetmişsiniz attığınız kodda bir kaç bölümün işlevini bilmiyorum acaba bunlar hakkında bilgi verme şansınız var mı örneğin: *args, **kwargs ve super().__init__(*args, **kwargs) bölümleri gibi ayrıca aşagıdaki kod sorunsuz çalışıyor.

image
image
image

import threading
import time
import tkinter as tk
from PIL import Image, ImageTk, ImageOps

def test(e=None):
    print("+"*10)

def test2(e=None):
    print("*"*10)

class Button(tk.Tk):
    def __init__(self,x,y,img,ch_img,cmd=None,change=None):
        btn = tk.Button(root,text="Test",bg="black",activebackground="black")
        btn.place(x=x,y=y)
        res = Image.open(f"images/{img}.png").resize((50, 50))
        resim = ImageTk.PhotoImage(res)
        btn["image"] = resim
        btn.resim = resim
        if cmd != None:
            btn.bind('<ButtonRelease-1>',lambda event: threading.Thread(target=command,args=(cmd,change,ch_img)).start())
        def command(cmd,change=None,ch_img=None):
            cmd()
            if change!=None:
                ch_res = Image.open(f"images/{ch_img}.png").resize((50, 50))
                ch_resim = ImageTk.PhotoImage(ch_res)
                btn["image"] = ch_resim
                time.sleep(1)
                btn["image"] = resim






if __name__ == '__main__':

    root = tk.Tk()
    root.geometry("400x400")
    Button(x=70,y=50,cmd=test,img="Bulunan",ch_img="arama",change=True)
    Button(x=200,y=50,cmd=test,img="Bulunamadı",ch_img="güncellendi",change=True)
    root.mainloop()

Paylaştığınız kod düzgün çalışıyor. Ancak benim demek istediğim, yazdığınız kod, nesne tabanlı programlama yaklaşımına pek benzemiyor.

Mesela aşağıdaki parantez içindeki tk.Tk'yı boşuna yazmışsınız. Onu silerseniz, hiç bir değişiklik olmadığını görürsünüz.

class Button(tk.Tk):

*args ve **kwargs**'ın ne anlama geldiğinden bahsedeyim:

Biz neden class Button(tk.Button): gibi bir ifade yazarız? Oluşturacağımız Button isimli sınıf, tk.Button'u miras alsın diye.

Bir tk.Button nesnesi oluşturduğumuzda parametreler kullanıyoruz değil mi? İşte bu kullanılan parametreleri oluşturacağımız Button sınıfında da kullanabilmek için bu *args ve **kwargs ifadelerini kullanırız.

Örnek:

import tkinter as tk


class Button(tk.Button):
    def __init__(self):
        super().__init__()
        self.pack()


root = tk.Tk()
Button(master=root)
root.mainloop()

Yukarıdaki kod TypeError: __init__() got an unexpected keyword argument 'master' gibi bir hata almamıza neden olur. Çünkü master parametresi __init__ metoduna ait bir parametre değil.

Peki! master parametresini __init__ metoduna ekleyelim.

import tkinter as tk


class Button(tk.Button):
    def __init__(self, master):
        super().__init__()
        self.pack()


root = tk.Tk()
frame = tk.Frame(master=root)
frame.pack()
Button(master=frame)
for child in frame.winfo_children():
    print(child)
root.mainloop()

Bu kodları çalıştırdığımızda, Button nesnesinin ekrana yerleştirildiğini görürsünüz. __init__ metodunun master isimli bir parametresi var. Ama bu parametrenin, miras alınan sınıfın __init__ metodu üzerinde herhangi bir etkisi yok. Bu yüzden bu düğme tk.Frame widgetine değil, tk.Tk widgetine yerleştirildi.

Kodu biraz daha değiştirelim:

import tkinter as tk


class Button(tk.Button):
    def __init__(self, master):
        super().__init__(master=master)
        self.pack()


root = tk.Tk()
frame = tk.Frame(master=root)
frame.pack()
Button(master=frame)
for child in frame.winfo_children():
    print(child)
root.mainloop()

Yukarıdaki koda göre, Button sınıfının örnek yapıcı fonksiyonu master parametresini alıyor ve bu parametre, miras alınan tk.Button sınıfının master parametresinin değeri haline getiriliyor. Sonuç olarak, bu örnekteki print fonksiyonu, .!frame.!button yazısını ekrana yazdırır. Yani Button nesnesinin ebeveyn widgeti, tk.Frame widgeti olmuş.

Ancak biz hala tk.Button sınıfına ait bütün parametreleri Button sınıfı için kullanamıyoruz. Aşağıdaki koda bakın mesela:

import tkinter as tk


class Button(tk.Button):
    def __init__(self, master):
        super().__init__(master=master)


root = tk.Tk()
Button(master=root, text="hello")
root.mainloop()

Bu kodu çalıştırdığımız zaman TypeError: __init__() got an unexpected keyword argument 'text' şeklinde bir hata alırız.

O halde bizim text parametresini de yapıcı fonksiyonun parametresi haline getirmemiz gerekiyor.

import tkinter as tk


class Button(tk.Button):
    def __init__(self, master, text):
        super().__init__(master=master, text=text)


root = tk.Tk()
Button(master=root, text="hello")
root.mainloop()

Biz bir sınıftan miras alırken, miras aldığımız sınıfın __init__ metodundaki bütün parametreleri yazmamak için *args ve **kwargs ifadesini kullanırız.

*args: Argümanların hepsini temsil eder, bir tuple nesnesidir.

def f(a, b, c):
    return a, b, c


def g(*args):
    return args

**kwargs: Anahtar kelime argümanlarını (ön tanımlı argümanları) temsil eder, bir dict nesnesidir.

def f(a=1, b=2, c=3):
    return a, b, c


def g(**kwargs):
    return kwargs.values()

Button örneğimize tekrar gelelim, kodu aşağıdaki gibi değiştiriyorum:

import tkinter as tk


class Button(tk.Button):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


root = tk.Tk()
Button(master=root, text="hello")
root.mainloop()

Artık, tk.Button nesnesinin kaç tane argümanı ve anahtar kelime argümanı varsa hepsini Button sınıfından bir örnek oluşturmak istediğimizde kullanabiliriz.

super ifadesi de miras alınan sınıfın yerine kullanılır.

class Button(tk.Button):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

ile

class Button(tk.Button):
    def __init__(self, *args, **kwargs):
        tk.Button.__init__(self, *args, **kwargs)

aynı işlevi görür.

4 Beğeni

Bu akıcı anlatım ve örneklendirmeler için teşekkürler benim için çok değerli bilgiler :heart: