8. Atlıkarınca ve Resim Gösterici

Atlı karınca dememizin nedeni “carousel” kelimesi İngilizce’de “atlıkarınca” anlamını taşıması, elbette bir de at yarışlarındaki gösteri turnuvasına denmekte, ancak “carousel” isminin neye dayanarak verildiğini bilmiyorum (merak etmiyor da değilim, yoksa şu parklarda gördüğümz askıda dönen salıncaklardan mı geliyor acabağa). Bu “Carousel” denen şey nedir? Cep telefonunuzu kullanırken, ekranı sağa sola (ya da üste alta) parmaklarınızın ucu ile kaydırıyorsunuz ya, işte o. Bu bölümde Corusel (atlıkarınca) kullanarak bir resim gösterici yapmayı planlıyoruz. Normalde bir resim göstericisini atlıkarınca ile yapmak ne kadar mantıklı bilemiyorum. Çünkü tüm resimleri başta atlıkarıncaya yüklüyorsunuz. Bu da sanırım bellek kullanımını artırır. Her neyse biz burada bu atlıkarıncayı nasıl kullanacağımızı öğreneceğiz.

8.2. Resim Gösterici

Atlıkarıncaya etiket yerine, resim grafik parçacığını eklersek, metin yerine resimleri göstermiş olur.

8.2.1. Bir Klasördeki Resimler

Atlıkarıncayı basit olarak, programın bulunduğu dizindeki resimleri gösterecek şekilde kullanmaya çalışalım. Daha sonra programımızı geliştireceğimizden kv dilini kullanarak hazırlayalım. Öncelikle resimgosterici.kv dosyamızı Liste 8.2‘deki gibi yazalım.

Liste 8.2 resimgosterici.kv
1
2
3
4
5
6
7
<resimGosterici>:
BoxLayout:
    orientation: "vertical"
    Carousel:
        size_hint_y: 90
        id: karinca

Buarada anadüzenimizi BoxLayout yaptık çünkü ilerde düğmeler yerleştireceğiz. Onun dışında bilmediğiniz bir kod bulunmuyor. Atlıkarıncayı 4. satırdaki Carousel ile olışturduk. Bu nesneye ulaşmak için id’sini karinca yaptık. Bu kv dosyasını kullanıp, programın çalıştığı klasördeki png resimlerini gösterecek main.py programını da Liste 8.3‘deki gibi yazabiliriz.

Liste 8.3 main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# -*- coding: utf-8 -*-

import os
from kivy.app import App
from kivy.uix.image import Image

class resimGosterici(App):

    def build(self):
        self.son_patika=os.getcwd()
        for dosya in os.listdir(self.son_patika):
            if os.path.splitext(dosya)[1]=='.png':
                resim=Image(source=dosya)
                self.root.ids.karinca.add_widget(resim)

resimGosterici().run()

Bu programda os.listdir() ile bulunduğumuz klasördeki (os.getcwd() ile alınıyor) dosylara üzerinde bir iterasyon yapılıyor (11. satır). İterasyon içerisinde os.path.splitext() ile dosyaların (dosya_adı, uzantisi) şeklinde ayrılıyor ve uzantısı .png olan dosyalardan bir resim nesnesi oluşturuluyor. Resim nesnesi Image sınıfı ile oluşturulur. Bu sınıfa source paramteresi ile oluşturulacak resmin tam dosya adı (yada programın çalıştığı klasördeki dosya adı) verilir. Oluşturulan resim nesnesi atlıkarıncanın add_widget özelliği ile ekleniyor. Ben programın çalıştığı klasöre Kivy, Android ve Python logolarını koydum (umarım telif haklarını ihlal etmemişimdir). Programı çalıştırıp resmi sürüklerken ekran görüntüsünü aşağıdaki (Şekil 8.2) gibi aldım.

_images/resimGosterici2Img.png

Şekil 8.2 Basit Resim Gösterici (kaydırırken)

