Results 1 to 5 of 5

Thread: Proper PyQt5 signal & slot syntax

  1. #1
    Join Date
    Oct 2019
    Posts
    12
    Thanks
    1
    Thanked 1 Time in 1 Post
    Qt products
    Qt5
    Platforms
    Windows

    Default Proper PyQt5 signal & slot syntax

    Hi,

    I've read conflicting blog and StackOverflow (SO) posts about PyQt5's signal & slot syntax. This site is supposed to be the reference:

    https://www.riverbankcomputing.com/s...als_slots.html

    but their examples don't work, at least not in the way I'm trying them. I'm working in PyQt5, but the syntax should be exactly the same according to several posts on SO. I'm just trying to connect a bunch of items to a single signal, but the syntax just doesn't work as it keeps telling me there is no 'connect'. Some posts mention connectNotify, but I've tried that too with no success. Here is a small example of what I'd like to do.

    I have a signal 'increase' as a class variable in class 'ChangeSignals', just like in the reference above. However, I want a different class, 'SignalWatchers' to connect to the 'increase' signal and call the SignalWatchers callback 'signal_handler' when 'increase' is emitted.

    What is the proper way to construct this?

    Qt Code:
    1. import sys
    2. from PyQt5 import QtCore, QtGui, QtWidgets
    3. from PyQt5.QtCore import *
    4. from PyQt5.QtGui import QPen, QPainter, QColor
    5. import string as st
    6. #import time
    7. from threading import Timer
    8. from itertools import count
    9.  
    10. def print_dir(key, alphabetize=True):
    11. cnt = 0
    12. for i in sorted(dir(key)):
    13. print(i, end=", ")
    14. cnt += 1
    15. if cnt % 10 == 0:
    16. print()
    17. print()
    18.  
    19. class GlobalTimer(object):
    20. def __init__(self, interval=1.0):
    21. self._registered = set()
    22. self._interval = interval
    23. self._timer = None
    24. self.isActive = False
    25.  
    26. def register_callback(self, callback):
    27. self._registered.add(callback)
    28.  
    29. def unregister_callback(self, callback):
    30. self._registered.remove(callback)
    31.  
    32. def setInterval(self, interval=2.5):
    33. if interval<0 or interval>10:
    34. return
    35. self._interval = interval
    36.  
    37. def start(self):
    38. self._timer = Timer(self._interval, self._callback)
    39. self._timer.start()
    40. self.isActive = True
    41.  
    42. def stop(self):
    43. self._timer.cancel()
    44. self.isActive = False
    45.  
    46. def _callback(self):
    47. self._timer.cancel()
    48. self.isActive = False
    49. for callback in self._registered:
    50. callback()
    51. #self._start_timer()
    52.  
    53. class ChangeSignals(QObject):
    54.  
    55. increase = pyqtSignal()
    56. trigger = pyqtSignal()
    57.  
    58. # This defines a signal called 'rangeChanged' that takes two
    59. # integer arguments.
    60. range_changed = pyqtSignal(int, int, name='rangeChanged')
    61.  
    62. # This defines a signal called 'valueChanged' that has two overloads,
    63. # one that takes an integer argument and one that takes a QString
    64. # argument. Note that because we use a string to specify the type of
    65. # the QString argument then this code will run under Python v2 and v3.
    66. valueChanged = pyqtSignal([int], ['QString'])
    67.  
    68. #@staticmethod
    69. def connect_trigger(self):
    70. # Connect the trigger signal to a slot.
    71. self.trigger.connect(self.handle_trigger)
    72.  
    73. #@staticmethod
    74. def emit_trigger(self):
    75. # Emit the signal.
    76. self.trigger.emit()
    77.  
    78. sig = ChangeSignals()
    79. sig.connectNotify()
    80.  
    81. class SignalWatchers(QtWidgets.QGraphicsRectItem):
    82. _instance_count = count(0)
    83.  
    84. def __init__(self, chr_num, parent=None):
    85. super(SignalWatchers, self).__init__(parent)
    86. self.chr_num = chr_num
    87. self.count = self._instance_count
    88. self._instance_count = next(self._instance_count)
    89. #print(dir(self._instance_count))
    90.  
    91. self.timer = GlobalTimer(4)
    92. self.timer.register_callback(self.time_callback)
    93.  
    94. self.font_size = 9
    95. print("count = {}".format(self.count))
    96.  
    97. #ChangeSignals.increase.connect(self.signal_handler)
    98. #ChangeSignals.increase.connectNotify(self.signal_handler)
    99. if self._instance_count == 1:
    100. print_dir(ChangeSignals.increase)
    101.  
    102.  
    103. def connect_and_emit_trigger(self):
    104. # Connect the trigger signal to a slot.
    105. self.trigger.connect(self.handle_trigger)
    106.  
    107. # Emit the signal.
    108. self.trigger.emit()
    109.  
    110. @pyqtSlot()
    111. def signal_handler(self):
    112. self.chr_num += 1
    113.  
    114. def time_callback(self):
    115. pass
    116.  
    117. def paint(self, p, opts, widget):
    118. self.paint_me(p)
    119.  
    120. def paint_me(self, p):
    121. p.setRenderHint(QtGui.QPainter.Antialiasing)
    122. txt = chr(self.chr_num)
    123. penWidth = 1
    124. font = QtGui.QFont("Helvetica", 9, QtGui.QFont.Bold)
    125. pen = QtGui.QPen(Qt.black, 1)
    126. p.setPen(pen)
    127. p.setFont(font)
    128. p.drawRoundedRect(self.rect(), 5, 5)
    129. p.drawText(12*(self._instance_count -2), 10, txt)
    130.  
    131.  
    132. class MainWindow(QtWidgets.QMainWindow):
    133. def __init__(self, parent=None):
    134. super(MainWindow, self).__init__(parent)
    135. self.app_width = 605
    136. self.keyboard_height = 220
    137. self.iteration_delay = 50
    138. self.press_cnt = 0
    139. self.setWindowTitle("Signal Test")
    140. self.scene = QtWidgets.QGraphicsScene(self)
    141. self.scene.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)
    142.  
    143. self.create_central_widget()
    144. self.sigs = {}
    145. for i in range(65,70):
    146. self.sigs[i] = SignalWatchers(i)
    147. self.scene.addItem(self.sigs[i])
    148.  
    149. def handle_signal(self, signal_val):
    150. print("signal_val = {}".format(signal_val))
    151. pass
    152.  
    153.  
    154. def create_layout(self):
    155. self.layout = QtWidgets.QGraphicsLinearLayout()
    156. self.widget = QtWidgets.QGraphicsWidget()
    157. self.widget.setLayout(self.layout)
    158. self.scene.addItem(self.widget)
    159. width = self.app_width
    160. height = self.keyboard_height
    161. self.setMinimumSize(width, height)
    162. self.scene.setSceneRect(0, 0, width, height)
    163.  
    164. def create_central_widget(self):
    165. self.view = QtWidgets.QGraphicsView(self.scene)
    166. self.view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
    167. self.view.setBackgroundBrush((QColor("bisque")))
    168. self.setCentralWidget(self.view)
    169. scale_val = 1
    170. self.view.scale(scale_val, scale_val)
    171.  
    172. def main():
    173. app = QtWidgets.QApplication(sys.argv)
    174. w = MainWindow()
    175. w.show()
    176. ret = app.exec_()
    177. sys.exit(ret)
    178.  
    179. if __name__ == "__main__":
    180.  
    181. main()
    To copy to clipboard, switch view to plain text mode 

  2. #2
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,719
    Thanks
    259
    Thanked 760 Times in 750 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Proper PyQt5 signal & slot syntax

    #ChangeSignals.increase.connect(self.signal_handle r)
    I am no PyQt expert, but in C++, you would need an instance of the ChangeSignals class as an argument of the connect() statement. Otherwise, how does connect() know which instance of the signalling class to connect to? Signals aren't broadcast, there must be explicit signal-slot or signal-signal connections between instances of the classes on each end. The only argument you supply is your signal_handler slot (and I assume that in PyQt connect() defaults the SignalWatchers instance on the receiving end (slot) to "self").
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  3. #3
    Join Date
    Oct 2019
    Posts
    12
    Thanks
    1
    Thanked 1 Time in 1 Post
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Proper PyQt5 signal & slot syntax

    Thank you for the response. I had also tried using an instantiated version of the class, but was still getting errors on connect.

    It turns out that the line 110 above:
    @pyqtSlot

    was causing the issue. I don't know why, but when I commented out this line everything worked! It looks like that decorator is only for non-class functions.

    Thanks!

  4. #4
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,719
    Thanks
    259
    Thanked 760 Times in 750 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Proper PyQt5 signal & slot syntax

    Maybe it is my ignorance of PyQt and python in general, but where is the instance of ChangeSignals that forms the sending (signal) end of the increase() signal? I see you have defined a global variable named sig that is a ChangeSignals instance, but where is that used in the connect() statement?

    The only thing I can imagine is that ChangeSignals.increase.connect() must create a temporary instance of ChangeSignals that provides the signalling end and once __init__ exits this goes out of scope, gets destroyed, and the connection broken. Shouldn't this be "sig.increase.connect()" instead?
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  5. #5
    Join Date
    Oct 2019
    Posts
    12
    Thanks
    1
    Thanked 1 Time in 1 Post
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Proper PyQt5 signal & slot syntax

    No, you didn't miss anything. I went back to the version where I instantiated the class and used that signal, but I didn't update the code in my question. THEN I removed the pyqtSignal decorator.

    Sorry to throw you off!

  6. The following user says thank you to illuzioner1 for this useful post:

    d_stranz (3rd May 2020)

Similar Threads

  1. Replies: 0
    Last Post: 17th November 2015, 19:11
  2. PyQt4 Signal to PyQt5 Signal Convert?
    By mthnzbk in forum Qt Programming
    Replies: 3
    Last Post: 13th September 2015, 22:38
  3. Replies: 1
    Last Post: 28th February 2015, 17:20
  4. Replies: 2
    Last Post: 26th October 2013, 06:40
  5. Trouble with proper syntax for directorys
    By prophet0 in forum Newbie
    Replies: 2
    Last Post: 21st February 2012, 23:42

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.