6. Nasıl Yapılır?

6.1. Tkinter’de Fare ve Klavye Hareketleri (Events and Bindings)

Tkinter’de yazdığımız bir uygulamada, kullanıcının fare hareketlerini ve klavyede bastığı tuşları belli fonksiyonlara bağlamak ve bu hareketleri yakalamak bu bölümün konusunu oluşturuyor. Mesela kullanıcı klavyedeki “enter” tuşuna bastığında programımızın özel bir işlemi yerine getirmesini sağlamak, kullanıcının hangi tuşa bastığını bulmak bu konunun kapsamına giriyor. Her zamanki gibi lafı hiç uzatmadan, bir örnekle derdimizi anlatmaya çalışalım. Diyelim ki şöyle bir şey var elimizde:

# -*- coding: utf-8 -*-

from Tkinter import *

pencere = Tk()

etiket = Label(text="Aşağıdaki kutucuğa adınızı yazıp Tamam düğmesine basın!\n")
etiket.pack()

giris = Entry()
giris.pack()

def kaydet():
    dosya = open("isimler_python.txt","a")
    veri = giris.get()
    veri = unicode.encode(unicode(veri),"utf8")
    dosya.write(veri+"\n")
    dosya.close()
    etiket["text"] = etiket["text"] + u"%s dosyaya başarıyla eklendi\n"%giris.get()
    giris.delete(0,END)

dugme = Button(text="Tamam",command=kaydet)
dugme.pack()

mainloop()

Burada kullanıcı “Tamam” düğmesine bastığında “kaydet()” fonksiyonu içinde tanımlanan işlemler gerçekleştirilecektir. Bu arada, yukarıdaki kodlarda Türkçe karakterleri alabilmek için nasıl bir yol izlediğimize dikkat edin. Dediğimiz gibi, bu programın çalışması için kullanıcının “Tamam” düğmesine basması gerekiyor. Peki biz aynı işlemin “enter” düğmesine basılarak da yapılabilmesini istersek ne olacak? Yani kullanıcının mutlaka “Tamam” düğmesine basmasını zorunlu kılmadan, klavyedeki “enter” tuşuna bastığında da kutucuğa yazılan isimlerin dosyaya eklenmesini nasıl sağlayacağız? İşte bunu yapabilmek için “bind” fonksiyonundan yararlanmamız gerekiyor. Bu fonksiyon, bize klavye ve fare hareketlerini belli fonksiyonlara bağlama ve bu hareketleri yakalama imkanı verecek. Nasıl mı? Hemen görelim:

# -*- coding: utf-8 -*-

from Tkinter import *

pencere = Tk()

etiket = Label(text="Aşağıdaki kutucuğa adınızı yazıp Tamam düğmesine basın!\n")
etiket.pack()

giris = Entry()
giris.pack()

def kaydet(event=None):
    dosya = open("isimler_python.txt","a")
    veri = giris.get()
    veri = unicode.encode(unicode(veri),"utf8")
    dosya.write(veri+"\n")
    dosya.close()
    etiket["text"] = etiket["text"] + u"%s dosyaya başarıyla eklendi\n"%giris.get()
    giris.delete(0,END)

dugme = Button(text="Tamam",command=kaydet)
dugme.pack()

pencere.bind("<Return>",kaydet)

mainloop()

Burada, önceki kodlarımıza yalnızca şu eklemeleri yaptık:

def kaydet(event=None):
    ....

....
pencere.bind("<Return>",kaydet)
...

“kaydet()” fonksiyonuna eklediğimiz “event” parametresi zorunlu bir adlandırma değildir. Hatta oraya “kezban” dahi yazdığınızda programın düzgün çalıştığını göreceksiniz. Ancak oraya bir parametre yazacağınız zaman herhangi bir kelime yerine “event” ifadesini kullanmanızı tavsiye ederim. Çünkü “event” tıpkı sınıflardaki “self” gibi, kemikleşmiştir... Burada dikkat etmemiz gereken iki şey var: Birincisi, o fonksiyonu parametresiz bırakmamak; ikincisi, parametreye varsayılan değer olarak “None” vermek. Eğer bu “None” değerini vermezseniz, kullanıcı “Enter” tuşuna bastığında program çalışır, ama “Tamam” düğmesine bastığında çalışmaz... Çünkü Tkinter “event” parametresinin değerini “command=kaydet” şeklinde gösterdiğimiz koda otomatik olarak atamayacaktır. Bu yüzden, oraya varsayılan değer olarak “None” yazmazsak, “kaydet()” fonksiyonunun bir adet argüman alması gerektiğine” dair bir hata alırız...

Kodlarımıza yaptığımız ikinci ekleme, bind() fonksiyonudur. Burada dikkat etmemiz gereken birkaç şey var. Öncelikle bu “bind()” fonksiyonunu “pencere” ile birlikte kullandığımıza dikkat edin. Bu satırla aslında Tkinter’e şu komutu vermiş oluyoruz:

Ey Tkinter! “pencere” seçili iken, yani etkinken, kullanıcı “Return” düğmesine (veya başka bir söyleyişle “Enter” düğmesine) bastığında “kaydet” adlı fonksiyonu çalıştır!

Artık kullanıcımız, kutucuğa bir isim yazıp “enter” düğmesine bastığında, yazdığı isim dosyaya eklenecektir.

Gördüğünüz gibi, eğer klavyedeki “enter” düğmesini bağlamak istiyorsak, kullanmamız gereken ifade “<Return>”... Peki başka hangi ifadeler var? Mesela “Escape” için bir örnek verelim. Yukarıdaki kodlara ufacık bir ekleme yapıyoruz:

# -*- coding: utf-8 -*-

from Tkinter import *
import sys

pencere = Tk()

etiket = Label(text="Aşağıdaki kutucuğa adınızı yazıp Tamam düğmesine basın!\n")
etiket.pack()

giris = Entry()
giris.pack()

def kaydet(event=None):
    dosya = open("isimler_python.txt","a")
    veri = giris.get()
    veri = unicode.encode(unicode(veri),"utf8")
    dosya.write(veri+"\n")
    dosya.close()
    etiket["text"] = etiket["text"] + u"%s dosyaya başarıyla eklendi\n"%giris.get()
    giris.delete(0,END)

