Notes on converting the PyQt4 code from Mark Summerfield’s book to PyQt5
I’ve been working at this a while and have quite a few notes and suggestions.
- In the code examples from the book, all Qstrings are converted to Unicode. With PyQt5 this is no longer necessary. Just use python 3 strings in place of Qstrings and the Qstring will be converted automatically since python 3 strings are already unicode. I’m not sure if this works in python 2 though.
- All of the SIGNAL and SLOT code from the book uses the old style of defining signals and slots and their connections. The new way is MUCH simpler and allows you to throw away a lot of the detail of the old style. Here is an example. The first line is the old style syntax. The second line is the new style syntax introduced in PyQt 4.5:
old style: self.connect(self.table, SIGNAL(“itemDoubleClicked(QTableWidgetItem*)”), self.editEdit)
new style: self.table.itemDoubleClicked.connect(self.editEdit)
- Connections are made using QObject.connect(), broken using QOBject.disconnect() and emitted using QObject.emit()
To create a custom signal it’s a little trickier what you need to do is to declare the signal as a class attribute. Class attributes must appear at the very beginning of your class definition, before any methods. Here is an example from chapter 4 of PyQt book:
import sys
from PyQt5.QtCore import *
# from PyQt4.QtGui import *
from PyQt5.QtWidgets import *
class ZeroSpinBox(QSpinBox):
atzero = pyqtSignal(int)
zeros = 0
def __init__(self):
super().__init__()
self.valueChanged.connect(self.checkzero)
def checkzero(self):
if self.value() == 0:
self.zeros += 1
self.atzero.emit(self.zeros)
# in the original PyQt4 program the atzero signal was created and emitted in one line of code:
# self.emit(SIGNAL(“atzero”), self.zeros)
class Form3(QDialog):
def __init__(self):
super().__init__()
dial = QDial()
dial.setNotchesVisible(True)
self.zerospinbox = ZeroSpinBox()
…
skip some code
…
self.zerospinbox.atzero.connect(self.announce)
self.setWindowTitle(“Signals and Slots”)
def announce(self, zeros):
print(“ZeroSpinBox has been at zero {} times”.format(zeros))
# print(dir(zeros))
- Since the ZeroSpinBox widget doesn’t have an atzero signal so a subclass of QSpinBox must be written to include the signal, which has been done in the ZeroSpinBox class with the declaration of the atzero signal. It can then be emitted in the checkzero() method where if the value of an instance of ZeroSpinBox (zerospinbox here) is 0, the atzero signal is triggered. A connection is setup in the __init__ that calls self.announce when the signal is emitted. The signal also passes the value of self.zeros which has the number of times that the spinbox reached 0.
- In PyQt4, QFileDialog.getOpenFileName() and QFileDialog.getSaveFileName() return a str with the filename but in PyQt5 they return a tuple which can contain the file name as the first element of the tuple and the extension filter as the second element or an empty element. This can cause problems if you don’t modify the behavior of the code. One solution to to refer to the file name as filename[0] but this can be tricky. The easiest solution is change the assignment statement from
- filename = QFileDialog.getOpenFileName() to
- filename, _ = QFileDialog.getOpenFileName()
- This will throw out the second element of the tuple and filename will just get a str containing the file name. The rest of the code should work the same as it did in PyQt4.
def fileOpen(self): filename, _ = QFileDialog.getOpenFileName(self, "SDI Text Editor -- Open File") if filename: if (not self.editor.document().isModified() and self.filename.startswith("Unnamed")): self.filename = filename self.loadFile() else: MainWindow(filename).show()