Sınıflar ve tkinter hakkında

Merhaba. Foruma yeni kaydoldum. Aranızda olmaktan mutlu olduğumu söylemeliyim.

2-3 haftadır aktif olarak python çalışıyorum. tkinter modülünü kullanarak basit bir arayüz geliştirdim. Bütün kodlarım aynı dosyanın içinde . Şimdilik 600 satırı geçti. Fakat amatör bir iş yaptığmın farkındayım. Arkada mysql sorgusu ve pandas işlemleri yapılırken arayüz “yanıt vermiyor” yazarak kitleniyor işlem bitene kadar. Geçenlerde dildeolupbiten in “Tkinter uygulamalarında class kullanmanızı tavsiye ederim.” cümlesini okudum bir başlığın altında.

Biraz biraz öğrendiğim bilgiyle aşağıdaki kodu yazdım.
AttributeError: type object ‘Display’ has no attribute ‘ekranlar’ hatası verdi.

Sorunum hakkında yardımcı olursanız sevinirim.
Amacım öncelikle ana py dosyasında sadece arayüzün kodu olması.
Olabilirse her sekmenin py dosyası ayrı olacak. Bütün kodların aynı dosyada olması programı hantallaştırır diye düşünüyorum. Farklı sınıflar farklı dosyalar arasında değişken aktarımı nasıl olacak? Aynı dosyada farklı fonksiyonlarda global değişken tanımlamayla sorun çözülüyor. Fakat farklı sınırlar için yapamadım.

init le başlayan fonksiyon sanırım sınıfın ön tanımlı fonksiyonu gibi bir şey. Arayüzü de böyle tanımladım. Bu konuda daha yararlı yöntemler olacağına inanıyorum.

Şimdilik kodlama kısmına ağırlık verdiğim için sonrasını düşünmedim. Fakat günün sonunda kodları exe haline getirip bir kaç bilgisayarda daha kullanılmasını sağlamam gerekiyor. python kurulu olmayan bilgisayarlarda çalışmadığıyla ilgili bir şeyler okudum. Bu konuda öneriniz nedir?

Biraz uzun oldu sanırım. İlk mesajım olduğu için kusura bakmayın. :grinning:

import tkinter as tk
from tkinter import ttk, filedialog,messagebox
import os

icmalsablonyolu=""

def icmalsablon():
    global icmalsablonyolu, icmalsablonyoluetiket
    icmalsablonyolu = filedialog.askopenfilename(multiple=False, title="İcmal Şablonunu Seç",
                                                 filetypes=(("Excel Workbook", "*.xls"), ("Excel Workbook", "*.xlsx")))
    icmalsablonyoluetiket.config(text=icmalsablonyolu)

class icmal:
    def __init__(self):
        if icmalsablonyolu:
            verigir()
        else:
            messagebox.showinfo("İcmal Şablon Dosyası Bulunamadı",
                                "İcmal Şablonunu Belirlemek İçin Ayarlara Yönlendiriliyorsunuz")

            Display.ekranlar.select(Display.ayarlarekran)