dugme = Button(text="Tamam", command=kaydet)
dugme.pack()

pencere.bind("<Return>", kaydet)
pencere.bind("<Escape>", sys.exit)

mainloop()

Gördüğünüz gibi, klavyedeki “Escape” düğmesini kullanabilmek için kodlarımız içine “<Escape>” ifadesini eklememiz yeterli oluyor. Peki klavyedeki öbür düğmelerin karşılıkları nelerdir? Listeleyelim:

F1:
“<F1>” (Bütün F’li tuşlar aynı şekilde kullanılabilir...)
Num Lock:
“<Num_Lock>”
Scroll Lock:
“<Scroll_Lock>”
Backspace:
“<BackSpace>”
Delete:
“<Delete>”
Sol ok:
“<Left>”
Sağ ok:
“<Right>”
Aşağı ok:
“<Down>”
Yukarı ok:
“<Up>”
“Alt” Düğmesi:
“<Alt_L>”
“Ctrl” Düğmesi:
“<Control_L>”
“Shift” Düğmesi:
“<Shift_L>”
“Ctrl” + s:
“<Control-s>” (Öteki harfler de aynı şekilde kullanılabilir...)
“Shift” + s:
“<Shift-s>” (Öteki harfler de aynı şekilde kullanılabilir...)
“Alt” + s:
“<Alt-s>” (Öteki harfler de aynı şekilde kullanılabilir...)
Boşluk düğmesi:
“<space>”
“Print” düğmesi:
“<Print>”

Klavyedeki harfleri pencere araçlarına bağlamak için ise doğrudan o harfin adını kullanabiliriz. Mesela bir işlemin, “f” harfine basıldığında gerçekleşmesini istersek, yazmamız gereken ifade, “f” olacaktır. Bunun, “<f>” değil, sadece “f” şeklinde olduğuna dikkat edin. Tabii burada, Türkçe’ye özgü harfleri kullanmamaya dikkat ediyoruz...

Klavye hareketlerini bağlamayı öğrendik. Peki ya fare hareketlerini nasıl bağlayacağız? O da çok basittir. Burada da yine bind() fonksiyonundan yararlanacağız. Gelin isterseniz şöyle bir uygulama yazalım:

# -*- coding: utf-8 -*-
from Tkinter import *

pencere = Tk()
pencere.geometry("400x200")

etiket = Label(text="Farenin pencere üzerindeki konumu:")
etiket.pack(anchor=NW)

girisx_etiket = Label(text="X:")
girisx_etiket.pack(side=LEFT, anchor=N)

girisx = Entry(width=5)
girisx.pack(side=LEFT,padx=15, anchor=N)

girisy_etiket = Label(text="Y:")
girisy_etiket.pack(side=LEFT, anchor=N)

girisy = Entry(width=5)
girisy.pack(side=LEFT, anchor=N)

def hareket(event=None):
    girisx.delete(0, END)
    girisy.delete(0, END)
    girisx.insert(0, event.x)
    girisy.insert(0, event.y)

pencere.bind("<Button-1>", hareket)

mainloop()

Yukarıdaki uygulamayı çalıştırıp pencere üzerinde herhangi bir yere farenin sol tuşu ile tıkladığımızda, tıkladığımız noktanın koordinatları (x ve y düzlemine göre) kutucuklar içinde görünecektir.

Bu kodları biraz açıklayalım:

Kodlar arasında gördüğünüz “anchor” ifadesi, pack() adlı pencere yöneticisinin seçeneklerinden biridir. Bu seçenek, ilgili pencere aracının pencere üzerinde bir noktaya “çapa atmasını” sağlar (İngilizce “anchor” kelimesi Türkçe’de “çapa atmak, demir atmak” gibi anlamlara gelir...). Bu seçenek dört farklı değer alabilir:

  • N : “Kuzey” anlamına gelen “North” kelimesinin kısaltmasıdır. Pencere aracının “kuzey” yönüne sabitlenmesini sağlar.
  • S : “Güney” anlamına gelen “South” kelimesinin kısaltmasıdır. Pencere aracının “güney” yönüne sabitlenmesini sağlar.
  • W : “Batı” anlamına gelen “West” kelimesinin kısaltmasıdır. Pencere aracının “batı” yönüne sabitlenmesini sağlar.
  • E : “Doğu” anlamına gelen “East” kelimesinin kısaltmasıdır. Pencere aracının “doğu” yönüne sabitlenmesini sağlar.

Bu yönleri birarada da kullanabiliriz. Örneğin pencere aracının “kuzeybatı” yönünde sabitlenmesini istersek, kullanmamız gereken ifade “NW” olacaktır. Mesela yukarıdaki kodlarda bunu kullandık. “etiket.pack(anchor=NW)” ifadesi yardımıyla, etiket adlı pencere aracımızın kuzeybatı yönüne çapa atmasını sağladık (Yani pencerenin üst-sol ucuna...). Öteki pencere araçlarında sadece “anchor=N” ifadesini kullandık. Çünkü bu araçlardaki “side=LEFT” ifadesi, aracımızın sol yana yerleşmesini zaten sağlıyor... pack() yöneticisi içinde kullandığımız “padx” seçeneğini zaten biliyorsunuz. Bu seçenek yardımıyla pencere araçlarının “dirsek mesafesini” ayarlıyoruz...

Yukarıdaki kodlar içinde en önemli nokta tabii ki hareket() adlı fonksiyon. Burada parantez içinde “event” parametresini belirtmeyi unutmuyoruz. Kabaca ifade etmek gerekirse, bu parametre bizim örneğimizde fare hareketlerini temsil ediyor... Buna göre, fonksiyon içindeki “event.x” ve “event.y” ifadelerini, “farenin x düzlemi üzerindeki hareketi” ve “farenin y düzlemi üzerindeki hareketi” şeklinde düşünebiliriz... Bu arada, “giris.delete()” fonksiyonları yardımıyla, Entry aracının içini sürekli olarak boşalttığımıza dikkat edin. Eğer giris.delete() fonksiyonlarını yazmazsak, fare tıklaması ile bulduğumuz eski ve yeni koordinatlar birbirine karışacaktır...

