Converting PyQt4 code from Rapid GUI Programming with Python and Qt to PyQt5

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()

RoastMaster Utilities Progress

I finally got a rudimentary  GUI for my RoastMaster Utilities working.

2016-02-26 (1)

Here is the code with a brief explanation of the problem spot. If I had a bit of experience with python classes and inheritance, I probably wouldn’t have had problems.

from PyQt5 import QtCore, QtGui, QtWidgets
import coffeeMenu as cm
import RMPasteData as pd
import sys

from RMUtility2 import Ui_MainWindow


class MyWindow(Ui_MainWindow):
    '''
    Inherit from class created by Qt designer in the file RMUtility2
    The critical part is in the __init__ method (also known as a constructor)
     below. You usually see it as 
     super().__init__() which would call the __init__ method of the parent
     class however Qt Designer doesn't provide an __init__. It provides the
     setupUi method instead.
    '''
    def __init__(self):
        super().setupUi(MainWindow)

    def setupUi(self, MainWindow):
        print("In local setupUI")
        self.coffeeButton.clicked.connect(self.coffeeMenu)
        self.pasteDataButton.clicked.connect(self.pasteData)

    def coffeeMenu(self):
        debug = False
        roastmaster_db = 'C:/Users/kor/Dropbox/Apps/Roastmaster/Kor Database.sqlite'
        # roastmaster_db = 'c:/users/kor/dropbox/apps/roastmaster/Kor Database.sqlite'
        cm.check_db(roastmaster_db)
        cm.show_coffee_list(debug)
        cm.create_report()

    def pasteData(self):
        print("In pastData - local")
        debug = False
        roastmaster_db = 'c:/users/kor/dropbox/apps/roastmaster/Kor Database.sqlite'
        # roastmaster_db = 'C:/Users/kiley_000/Dropbox/Apps/Roastmaster/My Database.sqlite'
        pd.check_db(roastmaster_db)
        pd.show_coffee_list()
        pd.create_report()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = MyWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

More PyQt tutorials

I’ve skipped to a new series of PyQt tutorials. This one was produced by Sentdex. I’m sure the guy must have a name but I’m yet to hear it. Anyway, he’s produced a slew of videos on Python programming and many of them are specifically for PyQt4 programming. He recommends PyQt4 over PyQt5–says it is simpler but I would rather work with 5 and I’m not even sure it’s possible to simultaneously have both 4 and 5 environments. I seem to be having a few problems with that scenario.

Sentdex has a web site called pythonprogramming.net. He also has a $5/month subscription plan that allows you to download the videos, take quizzes, ask questions, etc. I signed up for the subscription plan.

And I’ve just discovered another series of videos for using PyQt and the Qt Designer. Here is one of the videos on You Tube.

Good PyQt5 tutorial

I’ve been looking for a good PyQt5 tutorial. This is quite a good one. Its one major weakness, though, is that all of the examples I’ve studied so far could have been done much more easily with the Qt designer. Nevertheless, this tutorial is helping me understand better how things are put together when creating a GUI with PyQt.

2016-02-20 (1)
Qt Designer version 5.5.1

 

Exploring Conda — Warming up for PyQT

There are several big Python distributions largely put together by the scientific community. One such is Anaconda. I’ve used this a bit in the past but never learned that much about it. I’m trying to really study it now to facilitate my development of some of my coffee roasting utilities with PyQt, a Python tool kit using the Qt development platform. According to the Riverbank web site:

PyQt is a set of Python v2 and v3 bindings for The Qt Company’s Qt application framework and runs on all platforms supported by Qt including Windows, MacOS/X and Linux. PyQt5 supports Qt v5. PyQt4 supports Qt v4 and will build against Qt v5. The bindings are implemented as a set of Python modules and contain over 1,000 classes.

Qt is largely used to develop GUI based application although it may also be used to develop command line applications.

Another distribution I’ve used a lot in the past is Enthought Canopy. The Canopy interface is pretty nice but I thought that it didn’t include some of the packages that I’m using for my scripts and thought that Anaconda seemed to include more of them. But as I gain experience, I find it is not that important. New packages like Reportlab and PyQt are easily added.

Every Python distribution uses a package manager to install and manage packages. The Anaconda team have created a very capable one called Conda. Yesterday I learned that PyCharm, the  IDE that I use for Python, allows you to include Conda environments. This is very desirable because it permits me to use PyCharm and Anaconda together.

Python and Qt
Python and Qt (Photo credit: Wikipedia)

Programming Foundations with Python

I’m still on track to create a Python GUI interface for my Roastmaster report application but have sidetracked. To use any of the Python GUI modules it is important to be comfortable with classes and OOP (object-oriented programming). To help me out in that area, I started another Udacity course. This one is called Programming Foundations with Python which has a specific goal of introducing you to Python classes and OOP. This course is pretty easy and a lot of fun since you get introduced to some very fun and practical applications of Python. A fun project was creating a very small application you can use to send texts to phones. To do this you need to set up an account on Twilio. The very first thing you learn how to do is to display a web page. It’s great to be able to do these things right away on a higher level and not having to spend hours and hours with the details of programming first. I think this can give a beginner incentive to learn the language in-depth after being given a taste of what can easily be accomplished with the right publicly available modules.