class Display:
    def __init__(self):
        global icmalsablonyolu, icmalsablonyoluetiket
        self.root=tk.Tk()
        self.en = 800
        self.boy = 400
        self.root.geometry('%dx%d+%d+%d' % (
        self.en, self.boy, ((self.root.winfo_screenwidth() / 2) - (self.en / 2)), ((self.root.winfo_screenheight() / 2) - (self.boy / 2))))
        self.root.resizable(False, False)
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)

        # SEKMELER
        self.ekranlar = ttk.Notebook(self.root)
        self.aekran = tk.Frame(self.ekranlar, bg="#A9CCE3")
        self.bekran = tk.Frame(self.ekranlar, bg="#FDEDEC")
        self.ayarlarekran = tk.Frame(self.ekranlar, bg="#EAF2F8")
        self.surecbilgiekran = tk.Frame(self.ekranlar)

        self.ekranlar.add(self.aekran, text='aekran')
        self.ekranlar.add(self.bekran, text='bekran')
        self.ekranlar.add(self.ayarlarekran, text='AYARLAR')
        self.ekranlar.add(self.surecbilgiekran, text='SUREC BILGI')

        self.ekranlar.grid(row=0, column=0, sticky="news")

        # SÜREÇ BİLGİ EKRANI
        self.surecbilgilist_sbarX = tk.Scrollbar(self.surecbilgiekran, orient='horizontal')
        self.surecbilgilist_sbarX.pack(side='bottom', fill='x')

        self.surecbilgilist_sbarY = tk.Scrollbar(self.surecbilgiekran)
        self.surecbilgilist_sbarY.pack(side='right', fill='y')

        self.surecbilgilist = tk.Listbox(self.surecbilgiekran, xscrollcommand=self.surecbilgilist_sbarX.set,
                                 yscrollcommand=self.surecbilgilist_sbarY.set)
        self.surecbilgilist.pack(side='right', expand=True, fill='both')
        self.surecbilgilist_sbarX.config(command=self.surecbilgilist.xview)
        self.surecbilgilist_sbarY.config(command=self.surecbilgilist.yview)

        self.mdm_button1 = tk.Button(self.aekran, text="MDM", width=10, height=2, font="Helvetica 12 bold")
        self.mdm_button1.grid(row=0, column=0, padx=10, pady=10)

        self.icmal_button = tk.Button(self.aekran, text="icmal", width=10, height=2, command=icmal,  font="Helvetica 12 bold")
        self.icmal_button.grid(row=0, column=1, padx=10)

        self.guncelseyt_button = tk.Button(self.aekran, text="Güncel SEYT", width=10, height=2,
                                   font="Helvetica 12 bold")
        self.guncelseyt_button.grid(row=1, column=0, pady=10)

        # AYARLAR SEKMESİ
        self.icmalsecbutton = tk.Button(self.ayarlarekran, text='İcmal Şablon Seç', command=icmalsablon, font="Helvetica 10")
        self.icmalsecbutton.grid(row=0, column=0)

        icmalsablonyoluetiket = tk.Label(self.ayarlarekran, text=icmalsablonyolu)
        icmalsablonyoluetiket.grid(row=0, column=1)

        self.cikis = tk.Button(self.ayarlarekran, text="Çıkış", command=self.root.destroy, width=10, height=2, font="Helvetica 12 bold")
        self.cikis.grid(row=3, column=0, pady=10)
        
        self.root.mainloop()


display=Display()
2 Beğeni

Bütün kodların aynı dosyada olması programı hantallaştırmaz sadece bizim için dezavantaj oluşturur. Neyi nerede arayacağımızı karıştırmaya başlayabiliriz. Halbuki kodlar belli gruplara bölünse, neyin nerede olduğunu daha rahat bulabiliriz.

Bu arada hatanın sebebi, ekranlar'ın bir örnek niteliği olması, bir sınıf niteliği değil. Dolayısıyla ekranlar'a örnek üzerinden ulaşmanız lazım.

icmal sınıfını şu şekilde düzenleyin:

class icmal:
    def __init__(self, _display):
        if icmalsablonyolu:
            verigir()
        else:
            messagebox.showinfo("İcmal Şablon Dosyası Bulunamadı",
                                "İcmal Şablonunu Belirlemek İçin Ayarlara Yönlendiriliyorsunuz")

            _display.ekranlar.select(_display.ayarlarekran)

Daha sonra, Display() sınıfındaki icmal_button düğmesini de şu şekilde düzenlerseniz sorun çözülür.

        self.icmal_button = tk.Button(
            self.aekran, 
            text="icmal", 
            width=10, 
            height=2, 
            command=lambda: icmal(_display=self),  
            font="Helvetica 12 bold"
        )

Bu konudaki önerim, karşı tarafa python kurmasını tavsiye etmek. :slight_smile:

Python kodlarının exe’ye dönüştürülmesi gerekmiyor ki.

2 Beğeni

Merhaba.

Eğer bu işlemler art arda yapılan ve kısa süren birkaç farklı işlemden oluşuyorsa aralarda Tk nesnesinin update metodu çağırılabilir, aksi takdirde threading kullanabilirsiniz.

@dildeolupbiten’in de dediği gibi yavaşlatmaz (hatta CPython’da çoğunlukla daha hızlı olur).
Ama kodların okunabilirliği ufacık optimizasyonlardan neredeyse her zaman daha önemlidir.

Fonksiyonların parametreleri veya örnek nitelikleri ile.