mainloop() satırından hemen önce, programımızdaki asıl işi yapan bind() fonksiyonunu görüyoruz. Farenin sol düğmesini “Button-1” ifadesinin temsil ettiğini görüyorsunuz. Peki buna benzer başka neler var? Görelim:

Button-2 :
Farenin orta düğmesine (veya tekerine) basılması
Button-3 :
Farenin sağ düğmesine basılması
Motion :
Farenin, hiç bir düğme basılı değilken hareket ettirilmesi
B1-Motion :
Farenin, sol düğme basılı halde hareket ettirilmesi
B2-Motion :
Farenin, orta düğme (veya teker) basılı halde hareket ettirilmesi
B3-Motion :
Farenin, sağ düğme basılı halde hareket ettirilmesi
ButtonRelease-1 :
Farenin sol düğmesinin serbest bırakılması (yani düğmeye basıldığındaki değil, düğmenin bırakıldığındaki hali...)
ButtonRelease-2 :
Farenin orta düğmesinin (veya tekerinin) serbest bırakılması
ButtonRelease-3 :
Farenin sağ düğmesinin serbest bırakılması
Double-Button-1 :
Farenin sol düğmesine çift tıklanması
Double-Button-2 :
Farenin orta düğmesine (veya tekerine) çift tıklanması
Double-Button-3 :
Farenin sağ düğmesine çift tıklanması
Enter :
Farenin pencere aracının üzerine gelmesi (Bunun “enter” tuşuna basmak anlamına gelmediğine dikkat edin...)
Leave :
Farenin pencere aracını terketmesi

Sırada klavye hareketlerini yakalamak var. Yani şimdiki görevimiz, bir kullanıcının klavyede hangi tuşlara bastığını bulmak. Bunun için “keysym” metodundan yararlanacağız:

# -*- coding: utf-8 -*-
from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

etiket = Label(text="Basılan Tuş:\n",wraplength=150)
etiket.pack()

def goster(event=None):
    etiket["text"] += event.keysym

pencere.bind("<Key>",goster)

mainloop()

Yine kodlarımızı biraz açıklayalım:

Öncelikle, gördüğünüz gibi, etiket adlı pencere aracımız içinde “wraplength” adlı bir seçenek kullandık. Bu seçenek etikete yazdırılabilecek metnin uzunluğunu sınırlandırıyor. Biz bu değeri 150 piksel olarak belirledik. Buna göre, etikete yazılan metin 150 pikseli aştığında kendiliğinden bir alt satıra geçecektir.

Şimdi fonksiyonumuza bir göz atalım: Burada “event.keysym” adlı bir ifade görüyoruz. Bu ifade, klavye üzerindeki sembolleri, yani tuşları temsil ediyor. Fonksiyonumuz içinde yazdığımız koda göre, kullanıcının girdiği klavye sembolleri ana penceremiz üzerindeki etikete yazdırılacak. Bir sonraki satırda yine bind() fonksiyonunu kullanarak, goster() adlı fonksiyon ile “<Key>” adlı özel ifadeyi birbirine bağlıyoruz. Bu “<Key>” ifadesi, kullanıcının klavye üzerindeki tuşlara basma hareketini temsil ediyor. Kodlarımızı bir bütün halinde düşünürsek, yukarıdaki uygulama, kullanıcının klavyede bastığı bütün tuşları ana pencere üzerindeki etikete atayacaktır. Bu uygulama yardımıyla, esasında “salt okunur” (read-only) bir pencere aracı olan “Label”i “yazılabilir” (writable) hale getirmiş oluyoruz (en azından görünüş olarak...).

Öğrendiğimiz bu “keysym” metodu yardımıyla bazı faydalı işler de yapabiliriz. Mesela bir “Entry” aracına kullanıcı tarafından girilen bir metnin silinmesini engelleyebiliriz. Nasıl mı?

# -*- coding: utf-8 -*-
from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

giris = Entry()
giris.pack()

def silemezsin(event=None):
    if event.keysym == "BackSpace" or event.keysym == "Delete":
        return "break"

giris.bind("<Key>", silemezsin)

mainloop()

Buradaki kodlara göre, eğer kullanıcı “BackSpace” veya “Delete” tuşlarına basarak yazdığı yazıyı silmek isterse, beklentilerinin aksine bu tuşlar çalışmayacaktır... Burada return “break” adlı özel bir ifadeden yararlanıyoruz. Bu ifade, normal şartlarda gerçekleşmesi engellenemeyecek olan işlemlerin etkisizleştirilmesini sağlayan özel bir kullanımdır... Örneğin yukarıdaki “silemezsin()” fonksiyonunu bir de şöyle yazmayı deneyin:

def silemezsin(event=None):
    if event.keysym:
        return "break"

Bu şekilde, kullanıcı Entry aracı içine hiçbir şekilde yazı yazamayacaktır. Dolayısıyla yukarıdaki fonksiyonun adını “silemezsin” yerine “yazamazsın” koymak daha uygun olacaktır!...

Son olarak, bu konuyla ilgili olduğu için, “focus_set()” fonksiyonundan da bahsetmekte fayda var. Bu fonksiyon, pencere araçlarını etkin hale getirmemizi sağlıyor. Bu bölümün en başında verdiğimiz örneği hatırlıyorsunuz. Kullanıcı bir kutucuğa adını giriyor ve girdiği bu ad bir dosyaya yazdırılıyordu. O örneği isterseniz yeniden çalıştırın. Göreceğiniz gibi, kutucuğa bir şey yazabilmek için öncelikle o kutucuğa bir kez tıklamak gerekiyor. Yani aslında programımız ilk açıldığında o kutucuk etkin değil. Bizim onu etkin hale getirmek için üzerine bir kez tıklamamız gerekiyor. Ama istersek, o kutucuğun açılışta etkin hale gelmesini sağlayabiliriz. Böylece kullanıcılarımız doğrudan kutuya yazmaya başlayabilirler:

# -*- coding: utf-8 -*-
from Tkinter import *

pencere = Tk()

etiket = Label(text="Aşağıdaki kutucuğa adınızı yazıp Tamam düğmesine basın!\n")
etiket.pack()

giris = Entry()
giris.pack()
giris.focus_set()