Bir klasördeki resimleri dosyaların uzantılarına bakarak belirlemek deyim yerinde ise amele işi (burada ameleleri küçümsemek gibi bir niyetimin olmadığını belirteyim), çünkü onlarda resim formatı var. Bunun yerine bir dosyanın resim olup olmadığını, Python’un imghdr modülünü kullanarak anlayabiliriz. Bu modülün what özelliği resim dosyasının tipini döndürür. Dosya resim değil ise hiçbirşey döndürmez. O Halde programımızı Liste 8.4‘deki gibi güncelleyebiliriz.

Liste 8.4 main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# -*- coding: utf-8 -*-

import os, imghdr
from kivy.app import App
from kivy.uix.image import Image

class resimGosterici(App):

    def build(self):
        self.son_patika=os.getcwd()
        for dosya in os.listdir(self.son_patika):
            if imghdr.what(dosya):
                resim=Image(source=dosya)
                self.root.ids.karinca.add_widget(resim)

resimGosterici().run()

Programın 2. satırında imghdr modülünü içerdiğimize dikkat edin.

Atlıkarıncaya resimleri build() altında eklemek mantıklı olmayacaktır. Çünkü ilerde çeşitli yollarla resim ekleyeceğiz her seferinde aynı işlemleri yapmamız gerekecek. Bunun yerine bir işlev yazalım ve resimleri orada ekleyelim. İşlevimiz kendisine bir liste halinde gelen dosyaları atlıkarıncaya eklesin. Eğer resimler programın çalıştığı dizin değil de (muhtemel olmayacak) başka bir yerde ise o zaman resimlerin tam patikasını vermek gerekecek. Bunu Liste 8.5‘’de 17. satırda kolayca yaptık.

Liste 8.5 main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding: utf-8 -*-

import os, imghdr
from kivy.app import App
from kivy.uix.image import Image

class resimGosterici(App):

    def resimleriEkle(self, dosyalar):
        self.root.ids.karinca.clear_widgets()
        if os.path.isfile(dosya):
            for dosya in dosyalar:
                if imghdr.what(dosya):
                    resim=Image(source=dosya)
                    self.root.ids.karinca.add_widget(resim)

    def build(self):
        self.son_patika=os.getcwd()
        dosyalar=[ os.path.join(self.son_patika, x) for x in os.listdir(self.son_patika) ]
        self.resimleriEkle(dosyalar)

resimGosterici().run()

Burada resimleriEkle() işlevinde kullandığımız atlıkarıncanın clear_widgets özelliği, daha önce eklenmiş tüm nesneleri (burada resimler) temizlemek içindir. Bu satırı yazmadığımız taktirde önceki resimler de görünecektir. Ayrıca, os.path.isfile(dosya) kontrolü ile, seçilen klasörde bir başka lat klasör var ise, buna ait resim tipi kontrolünün yapılmamasını sağladık.

Burada 19. satırı şu şekilde de yazabilirdiniz:

dosyalar=[]
for x in os.listdir(self.son_patika):
    dosyalar.append(os.path.join(self.son_patika, x))

Önceki yazdığımız daha kısa olmalı.

8.2.2. Klasörü Seçme

Resimler çoğu zaman, önceden belirlenen bir klasör yerine, kullanıcının programı çalıştırdıktan sonra seçeceği bir klasörde bulunacaktır. Bunu daha önce Metin Düzenleyici ‘de yapmıştık. Bunun için FileChooserListView` i kullanabiliriz. Fakat burada bir değişiklik yapalım ve index:`FileChooserIconView kullanalım. İkisinin de kullanımı benzer, sadece görüntüleri farklı. FileChooserIconView dosya ve klasörleri görüntülerken liste, değil simgelerle göstermektedir. Bu grafik parçacığını kv dosyasındaki bir form içerisinde kullanacağız. İlk olarak Liste 8.2 daki resimgosterici.kv dosyasına aşağıdaki kodları ekleyin:

Liste 8.6 acForm
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<acForm>:
    title: "Klasör/Dosya Aç"
    size_hint: (.9, .9)

    BoxLayout:
        orientation: 'vertical'

        FileChooserIconView:
            size_hint_y: 90
            id: dosya_secim
            filters: ['*.*']
            path: app.son_patika
            multiselect: True

        BoxLayout:
            size_hint_y: 10
            Button:
                text: "Seçilenleri Aç"
                on_press: app.secilenResimler(root); root.dismiss()
            Button:
                text: "Tüm Resimler"
                on_press: app.tumResimler(root); root.dismiss()

