20 Şubat 2019 Çarşamba

PyQt5'de Signal - Slot Kavramı

PyQt5 Signal - Slot (İşaret - İşlev) Kavramı


Qt birden çok platformu destekleyen (cross-platform) masaüstü, gömülü sistem ve mobil ortamlar için grafiksel kullanıcı arayüzü geliştirmemizi sağlayan bir araçtır. Qt ile kullanıcı arayüzü geliştirmek için C++ programlama dili kullanılmaktadır. PyQt5, Qt programını Python ile kullanmamızı sağlar. PyQt5 hakkında detaylı açıklamalara önceki yazılarımdan ulaşabilirsiniz:

PyQt5 (pip ile) Windows Kurulumu
PyQt5 ile Grafiksel Kullanıcı Arayüzü Geliştirme




Bu yazıda
  • Signal-slot nedir ?
  • Signal-slot mekanizması nasıl çalışır ?
  • Öntanımlı (Default) işaretler (signal) nedir, nasıl kullanılır ?
  • Qt Designer ile signal-slot bağlantısı nasıl yapılır ?
  • Kod yazarak signal-slot bağlantısı nasıl yapılır ?
  • Kendimiz işaret (signal) tanımlayabilir miyiz ?
  • Signal-slot bağlantısı nasıl kaldırılır (iptal edilir)?
  • sorularına cevap bulacağız.


1. Signal - Slot Nedir ? Signal - Slot Mekanizması Nasıl Çalışır ?

Signal, Türkçe'de hem sinyal hem de işaret olarak kullanılmaktadır. Slot kelimesinin ise yuva anlamı vardır. Aslında slot programlama dili açısından düşünüldüğünde fonksiyon olarak kullanılabilir. Fonksiyon da dilimizde yaygın olarak kullanılan bir kelime olmasına karşın Türkçe karşılığı işlev'dir. Signal-Slot yerine işaret-işlev diyebiliriz ancak bu kavram Qt'ye özgü bir kavram olduğu için signal-slot kavramından işaret-işlev olarak bahsedildiğinde anlam karmaşası olabilir. Bu sebeple yazının geri kalan bölümünde signal-slot olarak orijinal hali ile kullanmayı planlıyorum. Signal-Slot bana göre Qt'de dolayısıyla PyQt5'de arayüz geliştirmek için öğrenilmesi gereken en önemli kavram. Signal-Slot yapısı kullanılmadan kullanıcıya sadece yapmış olduğunuz tasarımı verebilirsiniz. Signal-Slot yapısı kullanılmadan yapılan bir arayüz yani tasarım üzerinde kullanıcı hiç bir işlem yapamaz. Kullanıcıya arayüz ile sunulan özelliklerin bir karşılığının olması ancak signal-slot yapısı ile sağlanır.


Signal-slot kavramını yukarıda yer alan animasyon üzerinden anlatmaya çalışalım. Animasyonun sol üst köşesinde bir kullanıcı yer alıyor, sağ üst köşede ise bizim hazırlamış olduğumuz bir kullanıcı arayüzü olduğunu varsayalım. Kullanıcı, hazırlamış olduğumuz arayüz üzerinde bir eylemde bulunduğunda örneğin bir tuşa bastığında bir sinyal yayınlanır ve bu sinyal bir fonksiyona bağlı ise o fonksiyon çağrılır ve fonksiyon içerisinde yer alan işlem gerçekleştirilir. Arayüzü hazırlayan kişi arayüz elemanları ve fonksiyonlar arasında signal-slot bağlantıları yapar ve kullanıcı arayüz üzerinde bir işlem yaptığında ilgili fonksiyon çağrılır. Signal-slot mekanizması bu şekilde çalışır. Örneklerle birlikte bu kavramı daha iyi anlayacaksınız.

2. Öntanımlı (Default) İşaretler (Signals) Nedir? Öntanımlı İşaretler Nasıl Kullanılır ?

Öntanımlı (default) işaretler Qt tarafından önceden tanımlanmış parçacığa özgü işaretlerdir. Bu hazır işaretler çok rahatlıkla kullanılabilir. Yazının ilerleyen bölümlerinde öntanımlı işaretlerin kullanımını örneklerle daha iyi anlayacaksınız. Örneğin basılabilir tuşun (QPushButton) tıklandı (clicked), basıldı (pressed), bırakıldı (released) gibi öntanımlı işaretleri bulunmaktadır. Bu işaretleri fonksiyonlara bağladığınızda ve kullanıcı bu tuş üzerinde bir eylemde bulunduğunda ilgili fonksiyonlar çalıştırılır.



3. Qt Designer ile Signal - Slot Bağlantısı Nasıl Yapılır ?

Qt Designer ile signal-slot bağlantısı yapmak için öncelikle Qt Designer açılır. Qt Designer içerisinde yer alan menüde bulunan Edit sekmesine tıklanır ve bu sekmede yer alan Edit Signals/Slots seçeneğine tıklanır.