def kaydet():
    dosya = open("isimler_python.txt","a")
    veri = giris.get()
    veri = unicode.encode(unicode(veri),"utf8")
    dosya.write(veri+"\n")
    dosya.close()
    etiket["text"] = etiket["text"] + u"%s dosyaya başarıyla eklendi\n"%giris.get()
    giris.delete(0,END)

dugme = Button(text="Tamam", command=kaydet)
dugme.pack()

mainloop()

Kodlar içinde yaptığımız eklemeyi koyu renkle gösterdim. “giris.focus_set()” fonksiyonu sayesinde, programımız açılır açılmaz kutucuk etkin hale geliyor. Böylece hemen adımızı yazmaya başlayabiliyoruz.

Bu konuyla bağlantılı olan şu örneğe bakalım bir de:

# -*- coding: utf-8 -*-

from Tkinter import *
from sys import exit

pencere = Tk()

etiket = Label(text="Programdan çıkmak istediğinize emin misiniz?")
etiket.pack()

cerceve = Frame()
cerceve.pack()

def sagol(event=None):
    yeni = Toplevel()
    etiket = Label(yeni, text="Teşekkürler... :)")
    yeni.bind("<Return>", exit)
    etiket.pack()

evet = Button(cerceve, text="Evet", command=pencere.destroy)
evet.grid(row=1,column=0)

hayir = Button(cerceve, text="Hayır", command=sagol)
hayir.grid(row=1,column=1)

evet.bind("<Return>", exit)
hayir.bind("<Return>", sagol)

mainloop()

Gördüğünüz gibi, klavyedeki “enter” tuşunu bazı fonksiyonlara bağladığımız halde, programı çalıştırdığımızda “enter” tuşuna basmak hiçbir işe yaramıyor. Bunun nedeni, programımız ilk açıldığında pencerenin kendisi hariç hiç bir pencere aracının etkin olmamasıdır. Bağladığımız tuşların çalışabilmesi için öncelikle ilgili pencere araçlarının etkinleştirilmesi gerekiyor:

# -*- coding: utf-8 -*-

from Tkinter import *
from sys import exit

pencere = Tk()

etiket = Label(text="Programdan çıkmak istediğinize emin misiniz?")
etiket.pack()

cerceve = Frame()
cerceve.pack()

def sagol(event=None):
    yeni = Toplevel()
    etiket = Label(yeni, text="Teşekkürler... :)")
    yeni.bind("<Return>", exit)
    etiket.pack()

kapat = Button(cerceve, text="Evet", command=pencere.destroy)
kapat.grid(row=1, column=0)

kapatma = Button(cerceve, text="Hayır", command=sagol)
kapatma.grid(row=1, column=1)

kapat.focus_set()

kapat.bind("<Return>", exit)
kapatma.bind("<Return>", sagol)

mainloop()

Buradaki tek fark, kodlar arasına “kapat.focus_set()” ifadesinin eklenmiş olması. Bu ifade sayesinde, programımız ilk açıldığında odak “Evet” düğmesi üzerinde olacaktır. Dolayısıyla “enter” tuşuna bastığımızda, bu düğmenin bağlı olduğu fonksiyon çalışacak ve programımız kapanacaktır. Program açıkken, klavyedeki “tab” tuşuna basarak odağı değiştirebiliriz. Mesela programımızı ilk çalıştırdığımızda odak “Evet” düğmesi üzerinde olacağı için, “tab” tuşuna bir kez bastığımızda odak “Hayır” düğmesine geçecektir. Bu haldeyken “enter” tuşuna basarsak, “Hayır” düğmesinin bağlı olduğu fonksiyon çalışacaktır...

İsterseniz konuyu kapatmadan önce basit bir “oyun” denemesi yapalım:

Önce http://www.akcilaclama.com/images/sinek.ilaclama.jpg adresine gidip oradaki sinek resmini bilgisayarınıza indirin. Adını da “sinek.jpg” koyun... Daha sonra şu kodları yazın:

# -*- coding: utf-8 -*-
from Tkinter import *

import random, ImageTk

pencere = Tk()
pencere.geometry("600x600")
pencere.tk_setPalette("white")

def yakala(event=None):
        k = random.randint(0,550)
        v = random.randint(0,550)
        print k, v
        dugme.place(x=k, y=v)

simge = ImageTk.PhotoImage(file="sinek.jpg")
dugme = Button(image=simge, command=yakala, relief="flat")
dugme.place(x=1, y=0)

dugme.bind("<Enter>", yakala)

mainloop()

Gördüğünüz gibi, “Enter” ifadesi kullanıcının “enter” tuşuna bastığı anlamına gelmiyor. O iş için “Return” ifadesini kullanıyoruz. Fazlasıyla birbirine karıştırılan bir konu olduğu için özellikle vurgulama gereği duyuyorum...

Burada sineği yakalama ihtimaliniz var. Eğer random’un ürettiği sayılar birbirine yakın olursa sinek elinizden kaçamayabilir...

Böylelikle bu konuyu da bitirmiş olduk. Her ne kadar başlangıçta karışıkmış gibi görünse de aslında hem çok kolay hem de çok keyifli bir konudur fare ve klavye hareketleri konusu... Bu yazıyı birkaç kez gözden geçirerek bazı noktaların tam olarak zihninize yerleşmesini sağlayabilirsiniz.

6.2. “Listbox” Öğelerine Görev Atamak

Daha önce “Listbox” (Liste kutusu) adlı pencere aracının ne olduğunu ve nasıl kullanıldığını öğrenmiştik. Tkinter’de liste kutularını şöyle oluşturuyorduk:

from Tkinter import *

pencere = Tk()

listekutusu = Listbox()
listekutusu.pack()

mainloop()

İsterseniz bu liste kutusunun daha belirgin olması için buna birkaç öğe ekleyelim:

from Tkinter import *

pencere = Tk()

listekutusu = Listbox()
listekutusu.pack()

dagitimlar = ["Debian", "Ubuntu", "Mandriva", "Arch", "Gentoo"]

for i in dagitimlar:
    listekutusu.insert(0, i)

mainloop()

Elbette “dagitimlar” adlı listedeki öğelerin “Listbox”taki konumlanışını ters de çevirebiliriz:

from Tkinter import *

pencere = Tk()

listekutusu = Listbox()
listekutusu.pack()