Burada farklı olarak multiselect özelliğinin değerini True yaptığımızı görüyorsunuz. Bu, kullanıcının birden fazla dosyayı seçebilmesine olanak tanıyacaktır. Bu kv formunu kullanacak sınıfı tanımlamak gerekiyor. Bunu class resimGosterici(App) satırından önce aşağıdaki kodları ekleyerek yapabiliriz:

class acForm(Popup):
    pass

main.py programında bu sınıfı tanımlamadan önce aşağıdaki gibi Popup‘ı içermeyi unutmayın.

from kivy.uix.popup import Popup

Yeni formumuzu açabilmek için ana pencerede bir düğme koymalıyız, ki bu formu açsın. Bunu resimgosterici.kv dosyasındaki resimGosterici formunu aşağıdaki gibi düzenleyerek yapabiliriz:

<resimGosterici>:
BoxLayout:
    orientation: "vertical"
    Carousel:
        size_hint_y: 90
        id: karinca

    BoxLayout:
        size_hint_y: 10
        Button:
            text: "Aç"
            on_press: app.klasorAc()

Buarada klasorAc() işlevi ile acForm‘u açacağız. İkinci bir BoxLayout eklememizin nedeni ilerde başka düğmeleri de koyacağımızdır. Önce düğmeye tıklandığında formun açılabilmesi için build() den önce aşağıdaki işlevi yazalım:

def klasorAc(self):
    form=acForm()
    form.open()

Ana penceredeki “Aç” düğmesine tıklandığında Şekil 8.3‘deki gibi açılacaktır.

_images/resimGosterici2Img1.png

Şekil 8.3 Dosya veya Klasör Seçimi

Önce tüm resimleri gösterebilmesi için , tumResimler() işlevini yazalım. Aşağıdaki işlevi build() den hemen önce yazın:

def tumResimler(self, kok):
    self.son_patika=kok.ids.dosya_secim.path
    dosyalar=[ os.path.join(self.son_patika, x) for x in os.listdir(self.son_patika) ]
    self.resimleriEkle(dosyalar)

Sanırım bu işlevdeki her satır sizin için anlaşılır. Yaptığımız tek şey son_patika değişkenine, FileChooserIconView nesnesinden dosya_secim.path değerini atamak oldu. Eğer bir resmi tüm tuvale genişletmek istiyorsanız, resim nesnesinin allow_stretch özelliğini True yapmalısınız. Bunu yaptığınızda en-boy oranı yine de korunacaktır. En-boy oranının da tuval’e eşitlenmesiniz istiyorsanız resim nesnesinin keep_ratio özelliğini False yapmanız gerekmektedir. Bunlar için resimleriEkle() işlevindeki resim=Image(source=dosya) satırından sonra şu satırları ekleyebilirsiniz:

resim.allow_stretch=True
resim.keep_ratio=False

Bu satırları eklediğimizde açılan resimler tüm tuval’i kaplayacaktır. Şimdide sadece seçilen resimleri göstermek üzere secilenResimler() işlevini yazalım. Aşağıdaki işlevi build() den hemen önce yazalım:

def secilenResimler(self, kok):
    self.son_patika=kok.ids.dosya_secim.path
    dosyalar=kok.ids.dosya_secim.selection
    self.resimleriEkle(dosyalar)

Öncekinden daha kolay oldu, sadece son_patika yı belirlemek ve dosyaları almak oldu. Dikkat edersenin seçilen dosyalar ile patikayı birleştirmedik, çünkü FileChooser nesnesi seçilen dosyayı patikası ile birlikte verir.

Programımız burada bitti, ancak kullanıcların istekler sonsuzdur. Bir programı yazmaya başlarken sadece temel ihtiyaçları göz önünde bulundurarak başlarız. Sonra aklımıza gelen eklentileri ya da kullanıcıların uygulanabilir makul isteklerini ekleriz. Şöyle bir şey aklımıza gelse “Slay gösterisi”. Gelin şimdi bunu yapalım.