Aynı işlemi F4 tuşuna basarak ya da aşağıdaki şekilde belirtilen tuşa basarak gerçekleştirebilirsiniz.


Signals-slots moduna geçmeden önce 1) arayüz tasarımını tamamlamış olmalısınız ve 2) arayüz üzerinde yer alan parçacıkların hangi işlevleri yapacağını belirlemiş olmanız gerekir. Signal-slot kavramını anlatabilmek için yatay kaydırma butonu (QHorizontalSlider) ve etiket (QLabel) parçacıklarından oluşan aşağıdaki tasarım yapıldı. Burada kaydırma butonunun konumu değiştirtirildiğinde o anki pozisyonunun değeri QLabel da gösterilecektir. Arayüz tasarımını tamamladık ve arayüzde yer alan parçacıkların ne iş yapacakları tanımlandıktan sonra yukarıda anlatıldığı gibi Signals/Slots moduna geçiş yapılır.


Signals-slots modunda iken Yatay Kaydırıcıya (HorizontalSlider) farenin sol tuşu ile tıklanır ve bu tuşa basılı tutularak etiketin (Label) üzerine getirilir ve burada tuş bırakılır. Tuşu bırakır bırakmaz Configure Connection adında yeni bir pencere açılacaktır. Bu pencerenin sol bölümünde yatay kaydırıcıya ait öntanımlı (default) işaretler bulunmaktadır. Biz burada değer değişti (valueChanged(int)) işaretini seçiyoruz. Bu işaret kaydırıcının değeri değiştiğinde işaret yayar. Biz bu işareti bir slot'a yani fonksiyona bağladığımızda kaydırıcının her değeri değiştiğinde bu fonksiyon çağrılacaktır.


İşaret seçimini yaptıktan sonra pencerenin sağ bölümü aktif hale gelir ve burada öntanımlı (default) slot'lar yani fonksiyonlar bulunmaktadır. Hem işaretler için hem de slot'lar için öntanımlı işaret ve slotları kullanabildiğimiz gibi kendi işaret ve fonksiyonlarımızı da Edit butonuna basarak tanımlayabiliriz. Biz burada etiket (Label) için setNum(int) fonksiyonunu seçiyoruz.





Yukarıda yer alan ekran görüntüsünde gördüğünüz gibi yatay kaydırıcının (HorizontalSlider) değeri değiştirildiğinde bu değer etikete (Label) yazdırılmaktadır. Burada basit bir şekilde herhangi bir kod yazmadan Qt Designer ile signal-slot bağlantısı gerçekleştirmiş olduk. Bu yöntem benim tercih ettiğim bir yöntem değil bu yüzden örnek sayısını arttırmadan ve daha fazla detaya girmeden konunun bu kadar yeterli olduğunu düşünüyorum. Kod yazarak signal-slot bağlantısının nasıl yapıldığını bir sonraki bölümde inceleyeceğiz.


4. Kod Yazarak Signal-Slot Bağlantısı Nasıl Yapılır ?

Bu bölümde, yazının başında yer alan şemada gösterilen connect(), @pyqtSlot() ve pyqtSignal fonksiyonlarının kullanımını öğreneceğiz.

4.1. "connect()" Fonksiyonu ile Signal-Slot Bağlantısı

Qt Designer ile Signal-Slot bağlantısı bölümünde anlatılan Yatay kaydırıcı (QHorizontalSlider) ve etiket (QLabel) parçacıklarından oluşan tasarım bu bölümde de kullanılacak.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from PyQt5.QtWidgets import*
from qt_designer_python import Ui_MainWindow


class dersler_7(QMainWindow):

    def __init__(self):
        super().__init__()

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.horizontalSlider.valueChanged['int'].connect(self.set_current_value_slot)


    def set_current_value_slot(self, current_index):

        self.ui.label.setText("değer = " + str(current_index))


uygulama = QApplication([])
pencere = dersler_7()
pencere.show()
uygulama.exec_()

13 nolu satırda Yatay Kaydırıcının o anki değerini int tipinde değer gönderen "valueChanged" işareti 16 nolu satırda kendi tanımladığımız "set_current_value_slot" fonksiyonuna "connect" ile bağladık. Dikkat ederseniz yazmış olduğumuz slot yani fonksiyon "valueChanged" işaretinden gönderilen int tipindeki yatay kaydırıcının değerini alacak şekilde yazılmıştır. Daha detaylı bilgiye yazının en alt bölümünde yer alan videoyu izleyerek ulaşabilirsiniz.

4.2. @pyqtSlot() ile Sinyal-Slot Bağlantısı Yapma

Burada @pyqtSlot() ile signal-slot bağlantısı yaparken slot'a istediğimiz ismi veremiyoruz. Slot'a vereceğimiz isim aşağıda belirtilen formatta olması gerekir.

on_ nesne_adi_ sinyal_adi()