dagitimlar = ["Debian", "Ubuntu", "Mandriva", "Arch", "Gentoo"]

for i in dagitimlar:
    listekutusu.insert(END, i)

mainloop()

Burada “listekutusu.insert()” satırında bir önceki kodlarda “0” sıra numarasını verirken, yukarıdaki kodlarda “END” ifadesini kullandığımıza dikkat edin.

Gelin şimdi isterseniz bu liste öğelerinin her birine bir görev atayalım. Yani mesela kullanıcı “Debian” adlı öğenin üzerine çift tıkladığında kendisine Debian GNU/Linux hakkında bilgi verelim:

#-*-coding: utf-8-*-
from Tkinter import *

pencere = Tk()
pencere.geometry("400x200")

listekutusu = Listbox()
listekutusu["relief"] = "raised"
listekutusu.pack(side=LEFT, anchor=NW, fill=BOTH)

etiket = Label()
etiket["text"] = ""

dagitimlar = ["Debian", "Ubuntu", "Mandriva", "Arch", "Gentoo"]

for i in dagitimlar:
    listekutusu.insert(END, i)

def gosterici(event):
    etiket["text"] = "%s bir GNU/Linux dağıtımıdır!"%listekutusu.get(ACTIVE)
    etiket.pack()

listekutusu.bind("<Double-Button-1>", gosterici)

mainloop()

Burada bizi ilgilendiren kısım şu satır:

listekutusu.bind("<Double-Button-1>", gosterici)

Liste kutusuna görev atama işlemini bu satır yardımıyla yaptık. Kullanıcı listekutusu içindeki herhangi bir öğeye çift tıkladığı zaman, tıklanan öğeyle ilgili bilgi veriyoruz. Listbox() adlı pencere aracının kendisine ait bir “command” seçeneği olmadığı için, istediğimiz işlevi, daha önceki derslerde gördüğümüz “bind” metodu yardımıyla hallediyoruz. Burada “Double-Button-1” ifadesini kullandığımıza dikkat edin. Bu ifade farenin sol tuşuna çift tıklama hareketini temsil ediyor. Liste kutularındaki öğelere işlev atamak istediğimiz zaman en doğru sonucu “Double-Button-1” ile elde ediyoruz. Öteki seçenekler her zaman istediğimiz işlevi yerine getirmeyebilir...

6.3. Pencereleri Başlıksız Hale Getirmek

Tkinter’de standart bir pencerenin nasıl oluşturulacağını biliyoruz:

pencere = Tk()

Bu şekilde bir pencere oluşturduğumuzda, elde ettiğimiz pencere, bir pencerenin sahip olması gereken bütün özellikleri taşır. Dolayısıyla pencereyi kapatmak, pencereyi küçültmek veya pencereyi aşağı indirmek istediğimizde özel olarak herhangi bir kod yazmamıza gerek yoktur. Pencere başlığı üzerindeki çarpı, kare ve eksi düğmelerine tıklayarak gerekli işlevleri yerine getirebiliriz. Normal şartlar altında bu tür özelliklerin olmadığı bir pencere pek bir işimize yaramaz. Ama bazen pencerenin sürükleme çubuğu dahil hiç bir özelliğinin olmamasını isteyebiliriz. İşte bu bölümde bu isteğimizi nasıl yerine getirebileceğimizi göreceğiz. Şimdi normal bir şekilde programımızı yazalım:

#!/usr/bin/env python
#-*-coding:utf-8-*-

from Tkinter import *

pencere = Tk()

mainloop()

Bu şekilde içi boş bir pencere oluşturduk. Şimdi bu kodlara şöyle bir şey ekleyelim:

pencere.overrideredirect(1)

Burada “overrideredirect” kelimesinin yazılışına azami özen göstermek gerekir. Uzun bir kelime olduğu için yanlış yazılma olasılığı oldukça yüksektir.

Kodlarımızın son hali şöyle oldu:

#!/usr/bin/env python
#-*-coding:utf-8-*-

from Tkinter import *

pencere = Tk()
pencere.overrideredirect(1)

mainloop()

Yalnız bu kodları mutlaka komut satırından çalıştırın. Kesinlikle yazdığınız betiğin üzerine çift tıklayarak çalıştırmayı denemeyin.

Şimdi komut satırını kullanarak betiğimizi çalıştırıyoruz. Gördüğünüz gibi, bomboş bir kare elde ettik. Bu pencerenin, kendisini kapatmamızı sağlayacak bir düğmesi bile yok... Şimdi sanırım neden bu betiği komut satırından çalıştırdığımızı anlamışsınızdır. Bu başlıksız pencereyi CTRL+C (GNU/Linux) veya CTRL+Z (Windows) tuşlarına basarak kapatabilirsiniz. Ya da doğrudan komut ekranını kapatarak da bu pencereyi ortadan kaldırabilirsiniz.

Peki böyle başlıksız bir pencere oluşturmak ne işimize yarar? Mesela bu başlıksız pencereleri, bir düğmeye tıklandığında aşağıda doğru açılan bir menü olarak kullanabiliriz. Şu örneğe bakalım:

#!/usr/bin/env python
#-*-coding:utf-8-*-

from Tkinter import *

pencere = Tk()


def liste():
    yenipencere = Toplevel()
    x = dgm.winfo_rootx()
    y = dgm.winfo_rooty() + dgm.winfo_height()
    yenipencere.geometry('+%d+%d' % (x,y))
    menu = Listbox(yenipencere)
    menu.pack()

    yenipencere.overrideredirect(1)

    dagitimlar = ["Debian", "Ubuntu", "Mandriva", "Arch", "Gentoo"]

    for i in dagitimlar:
        menu.insert(0, i)

dgm = Button(text="deneme", command=liste)
dgm.pack(fill=X)

mainloop()

Bu kodlar içinde geçen “x” ve “y” değişkenlerinin ne olduğunu bir sonraki bölümde inceleyeceğiz. Şimdilik bu değişkenlere fazla takılmayalım. Bu “x” ve “y” değişkenleri “yenpencere”nin ana pencereye ve bunun üzerindeki “deneme” adlı düğmeye göre konumunu belirlememizi sağlıyor. Burada “deneme” adlı düğmeye tıkladığımızda bir seçim kutusu açıldığını görüyoruz. Elbette burada sadece kavramsal olarak durumu göstermek için bir örnek verdik. Şu haliyle yukarıdaki kodlar oldukça eksik. Amacım yalnızca başlıksız pencerelerle neler yapılabileceğine dair ufak bir örnek göstermekti... Yukarıdaki kodları işe yarar bir hale getirmek için üzerinde biraz çalışmanız gerekir.