8.3. Zamanlayıcı ve Slayt Gösterisi

Slayt gösterisini yapabilmek için, zamanlayıcı ya (timer) ihtiyacımız var. Neden mi? Eğer resimler arası geçiş zamanını time.sleep() ile ayarlarsanız, döngü bitene kadar program ile etkileşim yapılamaz. Bu nedenle zamanlayıcıya ihtiyacımız var. Zamanlayıcıyı program içerisinde çalışan bir saat olarak düşünebilirsiniz. Bu saat her tık atışında bir işlevi çağırır ve sizde bu işlevde yapılması gerekenleri kodlarsınız. Öncelikle ekranın en altına slayt gösterisini başlatıp durdurabileceğimiz bir düğme ekleyelim. Bunu kv dosyasındaki <resimGosterici> formunun altındaki BoxLayout düzenine aşağıdaki satırları ekleyerek yapabiliriz:

Button:
    id: slyat_dugme
    text: "Slaytı Başlat"
    on_press: app.slaytGosterisi(root)

Eğer hiç resim yüklenmediyse, bu düğmeyi pasifleştirmek gerekir. Kivy’de bir nesneyi pasifleştirmek için disabled özelliğine Treu ataması yaparız. Ön tanımlı olarak nesnelerin disabled özelliği False konumundadır. O halde öncelikle build() işlevinin en sonuna aşağıdaki satırı ekleyelim:

self.root.ids.slyat_dugme.disabled=True

Böylelikle, program başladığında atlıkarıncada hiç resim olmayacağından, diğme etkin olmayacaktır. Bir de, resimleriEkle() işlevinin en altına aşağıdaki satırları eklemeliyiz:

if self.root.ids.karinca.slides:
    self.root.ids.slyat_dugme.disabled=False
else:
    self.root.ids.slyat_dugme.disabled=True

Nedenini şöyle açıklayalım. Herhangi bir düğmeye tıklayarak, atlıkarıncaya resim eklenmişse, self.root.ids.karinca.slides değeri None dan farklı olacaktır ve düğme etkinleşecektir aksi halde düğme pasif olacaktır.

Slayt gösterisi için zamanlayıcıya ihtiyacımız olduğunu söyledik, bunun için kivy modüllerini içerdiğimiz satırların sonuna şu satırı ekleyelim:

from kivy.clock import Clock

<resimGosterici> formuna eklediğimiz id si slyat_dugme olan düğmeye tıklandığında slaytGosterisi() işlevi çağrılacaktır. Şim bunu build() den hemen önce şu şekilde yazalım:

def slaytGosterisi(self, kok):
    if kok.ids.slyat_dugme.text=="Slaytı Başlat":
        Clock.schedule_interval(self.zamanlayiciIslevi, 1)
        kok.ids.slyat_dugme.text="Slaytı Durdur"
    else:
        Clock.unschedule(self.zamanlayiciIslevi)
        kok.ids.slyat_dugme.text="Slaytı Başlat"

Bu işleve başlarken, slayt gösterisi devam ediyor mu etmiyor mu onu kontrol ederek başladık. Bunu slyat_dugme nin üzerindeki metni kullanarak kontrol ettik. Eğer düğme metni “Slaytı Başlat” ise, slayt başlamamıştır ve bu blokta slaytı başlatıyoruz. Slaytı başlatmak için saatin (Clock) tik atış aralığını veriyoruz. Bunu Clock nesnesinin schedule_interval özelliği ile yaparız. Zamanlayıcı şu şekilde başlatılır:

Clock.schedule_interval(islev, atis_zaman_araligi)

Burada islev her tık attığında çağrılacak işlevi ve zaman_araligi tik aralıklarını saniye cinsinden ifade etmektedir. Programımızda her tık atışta zamanlayiciIslevi() çağrılacaktır. Tık aralıkları ise 1 saniye olarak verilmiştir. Zamanlayıcı (daha doğrusu slayt gösterisi) başladıktan sonra, slyat_dugme nin üzerindeki metni “Slaytı Durdur” olarak değiştiriyoruz. Böylelikle aynı düğme salytı hem başlatmak hem de durdurmak için kullanılıyor.

