Search code examples
pythonsyntaxpyqt4pyqt5signals-slots

Correct way to convert old SIGNAL and SLOT to new style?


I'm currently trying to convert an old python program from Python 2 to Python 3, and update from PyQt4 to PyQt5. The application uses the old style signal and slots that are not supported under PyQt5. I have figured out most of what needs to be done, but below are a few lines that I can't seem to get working:

self.emit(SIGNAL('currentChanged'), row, col)
self.emit(SIGNAL("activated(const QString &)"), self.currentText())
self.connect(self,SIGNAL("currentChanged(const QString&)"), self.currentChanged)

The top two lines, I have no idea where to start since they don't seem to be attached to anything. The last example I'm not quite sure what to do with (const QString &).

I'm not entirely sure how to approach these, and I'm still learning python, but any help would be appreciated.

EDIT: The documentation doesn't really seem to go into depth on these cases, at least in a way that I understand.


Solution

  • The exact answer to this will depend on what kind of object self is. If it is a Qt class that already defines those signals, then the new-style syntax would be this:

    self.currentChanged[int, int].emit(row, col)
    self.activated[str].emit(self.currentText())
    self.currentChanged[str].connect(self.handleCurrentChanged)
    

    However, if any of those aren't pre-defined, you would need to define custom signals for them, like this:

    class MyClass(QWidget):
        # this defines two overloads for currentChanged
        currentChanged = QtCore.pyqtSignal([int, int], [str])
        activated = QtCore.pyqtSignal(str)
    
        def __init__(self, parent=None):
            super(MyClass, self).__init__(parent)
            self.currentChanged[str].connect(self.handleCurrentChanged)   
    
        def handleCurrentChanged(self, text):
            print(text)
    

    The old-style syntax allowed custom signals to be emitted dynamically (i.e. without defining them first), but that is not possible any more. With the new-style syntax, custom signals must always be explicitly defined.

    Note that, if there is only one overload defined for a signal, the selector can be omitted:

        self.activated.emit(self.currentText())
    

    For more information, see these articles in the PyQt Docs:

    EDIT:

    For your actual code, you need to make the following changes for the currentChanged signals:

    1. In Multibar.py (around line 30):

      This defines a custom signal (because QWidget does not have it):

      class MultiTabBar(QWidget):
          # add the following line
          currentChanged = pyqtSignal(int, int)
      
    2. In Multibar.py (around line 133):

      This emits the custom signal defined in (1):

      # self.emit(SIGNAL('currentChanged'), row, col)
      self.currentChanged.emit(row, col)
      
    3. In ScWindow.py (around line 478):

      This connects the signal defined in (1):

          # self.connect(self.PieceTab,SIGNAL("currentChanged"),self.pieceTabChanged)
          self.PieceTab.currentChanged.connect(self.pieceTabChanged)
      
    4. In ItemList.py (around line 73):

      The QFileDialog class already defines this signal, and there is only one overload of it. But the name of the slot must be changed, because it is shadowing the built-in signal name (which has become an attribute in the new-style syntax). So the connection should be made like this:

          # self.connect(self,SIGNAL("currentChanged(const QString&)"),self.currentChanged)
          self.currentChanged.connect(self.onCurrentChanged)
      
    5. In ItemList.py (around line 78):

      This renames the slot for the connection made in (4):

          # def currentChanged(self, file):
          def onCurrentChanged(self, file):