Hatırlarsanız, önceki bölümlerden birinde Pencere Araçlarına İpucu Metni Eklemek” başlıklı bir konu işlemiştik. Orada gösterdiğimiz “wcktooltips.py” adlı modülde de bu “overrideredirect” özelliğinden yararlanılıyor. Şu adrese gidip wcktooltips modülünün kaynağına baktığımızda kodlar arasında “self.popup.overrideredirect(1)” satırını görüyoruz. Yani Fredrik Lundh ipucu penceresini oluşturmak için başlıksız pencerelerden yararlanmış... Gördüğünüz gibi başlıksız pencereler pek çok yerde işimize yarayabiliyor.

6.4. Pencere/Ekran Koordinatları ve Boyutları

Bu bölümde bir pencerenin veya üzerinde çalıştığımız ekranın koordinatlarını ve boyutlarını nasıl öğrenebileceğimizi inceleyeğiz. Peki bu bilgi ne işimize yarayacak? Mesela yazdığımız program ilk açıldığında kullanıcının ekranını ortalasın istiyorsak, programımızın çalıştırıldığı bilgisayar monitörünün boyutlarını/çözünürlüğünü bilmemiz gerekir, ki buna göre ekranın orta noktasını hesaplayabilelim... Ayrıca yazdığımız program içindeki pencere araçlarını da özellikle belli noktalarda konumlandırmak istiyorsak pencere ve üzerindeki araçların koordinatlarını bilmemiz gerekir. İsterseniz öncelikle hangi metotlardan yararlanabileceğimize bakalım:

>>> from Tkinter import *

>>> pencere = Tk()

>>> for i in dir(pencere):
...     if i.startswith("winfo"):
...         print i
...
winfo_atom
winfo_atomname
winfo_cells
winfo_children
winfo_class
winfo_colormapfull
winfo_containing
winfo_depth
winfo_exists
winfo_fpixels
winfo_geometry
winfo_height
winfo_id
winfo_interps
winfo_ismapped
winfo_manager
winfo_name
winfo_parent
winfo_pathname
winfo_pixels
winfo_pointerx
winfo_pointerxy
winfo_pointery
winfo_reqheight
winfo_reqwidth
winfo_rgb
winfo_rootx
winfo_rooty
winfo_screen
winfo_screencells
winfo_screendepth
winfo_screenheight
winfo_screenmmheight
winfo_screenmmwidth
winfo_screenvisual
winfo_screenwidth
winfo_server
winfo_toplevel
winfo_viewable
winfo_visual
winfo_visualid
winfo_visualsavailable
winfo_vrootheight
winfo_vrootwidth
winfo_vrootx
winfo_vrooty
winfo_width
winfo_x
winfo_y

Gördüğünüz gibi, elimizde epey metot var. Yukarıdaki kod yardımıyla Tk() sınıfı içindeki, “winfo” ile başlayan bütün metotları listelediğimize dikkat edin... Biz bu bölümde bu metotların hepsini değil, bunlar arasında en sık kullanılanları inceleyeceğiz. İnceleyeceğimiz metotlar şunlar olacak:

winfo_height

winfo_width

winfo_rootx

winfo_rooty

winfo_screenheight

winfo_screenwidth

winfo_x

winfo_y

Hemen ilk metodumuzla işe başlayalım:

winfo_height()

Bu metot, oluşturduğumuz pencerenin (veya pencere aracının) yüksekliği hakkında bilgi verir bize. Şöyle bir örnekle durumu görelim:

>>> pencere = Tk()
>>> yukseklik = pencere.winfo_height()
>>> print yukseklik

1

Galiba bu çıktı pek fazla bir şey anlatmıyor bize. Bu kodlardan böyle bir çıktı almamızın nedeni bir pencere ilk oluşturulduğunda yükseklik değerinin “1” olarak kabul edilmesidir. Eğer pencerenin gerçek boyutunu görmek istersek yukarıdaki kodları şu şekilde yazmamız gerekir:

pencere = Tk()

pencere.update()

yukseklik = pencere.winfo_height()
print yukseklik

mainloop()

Burada kullandığımız “pencere.update()” komutu penceremizin mevcut durumunu güncellememizi sağlıyor. Dolayısıyla bu komut bize doğru bir şekilde “200” gibi bir çıktı veriyor. Buradan aldığımız sonuca göre, oluşturduğumuz pencerenin yüksekliği 200 piksel. Dediğimiz gibi, bu metodu yalnızca pencere üzerine uygulamak zorunda değiliz. Aynı metodu pencere araçlarıyla birlikte de kullanabiliriz:

from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

btn = Button(text="deneme")
btn.pack()

btn.update()

yukseklik = btn.winfo_height()

print yukseklik

mainloop()

Burada da düğme bilgilerini güncellemek için “btn.update()” gibi bir komuttan yararlandığımıza dikkat edin. Eğer bu örnekte update() metodunu kullanmazsak biraz önce olduğu gibi, alacağımız çıktı “1” olacaktır.

Elbette “winfo_height()” metodunu bir değişkene atamadan da kullanabiliriz. Ama açıkçası o şekilde pek pratik olmayacaktır...

Şimdi sıra geldi winfo_width metodunu incelemeye:

winfo_width()

İlk incelediğimiz metot olan “winfo_height”, bir pencere veya pencere aracının yüksekliğini veriyordu. “winfo_width()” metodu ise pencere veya pencere aracının genişliğini verir. Hemen görelim:

from Tkinter import *

pencere = Tk()

btn = Button(text="deneme")
btn.pack()

pencere.update()

genislik = pencere.winfo_width()

print genislik

mainloop()

Buradan aldığınız çıktı, pencerenin genişliğini gösterir. Eğer pencerenin değil de düğmenin genişliğini almak isterseniz ne yapmanız gerektiğini tahmin edebilirsiniz:

from Tkinter import *

pencere = Tk()

btn = Button(text="deneme")
btn.pack()

btn.update()

genislik = btn.winfo_width()

print genislik

mainloop()

Muhtemelen aldığınız çıktı, pencereden aldığınız çıktı ile aynı olacaktır. Çünkü Tkinter pencere üzerindeki araçlara göre pencerenin boyutunu ayarlıyor. Burada da Tkinter pencere üzerinde tek bir pencere aracı olduğu için, pencereyi “deneme” adlı düğmenin boyutu kadar ufalttı. Dolayısıyla pencerenin kendisi ve düğme aynı genişliğe sahip oldu... Pencerenin üzerinde birden fazla pencere aracı olduğu durumlarda winfo_width() metodunun işlevi daha net görülecektir.

winfo_rootx()

Bu metot pencere veya pencere araçlarının x düzlemi üzerindeki koordinatını verir. Mesela:

from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

btn = Button(text="deneme")
btn.pack()

pencere.update()

xkoord = pencere.winfo_rootx()

print xkoord

mainloop()

Ben bu kodları kendi bilgisayarımda çalıştırdığımda “965” çıktısını aldım. Benim monitörümün çözünürlüğü “1440x900”. Demek ki bu pencerenin sol kenarı, ekranın soldan sağa 965’inci noktasına denk geliyormuş. Bir de şuna bakalım:

from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

btn = Button(text="deneme")
btn.pack()

btn.update()

xkoord = btn.winfo_rootx()

print xkoord

mainloop()

Bu kodları çalıştırdığımda ise konsolda “1027” çıktısını gördüm. Demek ki “deneme” adlı düğmenin sol kenarı “1440x900”lük monitörün 1027’inci noktasına denk geliyormuş...

winfo_rooty()

Bir önceki metodumuz olan winfo_rootx() x-koordinatlarının bilgisini veriyordu. winfo_rooty() ise y-koordinatlarının bilgisini verir:

from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

btn = Button(text="deneme")
btn.pack()

pencere.update()

ykoord = pencere.winfo_rooty()

print ykoord

mainloop()

Buradan aldığımız sonuç, pencerenin üst sınırının y düzlemi üzerinde hangi noktaya karşılık geldiğini gösteriyor. Penceremiz ekranın en tepesinde bile olsa bu kodlar “0” veya “1” gibi bir sonuç vermez. Çünkü pencere başlığının hemen altındaki alanın karşılık geldiği nokta hesaplanmaktadır. Bir de şu örneğe bakalım:

from Tkinter import *

pencere = Tk()
pencere.geometry("200x200")

btn = Button(text="deneme")
btn.pack(pady=30)

btn.update()

ykoord = btn.winfo_rooty()

print ykoord

mainloop()

Burada “pady” seçeneğini kullanarak düğmeyi biraz aşağıya kaydırdık, ki bir önceki kodla arasındaki farkı görebilelim...

winfo_screenheight()

Bu metot ekran yüksekliğinin kaç olduğunu söyler. Örneğin 1024x768’lik bir çözünürlüte bu metodun verdiği değer 768 olacaktır...

from Tkinter import *

pencere = Tk()

ekran_y = pencere.winfo_screenheight()

print ekran_y

mainloop()

winfo_screenwidth()

Bu metot da winfo_screenheight() metoduna benzer. Ama onun aksine, bir pencerenin yüksekliğini değil, genişliğini verir. Dolayısıyla 1024x768’lik bir ekran çözünürlüğünde bu değer 1024 olacaktır:

from Tkinter import *

pencere = Tk()

ekran_g = pencere.winfo_screenwidth()

print ekran_g

mainloop()

En önemli “winfo” metotlarını gördüğümüze göre bunları kullanarak bazı yararlı işler yapmaya başlayabiliriz...

6.5. Programı Tam Ekran olarak Çalıştırmak

Geçen bölümde “winfo” metotlarını gördüğümüze göre, bu bölümde bu metotları kullanarak bazı faydalı işler yapmaya çalışacağız... Bu metotlar yazdığımız programları istediğimiz şekilde konumlandırmada ve boyutlandırmada bize epey yardımcı olacaklardır. Örneğin geçen bölümde öğrendiklerimizi kullanarak, yazdığımız bir programı tam ekran çalıştırabiliriz:

from Tkinter import*

pencere = Tk()
gen = pencere.winfo_screenwidth()
yuks = pencere.winfo_screenheight()
pencere.geometry("%dx%d"%(gen, yuks))

dgm = Button(text="~~~~~~~~~~~~TAM EKRAN~~~~~~~~~~")
dgm.pack(expand=YES, fill=BOTH)

pencere.mainloop()

Burada yaptığımız şey şu: Önce “pencere.winfo_screenwidth()” ifadesi yardımıyla ekran genişliğini alıyoruz. Daha sonra “pencere.winfo_screenheight()” ifadesini kullanarak ekran yüksekliğini öğreniyoruz. Bu bilgileri, kullanım kolaylığı açısından “gen” ve “yuks” adlı iki değişkene atadık. Ardından da “pencere.geometry” içinde bu değerleri kullanarak programımızın ilk açılışta ekranın tamamını kaplamasını sağladık.

Python’un arkaplanda neler çevirdiğini daha net görmek için, kullandığımız bu “gen” ve “yuks” değişkenlerini ekrana yazdırmak isteyebilirsiniz...

6.6. Ekranı Ortalamak

“winfo” metotlarını kullanarak, yazdığımız bir programın ilk açıldığında ekranın tam ortasına denk gelmesini de sağlayabiliriz:

from Tkinter import*

pencere = Tk()

pgen = 200
pyuks = 200

ekrangen = pencere.winfo_screenwidth()
ekranyuks = pencere.winfo_screenheight()

x = (ekrangen - pgen) / 2
y = (ekranyuks - pyuks) / 2

pencere.geometry("%dx%d+%d+%d"%(pgen, pyuks, x, y))

pencere.mainloop()

Burada önce “pgen” ve “pyuks” değişkenleri içinde programımızın genişliğini ve yüksekliğini belirttik. Bunları elbette doğrudan “pencere.geometry” ifadesi içine de yerleştirebilirdik. Ama bu iki değerle bazı hesaplamalar yapacağımız için, en iyisi bunları bir değişken içine atmak.

