1

I'd like to know how to generate OOP style Python code from the .ui files that I have created with QtDesigner.

What I try to say is that pyuic4 generates code that isn't OOP style: The widgets don't become subclasses of the QtGui classes. The code is hard to read and extend.

I want classes that encapsulate all the child widgets, layouts and functions.

Let's look at a simple example. This mainwindow contains a slider and a lcd display, and they are connected in the obvious way.

First, this is the code generated by pyuic4:

from PyQt4 import QtCore, QtGui

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(268, 186)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalSlider = QtGui.QSlider(self.centralwidget)
        self.horizontalSlider.setOrientation(QtCore.Qt.Vertical)
        self.horizontalSlider.setObjectName("horizontalSlider")
        self.gridLayout.addWidget(self.horizontalSlider, 0, 1, 1, 1)
        self.lcdNumber = QtGui.QLCDNumber(self.centralwidget)
        self.lcdNumber.setObjectName("lcdNumber")
        self.gridLayout.addWidget(self.lcdNumber, 0, 2, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 268, 25))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QObject.connect(self.horizontalSlider, QtCore.SIGNAL("valueChanged(int)"), self.lcdNumber.display)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))


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

I manually edited that to the style I want:

from PyQt4 import QtCore, QtGui

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)

        self.setObjectName("MainWindow")
        self.resize(268, 186)
        self.centralwidget = QtGui.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalSlider = QtGui.QSlider(self.centralwidget)
        self.horizontalSlider.setOrientation(QtCore.Qt.Vertical)
        self.horizontalSlider.setObjectName("horizontalSlider")
        self.gridLayout.addWidget(self.horizontalSlider, 0, 1, 1, 1)
        self.lcdNumber = QtGui.QLCDNumber(self.centralwidget)
        self.lcdNumber.setObjectName("lcdNumber")
        self.gridLayout.addWidget(self.lcdNumber, 0, 2, 1, 1)
        self.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(self)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 268, 25))
        self.menubar.setObjectName("menubar")
        self.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)

        self.retranslateUi()

        QtCore.QObject.connect(self.horizontalSlider, QtCore.SIGNAL("valueChanged(int)"), self.lcdNumber.display)
        QtCore.QMetaObject.connectSlotsByName(self)

    def retranslateUi(self):
        self.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

This could be done by a code generator without the need of boring editing. My question is: Is there one such code generator available?

user3202179
  • 111
  • 2
  • 3
    You should **not** modify automatically generated files. The "The code is hard to read and extend." does **not** hold because you are **not** supposed to touch that code *at all*. The output produced by `pyuic` is meant to be used as parent class. In your case you'd do `class MainWindow(QMainWindow, Ui_MainWindow):` then in your `__init__` you call `setupUi(self)` and you are done. Without having to look into the generated code. – Bakuriu Jan 16 '14 at 11:44
  • @Bakuriu, thanks for your answer! :) That `class MainWindow(QMainWindow, Ui_MainWindow)` was surprising but excellent! – user3202179 Jan 16 '14 at 12:18
  • I don’t see why that’s “not OOP style”. A good OOP design rule is to favor composition over inheritance, which is exactly what uic does. The current pattern was actually introduced in Qt4 on purpose, opposed to Qt3, where the generated widgets were subclasses. See http://qt-project.org/doc/qt-4.8/porting4-designer.html for more. – Frank Osterfeld Jan 16 '14 at 12:38
  • @Frank, I couldn't see the OO possibilities until Bakuriu told the use of multi-inheritance. Now I see. – user3202179 Jan 16 '14 at 13:14
  • Okay, I have to admit that I have used wxWidgets earlier... :-[ Please don't shoot me. ;) – user3202179 Jan 16 '14 at 13:25
  • @user3202179: You confuse inheritance (heavily overused) with OOP :) At least in C++, the multi-inheritance approach is actually rarely used, one usually make the generated class a member of the dialog (composition). – Frank Osterfeld Jan 16 '14 at 14:14
  • @FrankOsterfeld. I much preferred the PyQt3 approach, which is _way, way_ simpler for PyQt newbies to grok. There are screeds of questions in the python/qt tags which result from the confusion caused by the indirection of the composition-style approach. With PyQt3, you just imported your main form, created a simple subclass (single-inheritance), and everything was exactly where you expected it to be (i.e. a direct attribute or method of your subclass). – ekhumoro Jan 17 '14 at 04:42

2 Answers2

2

You can use multiple inheritance instead (see Using Qt Designer in PyQt4 for possible approaches):

from PyQt4.QtGui import QDialog
from ui_imagedialog import Ui_ImageDialog

class ImageDialog(QDialog, Ui_ImageDialog):
    def __init__(self):
        QDialog.__init__(self)

        # Set up the user interface from Designer.
        self.setupUi(self)

        # Make some local modifications.
        self.colorDepthCombo.addItem("2 colors (1 bit per pixel)")

        # Connect up the buttons.
        self.okButton.clicked.connect(self.accept)
        self.cancelButton.clicked.connect(self.reject)

Nevertheless, both approaches are OOP-style. Each of them has pros and cons.

And don't read or edit automatically generated files.

Pavel Strakhov
  • 39,123
  • 5
  • 88
  • 127
  • Thank you as well! :) One more stupid question: How do you guys find out the names of your child widgets without reading the ui_-files? From Qt Designer? (You can't remember everything, do you?) – user3202179 Jan 16 '14 at 13:46
  • I use autocomplete for C++ code (in Qt Creator). I don't know how to get it work for Python but I think there might be a way. – Pavel Strakhov Jan 16 '14 at 15:36
  • @user3202179. I make sure I reset the `objectName` property for all the widgets I'm interested in, and use a consistent naming style for all my PyQt applications. Of course, autocompletion helps as well ;-) – ekhumoro Jan 17 '14 at 04:14
  • @user3202179 I set them in designer – Frodon Jan 17 '14 at 09:17
1

You don't even have to generate the python file from the ui file: use the uic module

import os
from PyQt4 import QtGui, QtCore, uic

class MyMainWindow(QtGui.QMainWindow):
  def __init__(self, parent):
    QtGui.QMainWindow.__init__(self, parent)
    # We want to inheritate from MyMainWindow.ui
    uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)),"MyMainWindow.ui"), self)

    # All widgets are available as classic attributes:
    self.okButton.clicked.connect(self.accept)
Frodon
  • 3,684
  • 1
  • 16
  • 33
  • Unless I'm missing something, don't you need to assign the result of `uic.loadUI()` to something. For instance, I think your example should read `self.ui = uic.loadUi(...)' and 'self.ui.okButton.clicked.connect(self.accept)` – three_pineapples Jan 16 '14 at 23:26
  • @three_pineapples. You are missing something: the `baseinstance` argument to `loadUi` (which is `self` in this case). It bypasses the usual `setupUi` boiler-plate nonsense, and injects the ui directly into the passed in instance. – ekhumoro Jan 17 '14 at 04:24
  • @ekhumoro Oh...I had to scroll to see that and didn't notice the baseinstance argument was being used. Oops! – three_pineapples Jan 17 '14 at 07:25