Bir önceki bölümde "connect()" fonksiyonu ile yapmış olduğumuz bağlantıyı aşağıdaki kodlarda @pyqtSlot() dekoratörü ile yaptık.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from PyQt5.QtWidgets import*
from PyQt5.QtCore import pyqtSlot
from qt_designer_python import Ui_MainWindow


class dersler_7(QMainWindow):

    def __init__(self):
        super().__init__()

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

    @pyqtSlot(int)
    def on_horizontalSlider_valueChanged(self, current_index):

        self.ui.label.setText("değer = " + str(current_index))


uygulama = QApplication([])
pencere = dersler_7()
pencere.show()
uygulama.exec_()

2 nolu satırda QtCore modülünden pyqtSlot sınıfını kodlarımıza eklediğimize dikkat ediniz. Daha detaylı bilgiye yazının en alt bölümünde yer alan videoyu izleyerek ulaşabilirsiniz.

  • Bir İşaret birden fazla Slot'a bağlanabilir


  • Birden fazla İşaret bir Slot'a bağlanabilir


  • Bir İşaret başka bir İşaret'e bağlanabilir


4.3. pyqtSignal() ile Sinyal Oluşturma

Bu bölümde pyqtSignal() kullanımına örnek verebilmek için aşağıdaki akış diyagramını gerçekleştiren kodlar yazıldı.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from PyQt5.QtWidgets import*
from PyQt5.QtCore import pyqtSignal
from qt_designer_python import Ui_MainWindow


class dersler_7(QMainWindow):

    my_signal = pyqtSignal(int)

    def __init__(self):
        super().__init__()

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.horizontalSlider.valueChanged[int].connect(self.my_signal[int])

        self.my_signal[int].connect(self.my_slot)


    def my_slot(self, current_index):

        self.ui.label.setText("değer = " + str(current_index))


uygulama = QApplication([])
pencere = dersler_7()
pencere.show()
uygulama.exec_()

2 nolu satırda QtCore modülünden pyqtSignal sınıfını kodumuza eklediğimize dikkat ediniz. 8 nolu satırda int tipinde değer gönderen-alan kendi sinyalimizi tanımladık. Yatay kaydırıcının valueChanged sinyalini kendi tanımlamış olduğumuz sinyale 16 nolu satırda bağladık. Bu bağlantı ile bir sinyalin bir başka sinyale bağlanabildiğini görmüş olduk. Son olarak kendi tanımladığımız sinyali yine kendi tanımladığımız slot'a 18 nolu satırda bağladık. Kaydırıcının değeri değiştiğinde valueChanged sinyali yayınlanacak dolayısıyla buna bağlı olan sinyalimiz de yayınlanmış olacak ve kendi sinyalimize bağlı my_slot() fonksiyonu çağrılarak etikte yatay kaydırıcının değeri yazdırılacak. Bu tarz bağlantılar bu uygulama için gereksiz olsa da daha karmaşık arayüzler hazırladığınızda bu tür bağlantılara ihtiyacınız olacak.

Kendi tanımlamış olduğumuz işaretin yayınlanmasını sağlayan emit() fonksiyonu kullanımına örnek verebilmek için aşağıdaki akış diyagramını gerçekleştiren kodlar yazıldı.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from PyQt5.QtWidgets import*
from PyQt5.QtCore import pyqtSignal
from qt_designer_python import Ui_MainWindow


class dersler_7(QMainWindow):

    my_signal = pyqtSignal(int)

    def __init__(self):
        super().__init__()

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.horizontalSlider.valueChanged['int'].connect(self.set_current_value_slot)
        self.my_signal[int].connect(self.my_slot)
        
        
    def set_current_value_slot(self, current_index):
        
        self.my_signal.emit(current_index)


    def my_slot(self, current_index):

        self.ui.label.setText("değer = " + str(current_index))


uygulama = QApplication([])
pencere = dersler_7()
pencere.show()
uygulama.exec_()

5. Signal-slot bağlantısı nasıl kaldırılır (iptal edilir)?

Signal-slot bağlantısı yaparken connect() fonksiyonunu kullanarak yazmış olduğumuz ifadenin aynısını disconnect() ifadesiyle yazıyoruz. Eğer disconnect(slot_adi) yazarsak sadece yazmış olduğumuz slot ile bağlantı koparılır. disconnect() fonksiyonunu içerisinde hiçbir ifade yer almayacak şekilde yazarsak belirtilen işaret ile yapılmış olan tüm slot bağlantıları kaldırılır. Signal-slot bağlantısının iptal edilmesi çok sık kullanılan bir yöntem değildir ama arayüz geliştirirken ihtiyaç duyacağınız durumlar olabilir.

Signal-slot mekanizması kolay ve esnek kod yazmamızı sağlayan Qt nin sağlamış olduğu en önemli özelliklerden biridir. Bu yazıda signal-slot kavramını, signal-slot mekanizmasının nasıl çalıştığını, nasıl signal-slot bağlantısının yapıldığını elimden geldiğince anlatmaya çalıştım. Detaylı açıklamalara aşağıdaki videoyu izleyerek ulaşabilirsiniz.