Bu çözüm bir önceki sorunu beraberinde getiriyor. En doğrusu parametre kullanmak.

1 Beğeni

Düzeltmeniz çalıştı. Sınıflar ile ilgili eğitim videoları izlemiştim. Fakat genel bilgi olsun bir gün işime yararsa yeniden incelerim diye yüzeysel baktım. Ciddi bir şekilde incelemem gerektiği ortada.

Uzun süre işlerimi Excel makroları, MSSQL sorgularıyla yürütüyordum. Amacım bir sürü makro yerine tek program olmasıydı. Aynı zamanda makro / programlama bilmeyen çalışma arkadaşlarıma exe şeklinde tıkla çalıştır program hazırlamaktı.

1 Beğeni

Siz de biliyorsunuz ki, öğrendiğimiz konuları sık sık kullanmak artık onu otomatik bir şekilde kullanabilmemizi sağlar. Araba kullanmak gibi düşünün, veya yürümek, veya bir dili kullanmak gibi.

1 Beğeni

CPython zaten exe şeklindeki bir tıkla çalıştır program, en iyisi @dildeolupbiten’in de dediği gibi bilgisayarlara python kurmak. Hatta kurulum programını otomatik olarak bir bat dosyası ile çalıştırabilirsiniz.

2 Beğeni

Multithreading, Multiprocessing ilgili makaleler okudum. Ama tkinter modülüyle arayüzü ve pandas la çalışan fonksiyonları nasıl farklı threadinglere koyacağımı bilemedim. İki konuda benim için şimdilik bir üst seviyeydi. Multiprocessing 3-4 seviye ilerisi. Bu yüzden onları bırakıp class üzerinden yürümeye karar verdim.

Dediğiniz gibi bir yandan da amacım programın geliştirilmeye açık olabilmesi için farklı dosyalara dağılması. Böylece geliştirdiğim farklı modüllere sonradan ekleme yaparken bütün kodun içinde gezinmeme gerek kalmaz.

‘Fonksiyonların parametreleri ve örnek nitelikleri’ konusuna yabancıyım. Yani fonksiyonlara parantez içinde parametre belirlemeyi hatırlıyor gibiyim. Ama hiç kullanmadım. Örnek nitelik konusunu ise ilk defa duyuyorum.

Her nesne ait olduğu sınıfın bir örneğidir (instance). Mesela 1 nesnesi int sınıfının bir örneğidir.

Örnek niteliği de bu örneklere ait özellikler/değişkenler oluyor, mesela burada root, Display örneklerinin bir niteliği (attribute):

1 Beğeni

Müsaadenizle @EkremDincel’in sözlerine ilaveten kodlar yardımıyla size örnek ve sınıf niteliği kavramlarıyla alakalı basit bir örnek göstereyim.

class Sinif:
    sinif_niteligi = 1
    
    def __init__(self):
        self.ornek_niteligi = 2

    def ornek_metodu(self):
        print(self_ornek_niteligi)

    @classmethod
    def sinif_metodu(cls):
        print(cls.sinif_niteligi)

    @staticmethod
    def ne_ornek_ne_de_sinif_metodu():
        print("hello")

Demek istediğiniz anladım. ,Aslında mantığını bildiğim bir konu. Sınıflar arasında nesne (başka bir pencerenin butonu v.b gibi) veya değişken aktarımı yapabilmek için parantezlerin arasına parametre koymamız gerekiyor. Konuyla ilgili kafamdaki soru işaretlerini giderecek kısa bir örnek üzerinden soru sorayım. (Forumda kodlarda renklendirmenin nasıl yapıldığını bilmiyorum.)

Class Konu:
       def __init__(self):
             self.button1=tk.Button.......

       def ilkfonksiyon():
             button5.=tkButton..........

Class paragraf:
       def __init__(self):
            degisken=2
      
       def basfonksiyon():
            ayarekran=Toplevel()

Soru şu; Paragraf sınıfındaki basfonksiyon fonksiyonunda Konu sınıfındaki ilkfonksiyon altındaki buton5 nesnesinin herhangi bir özelliğini değiştirmem gerekiyor. Konu hakkında genel yöntemi açıklayabilir misiniz?

