将小部件从 .ui 文件加载到由单独的 .ui 文件定义的窗口中 [英] Loading a widget from a .ui file into a window defined by a separate .ui file

查看:36
本文介绍了将小部件从 .ui 文件加载到由单独的 .ui 文件定义的窗口中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这里的总体目标是定义一个包含多个重要小部件的主应用程序,其中每个小部件使用单独的 Python 类定义并从单独的 .ui 文件加载.

当小部件模块直接加载其 .ui 文件时,当我启动主应用程序时,我似乎无法显示小部件 UI.

我希望看到的是这个(图 1)

           

我看到的是这个(图2)

         

这是生成图 2 的代码.它摘自 HETP_main.py,在最后.

QtWidgets.QMainWindow.__init__(self)Ui_MainWindow.__init__(self)self.topPanelWDG = TopPanel() # 设置顶部面板的行为self.setupUi(self)

以下代码将生成图 1(我想要的).然而,它在 python 代码中构建主窗口,而不是从 .ui 文件加载它.此代码是使用 pyuic5 生成的,但我必须删除注释掉的行才能使其正常工作.

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) # 移除# self.topPanelWDG.setMinimumSize(QtCore.QSize(0, 75)) # 移除# self.topPanelWDG.setMaximumSize(QtCore.QSize(16777215, 75)) # 移除# self.topPanelWDG.setStyleSheet("background-color: yellow") # 移除# self.topPanelWDG.setObjectName("topPanelWDG") # 移除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) # 删除# QtCore.QMetaObject.connectSlotsByName(MainWindow) # 移除

很明显,我以某种方式双重定义了 topPanelWDG,但我不知道如何或如何修复它.所以我的问题是如何在从 .ui 文件加载时获得图 1.

附上此处使用的完整 .py 和 .ui 文件.

以下是.py和两个.ui文件的全部内容.

HETP_main.py

```from PyQt5 import QtCore, QtGui, QtWidgets, uic导入系统Ui_MainWindow, QtBaseClass = uic.loadUiType("main.ui")类 TopPanel(QtWidgets.QWidget):def __init__(self):super(TopPanel, self).__init__()uic.loadUi("toppanel.ui", self)类 MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):def __init__(self):QtWidgets.QMainWindow.__init__(self)Ui_MainWindow.__init__(self)构造方法 = '加载'构造方法 = 'build2'如果构造方法 == '加载':self.topPanelWDG = TopPanel() # 设置顶部面板的行为self.setupUi(self)别的:self.topPanelWDG = TopPanel() # 设置顶部面板的行为self.construct2(self)self.setWindowTitle(construct_method)def 构造 2(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) # 移除# self.topPanelWDG.setMinimumSize(QtCore.QSize(0, 75)) # 移除# self.topPanelWDG.setMaximumSize(QtCore.QSize(16777215, 75)) # 移除# self.topPanelWDG.setStyleSheet("background-color: yellow") # 移除# self.topPanelWDG.setObjectName("topPanelWDG") # 移除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) # 删除# QtCore.QMetaObject.connectSlotsByName(MainWindow) # 移除# def retranslateUi(self, MainWindow): # 移除# _translate = QtCore.QCoreApplication.translate # 删除# MainWindow.setWindowTitle(_translate("MainWindow", "HETP Scanning System")) # 移除app = QtWidgets.QApplication(sys.argv)窗口 = 主窗口()window.show()app.exec_() # 应该通过命令行 kwargs 吗?```

toppanel.ui

```<string>顶部面板</string></属性></小部件></项目></布局></小部件></项目></布局></小部件><资源/><连接/></ui>```

main.ui

我很难将其包含在问题中,因此可以在此处获得.

最后,点击添加",然后点击提升",并保存文件.

此时您只需要将 *args 和 **kwargs 参数添加到自定义小部件初始化中.这些是必要的,因为现在创建小部件是 Qt 的责任,并且由于所有小部件至少接受一个参数(父),当 Qt 创建它们时,这些参数将被添加:如果 __init__ 函数不接受这些参数,python 将引发 TypeError 异常,因为它正在接收意外的参数.

from PyQt5 import QtWidgets, uic导入系统类 TopPanel(QtWidgets.QWidget):def __init__(self, *args, **kwargs):super(TopPanel, self).__init__(*args, **kwargs)uic.loadUi("toppanel.ui", self)类 MainWindow(QtWidgets.QMainWindow):def __init__(self):super(MainWindow, self).__init__()uic.loadUi("main.ui", self)如果 __name__ == "__main__":app = QtWidgets.QApplication(sys.argv)窗口 = 主窗口()window.show()sys.exit(app.exec_())

为自定义小部件使用单独的文件通常也是一个好习惯,因为它们的代码将在每次添加升级小部件时加载.
这也意味着将实际运行您的程序的代码部分必须包含在 if __name__ == "__main__": 语句中,如果自定义小部件类在该文件中;这在任何情况下都是一个好习惯,因为每当导入文件时,它的主缩进代码"总是运行.

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)

               

What I seeing is this (Figure 2)

            

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

解决方案

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.

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.

这篇关于将小部件从 .ui 文件加载到由单独的 .ui 文件定义的窗口中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