0

My overall goal here is to define have a main app with multiple important widgets, where each widget is defined with a separate python class and loaded from a separate .ui file.

I can't seem to get widget UI to show up when I start the main app when the widget module loads its .ui file directly.

What I am hoping to see is this (Figure 1)

               enter image description here

What I seeing is this (Figure 2)

            enter image description here

Here is the code that generates Figure 2. It is excerpted from HETP_main.py, which is at the end.

QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.topPanelWDG = TopPanel()   # set up behaviors for the top panel
self.setupUi(self)

The following code will generate Figure 1 (what I want). However it constructs the main window in python code rather than loading it from a .ui file. This code was generated using pyuic5 but I had to remove the commented-out lines to get it to work.

MainWindow.setObjectName("MainWindow")
MainWindow.resize(649, 130)
MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setStyleSheet("QPushButton {background-color: rgb(239, 239, 239)};")
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
# self.topPanelWDG = QtWidgets.QWidget(self.centralwidget)     # removed
# self.topPanelWDG.setMinimumSize(QtCore.QSize(0, 75))         # removed
# self.topPanelWDG.setMaximumSize(QtCore.QSize(16777215, 75))  # removed
# self.topPanelWDG.setStyleSheet("background-color: yellow")   # removed
# self.topPanelWDG.setObjectName("topPanelWDG")                # removed
self.verticalLayout.addWidget(self.topPanelWDG)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)

# self.retranslateUi(MainWindow)                               # removed
# QtCore.QMetaObject.connectSlotsByName(MainWindow)            # removed

It's pretty clear that I am somehow doubly defining topPanelWDG, but I don't see how or how to fix it. So my question is how do I get Figure 1 while loading from a .ui file.

The full .py and .ui files used here are attached.

The following is the full contents of the .py and the two .ui files.

HETP_main.py

```from PyQt5 import QtCore, QtGui, QtWidgets, uic
import sys

Ui_MainWindow, QtBaseClass = uic.loadUiType("main.ui")


class TopPanel(QtWidgets.QWidget):
    def __init__(self):
        super(TopPanel, self).__init__()
        uic.loadUi("toppanel.ui", self)


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        construct_method = 'load'
        construct_method = 'build2'
        if construct_method == 'load':
            self.topPanelWDG = TopPanel()   # set up behaviors for the top panel
            self.setupUi(self)
        else:
            self.topPanelWDG = TopPanel()   # set up behaviors for the top panel
            self.construct2(self)

        self.setWindowTitle(construct_method)

    def construct2(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(649, 130)
        MainWindow.setStyleSheet("")
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setStyleSheet("QPushButton {background-color: rgb(239, 239, 239)};")
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        # self.topPanelWDG = QtWidgets.QWidget(self.centralwidget)     # removed
        # self.topPanelWDG.setMinimumSize(QtCore.QSize(0, 75))         # removed
        # self.topPanelWDG.setMaximumSize(QtCore.QSize(16777215, 75))  # removed
        # self.topPanelWDG.setStyleSheet("background-color: yellow")   # removed
        # self.topPanelWDG.setObjectName("topPanelWDG")                # removed
        self.verticalLayout.addWidget(self.topPanelWDG)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        # self.retranslateUi(MainWindow)                               # removed
        # QtCore.QMetaObject.connectSlotsByName(MainWindow)            # removed


    # def retranslateUi(self, MainWindow):                                               # removed
    #     _translate = QtCore.QCoreApplication.translate                                 # removed
    #     MainWindow.setWindowTitle(_translate("MainWindow", "HETP Scanning System"))    # removed

app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()  # should pass command line kwargs?```

toppanel.ui