İlk değişkenlerimizi tanımladıktan sonra ekran genişliğini ve ekran yüksekliğini öğreniyoruz. Bunun için “winfo_screenwidth()” ve “winfo_screenheight()” metotlarını kullandık. Yine kullanım kolaylığı açısından bu iki değeri sırasıyla “ekrangen” ve “ekranyuks” adlı değişkenlere atıyoruz.

Şimdi programımızın ekranı tam ortalayabilmesi için ufak bir hesap yapmamız gerekecek... “x” değerini bulabilmek için ekran genişliğinden programımızın pencere genişliğini çıkarıp, elde ettiğimiz değeri ikiye bölüyoruz. Mesela eğer kullandığımız ekran çözünürlüğü “1024x768” ise, “x” değeri şöyle olacaktır:

x = (1024 - 200) / 2

“y” değerini bulmak için de benzer bir şekilde ekran yüksekliğinden program yüksekliğini çıkarıp, bu değeri yine ikiye bölüyoruz. Dolayısıyla “y” değeri “1024x768”lik bir ekranda şöyle olur:

y = (768 - 200) / 2

Bu hesaplamadan elde ettiğimiz verileri pencere.geometry() içinde uygun yerlere yerleştirdiğimizde, programımız ekranın tam ortasında açılacaktır....

6.7. Pencereleri Her Zaman En Üstte Tutmak

Bazen ana pencereye ek olarak ikinci bir pencere daha oluşturmamız gerekir. Ancak bazen programın işleyişi sırasında, aslında hep üstte durması gereken bu ikinci pencerenin, ana pencerenin arkasına düştüğünü görürüz. Böyle bir durumda yapmamız gereken şey, ikinci pencerenin daima üstte kalmasını sağlayacak bir kod yazmaktır. Neyse ki Tkinter bize böyle bir durumda kullanılmak üzere faydalı bir metot sunar. Bu metodun adı “transient()”. İsterseniz hemen bununla ilgili bir örnek yapalım. Diyelim ki şöyle bir uygulamamız var:

#!/usr/bin/env python
#-*-coding:utf-8-*-

from Tkinter import *

from tkFileDialog import askopenfilename

pencere = Tk()

pgen = 200
pyuks = 200

ekrangen = pencere.winfo_screenwidth()
ekranyuks = pencere.winfo_screenheight()

x = (ekrangen - pgen) / 2
y = (ekranyuks - pyuks) / 2

pencere.geometry("%dx%d+%d+%d"%(pgen, pyuks, x, y))

def yeniPencere():
    yeni = Toplevel()

    xkonum = pencere.winfo_rootx()
    ykonum = pencere.winfo_rooty()
    yeni.geometry("+%d+%d"%(xkonum, ykonum))

    ybtn = Button(yeni, text="Dosya aç", command=yeniDosya)
    ybtn.pack()

def yeniDosya():
    dosya = askopenfilename()

btn = Button(pencere, text="yeni pencere aç", command=yeniPencere)
btn.pack()

mainloop()

Burada gördüğümüz tkFileDialog modülünün şimdilik üzerinde durmayalım. Birkaç bölüm sonra bu ve benzeri modülleri ayrıntılı olarak inceleyeceğiz. Biz şimdilik bu modülün dosya açma işlemlerinde kullanıldığını bilelim yeter...

Programımızın, ekranın tam ortasında açılacak şekilde ayarlandığına dikkat edin. Aynı şekilde ikinci pencere de ana pencerenin üzerinde açılacak şekilde ayarlandı. Bu işlemleri “winfo” metotları yardımıyla yaptığımızı görüyorsunuz.

Bu programı çalıştırdığımızda pencere araçlarının biraz tuhaf davrandığını görebilirsiniz. Mesela ikinci pencere üzerindeki “dosya aç” düğmesine bastığımızda açılan dosya seçme penceresi ikinci pencerenin altında kalıyor olabilir. Aynı şekilde, dosya seçme ekranında “Cancel” tuşuna bastığımızda ikinci pencere bir anda ortadan kaybolacak ve ana pencerenin arkasına gizlenecektir. Bu program belki her sistemde aynı tepkiyi vermeyebilir. Hatta belki bazı sistemlerde bu şekilde bile düzgün çalışıyor olabilir. Ama bizim istediğimiz şey, programımızın hemen her sistemde mümkün olduğunca aynı şekilde çalışmasını sağlamak... O halde hemen gerekli kodları yazalım. Yazacağımız kod çok basittir. Tek yapmamız gerekn, ikinci pencerenin ana pencereye göre üstte kalmasını garanti etmek. Bunu şu satırla yapacağız:

yeni.transient(pencere)

Yani kodlarımızın son hali şöyle olacak:

#!/usr/bin/env python
#-*-coding:utf-8-*-

from Tkinter import *
from tkFileDialog import askopenfilename

pencere = Tk()

pgen = 200
pyuks = 200

ekrangen = pencere.winfo_screenwidth()
ekranyuks = pencere.winfo_screenheight()

x = (ekrangen - pgen) / 2
y = (ekranyuks - pyuks) / 2

pencere.geometry("%dx%d+%d+%d"%(pgen, pyuks, x, y))

def yeniPencere():
    yeni = Toplevel()
    yeni.transient(pencere)

    xkonum = pencere.winfo_rootx()
    ykonum = pencere.winfo_rooty()
    yeni.geometry("+%d+%d"%(xkonum, ykonum))

    ybtn = Button(yeni, text="Dosya aç", command=yeniDosya)
    ybtn.pack()

def yeniDosya():
    dosya = askopenfilename()

btn = Button(pencere, text="yeni pencere aç", command=yeniPencere)
btn.pack()

mainloop()

Yeni eklediğimiz satır, “yeni” adlı ikinci pencerenin ana pencereye göre hep üstte kalmasını temin ediyor. Programımızı bu şekilde çalıştırdığımızda her şeyin olması gerektiği gibi olduğunu göreceğiz. Kodlarımızı bu şekilde yazdığımızda, ikinci pencereyi açtıktan sonra ana penceye tıklasak bile ikinci pencere ana pencerenin arkasına düşmeyecektir...