Zamanlayıcı şu şekilde durdurulur:

Clock.unschedule(islev)

Zamanlayıcı ayrı işlevleri çağırmak için planlanabilir (schedule edilebilir) ve her seferinde isev ile atis_zaman_araligi farklı olabilir. Planlanmış bir zamanlayıcıyı durdurmak için unschedule özelleğini kullanıyoruz.

Buradan anlaşılacağı gibi slayt gösterisini yapacak işlevin çağrılmasını durdurmak için (planı bozmak için): Clock.unschedule(self.zamanlayiciIslevi) satırını kullandık ve hemen sonrasında slyat_dugme nin üzerindeki metni “Slaytı Başlat” yaptık.

Şimdide salyat gösterini yapacak olan zamanlayiciIslevi() işlevini yazalım. Bu işlev zamanlayıcının her tik atışında çağrılacaktır. Her çağrılışta atlı karıncanın bir sonraki slaytını göstermesi gerekir. Atlıkarıncanın sonraki slaytı göstermesi için load_next işevini kullanırız (önceki için load_previous`). Zamanlayıcı işlevini slaytGosterisi() nden önce aşağıdaki gibi yazabiliriz:

def zamanlayiciIslevi(self, za):
    self.root.ids.karinca.load_next()

Zamanlayıcı işlevi çağırırken ilk argüman olarak, iki tik atışltaki zaman aralığını verir (belki programcının bunu kullanmaya ihtiyacı olabilir), bunu işlevimizde za olarak aldık.

Slayt gösterisi bittiğinde (en son resme gelindiğinde), sonraki resim gösterileyemecektir ve sürekli aynı resimde atlam olacaktır. Eğer atlıkarıncanın sonsuz döngüde (sona geldiğinde tekrar başa sarma) çalışmasını istiyorsanız loop özelliğini True yapmalısınız. Programımızda build() işlevinin başına aşağıdaki satırı ekleyerek sonsuz döngüye sokmuş oluruz:

self.root.ids.karinca.loop=True

Son durumda programımızın penceresi Şekil 8.4‘deki gibi açılacaktır.

_images/resimGosterici5Img.png

Şekil 8.4 Slayt Gösterici

Anlattıklarımızı takip edemediyseniz, yada ben yaptıklarımı gözden kaçırıp eksik yazmışsam, bu bölümde anlattıklarımı yaptığım dosyaları şu adreslerden alabilirsiniz:

main.py: https://github.com/mbaser/kivy-tr/blob/master/docs/programlar/resimGosterici/5/main.py

metinduzenleyici.kv: https://github.com/mbaser/kivy-tr/blob/master/docs/programlar/resimGosterici/5/resimgosterici.kv

8.4. Kronometre Uygulaması

Zamanlayıcı kullanarak bir kronometre yapınız. Kronometrede iki düğme bulunmalıdır: Başlat ve Sıfırla Başlat düğmesine tıklanınca kronomtere başlayacak ve üzerindeki metin Durdur olacaktır. Sıfırla düğmesine tıklanınca kronometre sıfırlanacaktır. Kronometre tıklama aralığı 0.1 saniye (1 salise) olacaktır. Etiketteki metnin büyüklüğünü 100 piksel yapınız. Bir etiketin yazıtipi büyüklüğünü font_size özelliği ile ayarlayabilirsiniz. Kronometre sayaç görüntüsü dak:saniye.salise şeklinde olacaktır.

Kronometreniz çalıştığında Şekil 8.5‘deki gibi bir pencere olacaktır.

_images/kronometre1Img.png

Şekil 8.5 Kronometre

Çözüm:

main.py: https://github.com/mbaser/kivy-tr/blob/master/docs/programlar/resimGosterici/kronometre/main.py

kronometre.kv: https://github.com/mbaser/kivy-tr/blob/master/docs/programlar/resimGosterici/kronometre/kronometre.kv