```<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>654</width>
    <height>72</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QHBoxLayout" name="horizontalLayout_2">
   <item>
    <widget class="QFrame" name="vanFrame">
     <property name="sizePolicy">
      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <property name="minimumSize">
      <size>
       <width>0</width>
       <height>48</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>16777215</width>
       <height>64</height>
      </size>
     </property>
     <property name="styleSheet">
      <string notr="true">
QWidget {background-color: rgb(167, 255, 195)}
.QPushButton {
    background-color: LightGray
    opacity 0.2;
    font: bold;
};</string>
     </property>
     <property name="frameShape">
      <enum>QFrame::Panel</enum>
     </property>
     <property name="lineWidth">
      <number>0</number>
     </property>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <widget class="QLabel" name="label">
        <property name="text">
         <string>Top Panel</string>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>```

main.ui

I had difficulty including this in the question so it is available here. main.ui

WesR
  • 1,292
  • 1
  • 18
  • 30
  • 1
    It seems you are defining class MainWindow twice - once in Python and once in main.ui (which also creates a class MainWindow). Try renaming one of them to something else. – Mario Camilleri Nov 14 '19 at 06:33
  • Thanks @makam. I tried changing the name of the QMainWindow in the main.ui file but this did not change the result. – WesR Nov 14 '19 at 19:02

1 Answers1

1

There are two possible solutions to your issue.

The easiest way is to remove the topPanelWDG from the main.ui, and manually add it from code.

from PyQt5 import QtWidgets, uic

# the following line is not necessary, as we can use loadUi in the same way for
# both the widget *and* the main window
# Ui_MainWindow, QtBaseClass = uic.loadUiType("main.ui")


class TopPanel(QtWidgets.QWidget):
    def __init__(self):
        super(TopPanel, self).__init__()
        uic.loadUi("toppanel.ui", self)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        uic.loadUi("main.ui", self)
        self.topPanelWDG = TopPanel()
        self.verticalLayout.addWidget(self.topPanelWDG)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    # there's no need to pass args here, usually they're important only to the app
    sys.exit(app.exec_())

Sometimes, though, you can't easily (or don't want to) add a widget to a layout of an UI created from designer from your code.
An alternative approach is to use what Qt calls "promoted widgets".

This works by adding a widget to the ui that will be used as an advanced "placeholder" for the actual widget class you're implementing. In this specific situation, it will be a standard QWidget, but if you're subclassing other widgets (buttons, tables, etc) you'll use them, since it will allows you to set their base properties (a label for the button, the headers of the table, etc) directly from Designer, leaving the class code to do only do what you need to implement.

In your case, leave the topPanelWDG in the main ui file, right click it and select "Promote to". In the "Promoted class name" insert the class name that will be used (TopPanel), and in the "Header file" field write the file name (including the relative path, if it is in a subdirectory) of the python file that contains the TopPanel class definition, without the py extension.

Promoting a widget

Finally, click "Add" and then "Promote", and save the file.

At this point you only need to add the *args and **kwargs arguments to the custom widget initialization. These are necessary as now it will be Qt's responsibility to create the widget and, since all widgets accept at least an argument (the parent) those arguments will be added when Qt creates them: if the __init__ function does not accept those arguments, python will raise a TypeError exception because it's receiving unexpected arguments.

from PyQt5 import QtWidgets, uic
import sys

class TopPanel(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super(TopPanel, self).__init__(*args, **kwargs)
        uic.loadUi("toppanel.ui", self)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        uic.loadUi("main.ui", self)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

It's also usually a good habit to use separated files for custom widgets, as their code will be loaded every time a promoted widget is added.
This also means that the portion of code that will actually run your program has to be enclosed in the if __name__ == "__main__": statement, if the custom widget class is in that file; this is a good habit in any case, as whenever a file is imported it's "main indentation code" is always run.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • I know that StackOverflow doesn't want me to thank you, so let me ask, do you do tutorials? Other readers might note that this also explains the mysterious Designer promotion window and explains an issue about QWidget, __init__.() arguments where I found it very difficult to interpret the error message. A trifecta! – WesR Nov 15 '19 at 21:44