Mesela daha önce yazdığınız başka bir sınıftaki buttonla bu Sinif taki ornek_metodu isimli fonksiyonu nasıl çalıştırmamız gerekiyor.

self.icmal_button = self.icmal_button = tk.Button(
self.aydinlatmaekran,
text=“icmal”,
width=10,
height=2,
command=lambda: icmal(self= ?),
font=“Helvetica 12 bold”
)

Orada self'i bir şeye eşitlemeniz gerekmiyor. aksine _display'i self'e eşitliyorsunuz. Ve böylece bir sınıfın örneğini başka bir sınıfta kullanmış oluyorsunuz.

Sanırım konuyu tam olarak anlayamadım. Bu örneğiniz üzerinden sorayım. (keşke self değil de başka bir fonksiyon üzerinden yürüseydim. self biraz kafa karıştırıcı)

icmal_button butonuna tıklanınca icmal sınıfına gidiyor. fakat parantez içindeki ifadeleri ( _display=self, self, _display) anlayamadım.

Size bir örnek hazırlayayım, umarım anlamanızda yardımcı olur.

Aşağıdaki örneği beraber inceleyelim isterseniz. Genelde tkinter widgetlerinden bir sınıf oluşturmak istediğimizde, o widgeti miras alan bir sınıf oluştururuz. Aşağıda Entry isminde oluşturduğumuz sınıf, tk.Entry widgetinin ve Button isminde oluşturduğumuz sınıf ise tk.Button widgetinin bütün özelliklerini miras alır.

import tkinter as tk


class Entry(tk.Entry):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.pack()
        
        
class Button(tk.Button):
    def __init__(self, entry: Entry = None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.entry = entry(master=self.master)
        self.pack()
        self.ayarla()
        
    def ayarla(self):
        self.configure(
            text="Çalıştır",
            command=lambda: print(self.entry.get())
        )
        
                
Button(master=tk.Tk(), entry=Entry).mainloop()

İki ayrı sınıfın iletişim kurmasını istiyorsak, kodların herhangi bir yerinde, o sınıfa erişmemizi sağlayan bir kod yazmalıyız.

Yukarıdaki Button sınıfından bir örnek oluştururken, Entry sınıfı ile iletişim kurması için, entry isimli bir parametre tanımladık. Ve bu parametrenin tipinin Entry olacağını belirtik. Ön-tanımlı değeri None olarak belirttik.

Her iki fonksiyonda bulunan super().__init__(*args, **kwargs) ifadesi, bu Button ve Entry sınıflarına yazılacak argümanların ve keyword argümanların miras aldıkları sınıflarla aynı olacağını gösteriyor. Yalnız dikkatinizi çektiyse, Bir tk.Button nesnesinin entry isimli bir keyword argümanı yok. Dolayısıyla bunu super ifadesinin içine yerleştirmedik. Bu bizim tanımladığımız sınıfa has bir özellik, miras alınan sınıfa değil.

Button fonksiyonundaki super ifadesinden sonra, self.entry isminde bir örnek niteliğini Entry sınıfından bir örneğe eşitledik. Entry sınıfının örneği, ebeveyn sınıf tk.Entry sınıfının örneğinin sahip olduğu bütün argümanları ve anahtar argümanları alır. Bu yüzden Entry()'nin bir master parametresi vardır. Tıpkı Button()'un olduğu gibi. Ve master=self.master dediğimiz zaman, buradaki self Button sınıfının örneği anlamına gelir. Bu örneğin master parametresinin değeri de tk.Tk() olur. Yani burada diyoruz ki, Button() ile Entry() örneklerinin master parametreleri aynı olsun.

Button sınıfının örneğini ekrana pack ile yerleştirdikten sonra self.ayarla diye bir örnek metodu çağrılmış. Ve bu örnek metodunun yaptığı şey şu:

self.configure() fonksiyonu ile Button() örneğinin bazı özellikleri değiştirilmiş. Bu özelliklerden bir tanesi Button() örneğinin text parametresi. Çünkü örneği tanımlarken bir tane text parametresi yazmamıştık. Bir diğer parametre ise command. Ve bu command parametresine de şu ifadeyi yazdık: lambda: print(self.entry.get()). Yani entry widgetine ne yazılmışsa bu yazıyı ekrana yazdıran bir fonksiyon bu. lambda fonksiyonlarını bilmiyorsanız, araştırmanızı tavsiye ederim.

Bu self.ayarla() örnek metodunu, metodun örnek üzerinde yaptığı değişiklikler örnek oluşturulur oluşturmaz aktif olsun diye __init__ metodunun içinde çağırıyoruz.

Sınıfı ve bu sınıftan oluşacak olan örneği tanımladıktan sonra. Sıra bu sınıftan örnek oluşturmaya geldi.

Button() ile bu örneği oluştururuz.

Button()'un master parametresi var. master parametresinin değeri tk.Tk() olacak demiştik. O halde kodları şöyle yeniden yazıyorum:

Button(master=tk.Tk())

Ayrıca, Button'un Entry tipinde bir parametresi de vardı. O halde kodları tekrar düzenliyorum:

Button(master=tk.Tk(), entry=Entry)

Şimdi geriye sadece mainloop etmek kaldı. Yalnız dikkatinizi çekmek istediğim bir durum var. Button'un entry parametresine Entry() değil de Entry yazdık. Bunun sebebi şu: Şayet Entry() yazsaydık, Entry sınıfından bir örnek oluşturmuş olacaktık. Ama biz Entry sınıfından bir örneği, Button sınıfının örneği oluşurken oluşturuyoruz.

Bakın:

        self.entry = Entry(master=self.master)

Bu arada bu Entry sınıfından örneği, Button sınıfının dışında oluşturup, onu yine parametre olarak button sınıfının içine dahil ederdik. O zaman kodlarda biraz daha değişiklik yapardık. Ama temel mantık şu şekilde: İki sınıf arasında iletişim kurmak istiyorsanız, bir sınıfı veya örneğini diğer sınıfın veya örneğinin içine parametre olarak aktarın veya bir sınıfın/sınıf örneğinin içinde başka bir sınıftan örnek oluşturun.

Umarım anlatabilmişimdir. Yine de anlamadığınız yer olursa veya karışık gelen bir yer varsa daha açık olmaya çalışırım.

2 Beğeni

Burada parametre olarak aldığınız entry yi kullanmadınız, onun yerine burda

Entry() sınıfından yeni bir obje oluşturdunuz. entry parametresi gereksiz oldu.

1 Beğeni

Orada bir karışıklık olmuş, sağ olun. Düzeltiyorum.

1 Beğeni

Kodu söz dizimine uygun yazmanız lazım, attığınız örnekte syntax hataları var.
Sonra:

```
Kod
```

Yazmak yerine şunu yazacaksınız:

```python
Kod
```


self.button5 = ... diyerek değişkeni bir nitelik haline getireceksiniz. Daha sonra Konu sınıfının örneğini paragraf.basfonksiyon fonksiyonuna parametre olarak yollayabilirsiniz.

Eğer Konu.ilkfonksiyon bir örnek metodu olmayacaksa da gerekli değişkenleri return edebilirsiniz.

Sınıf isimlerini büyük harflerle başlatmanız daha uygun olur.

1 Beğeni

En son bir forma üye olduğumdan beri çok zaman geçti. Buradaki arkadaşların sıcak ilgisi ve sabırları bana cesaret verdi. Yanılmadığım için mutluyum.

Yazbel dökümanlarından SINIF konusunu okumaya karar verdim. Ne olur ne olmaz fonksiyonlardan başladım. Yine de temel mantığı anlama açısından bir kaç soru sorayım.

  • icmal(_display=self) : icmal isimli sınıfı çağır. _display isimli parametreye self değişkenini ata.
    neden self?

bu fonksiyonu çağırırken _display değişkenine self demiştik. bu sebeple
__display.ekranlar.select(_display.ayarlarekran) yerine self._display.ekranlar.select(_display.ayarlarekran) yazdığımızda neden hata veriyor?

başka bir yerde Display.ekranlar.select(surecbilgiekran) yazdım. O da hata verdi. İlk mesajımdaki kodda da belirtmiştim. Display adında bir sınıfım var. def init in altında self.ekranlar = ttk.Notebook(self.root) isimli nesne var. ekranlar nesnesine eklenmiş sekmelerden birinin adı surecbilgiekran. koddan da anlaşılacağı gibi amacım 4 sekmeden birini seçip aktif edebilmek.