前言
最近在入坑pyside做一下桌面开发,下面以其他人做的软件说明一下如何实现:
1、使用qt designer进行组件开发
2、导出到py代码
3、使用qt designer导入这个自定义插件再进行开发
下面是需要用到的基础以及项目源代码:
https://github.com/muziing/PySide6-Code-Tutorial
https://github.com/Jai-wei/YOLOv8-PySide6-GUI
ps:本文是以YOLOv8-PySide6-GUI 里面的home界面作为例子,截取其中一个界面元素作为ui组件,再行开发的。请自行准备素材以及项目代码。
ps2:默认你已经知道怎么使用qt designer,怎么打开qt designer,怎么新建ui文件,怎么将ui文件导出为 py代码。
过程
项目代码如下:
ps:红线画出来的是我先行摸索探索做的ui组件,花了点时间终于打通了。
运行界面如下:
组件编写说明
组件场景,如下图:
下面要将这个显示块,total targets做成一个组件(如果你看过home里面的xml或者home.py代码会发现,就一个显示块里面包含的组件不少的,拿它来组件化很合适)。
home.py里面相关代码如下:
self.Target_QF = QFrame(self.QF_Group)
self.Target_QF.setObjectName(u"Target_QF")
self.Target_QF.setMinimumSize(QSize(170, 80))
self.Target_QF.setMaximumSize(QSize(170, 80))
self.Target_QF.setToolTipDuration(0)
self.Target_QF.setStyleSheet(u"QFrame#Target_QF{\n"
"color: rgb(255, 255, 255);\n"
"border-radius: 15px;\n"
"background-color: qradialgradient(cx:0, cy:0, radius:1, fx:0.1, fy:0.1, stop:0 rgb(253, 139, 133), stop:1 rgb(248, 194, 152));\n"
"border: 1px outset rgb(252, 194, 149)\n"
"}\n"
"")
self.Target_QF.setFrameShape(QFrame.Shape.StyledPanel)
self.Target_QF.setFrameShadow(QFrame.Shadow.Raised)
self.verticalLayout_9 = QVBoxLayout(self.Target_QF)
self.verticalLayout_9.setSpacing(0)
self.verticalLayout_9.setObjectName(u"verticalLayout_9")
self.verticalLayout_9.setContentsMargins(0, 0, 0, 0)
self.Target_top = QFrame(self.Target_QF)
self.Target_top.setObjectName(u"Target_top")
self.Target_top.setStyleSheet(u"border:none")
self.Target_top.setFrameShape(QFrame.Shape.StyledPanel)
self.Target_top.setFrameShadow(QFrame.Shadow.Raised)
self.horizontalLayout_7 = QHBoxLayout(self.Target_top)
self.horizontalLayout_7.setSpacing(0)
self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
self.horizontalLayout_7.setContentsMargins(0, 3, 0, 3)
self.label_6 = QLabel(self.Target_top)
self.label_6.setObjectName(u"label_6")
self.label_6.setMaximumSize(QSize(16777215, 30))
font2 = QFont()
font2.setFamilies([u"Segoe UI"])
font2.setPointSize(16)
font2.setBold(True)
font2.setItalic(True)
self.label_6.setFont(font2)
self.label_6.setStyleSheet(u"color: rgba(255, 255, 255,210);\n"
"padding-left:12px;\n"
"font: 700 italic 16pt \"Segoe UI\";")
self.label_6.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.label_6.setIndent(0)
self.horizontalLayout_7.addWidget(self.label_6)
self.verticalLayout_9.addWidget(self.Target_top)
self.line_3 = QFrame(self.Target_QF)
self.line_3.setObjectName(u"line_3")
self.line_3.setMaximumSize(QSize(16777215, 1))
self.line_3.setStyleSheet(u"background-color: rgba(255, 255, 255, 89);")
self.line_3.setFrameShape(QFrame.Shape.HLine)
self.line_3.setFrameShadow(QFrame.Shadow.Sunken)
self.verticalLayout_9.addWidget(self.line_3)
self.Target_bottom = QFrame(self.Target_QF)
self.Target_bottom.setObjectName(u"Target_bottom")
self.Target_bottom.setStyleSheet(u"border:none")
self.Target_bottom.setFrameShape(QFrame.Shape.StyledPanel)
self.Target_bottom.setFrameShadow(QFrame.Shadow.Raised)
self.verticalLayout_10 = QVBoxLayout(self.Target_bottom)
self.verticalLayout_10.setSpacing(0)
self.verticalLayout_10.setObjectName(u"verticalLayout_10")
self.verticalLayout_10.setContentsMargins(0, 6, 0, 6)
self.Target_num = QLabel(self.Target_bottom)
self.Target_num.setObjectName(u"Target_num")
self.Target_num.setMinimumSize(QSize(0, 30))
self.Target_num.setMaximumSize(QSize(16777215, 30))
font3 = QFont()
font3.setFamilies([u"Microsoft YaHei UI"])
font3.setPointSize(17)
font3.setBold(False)
font3.setItalic(False)
font3.setUnderline(False)
self.Target_num.setFont(font3)
self.Target_num.setStyleSheet(u"color: rgb(255, 255, 255);\n"
"font: 17pt \"Microsoft YaHei UI\";")
self.Target_num.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.verticalLayout_10.addWidget(self.Target_num, 0, Qt.AlignmentFlag.AlignTop)
self.verticalLayout_9.addWidget(self.Target_bottom)
self.verticalLayout_9.setStretch(1, 2)
self.verticalLayout_9.setStretch(2, 1)
self.horizontalLayout_3.addWidget(self.Target_QF)
稍微分析下,里面有一个top , bottom, 中间有一根横线line,top里面有一个label,bottom里面也有一个label。。。
复杂度可以成为一个组件。
组件规则说明
假设组件名称为:
ColorfulBlock,
一个组件由三部分组成:
A/ ui_colorfulblock.ui – xml形式的ui文件,由qt designer使用维护,方便随时修改。注意·:根元素是QFrame且objectName是ColorfulBlock (根元素的objectName会影响生成的python代码的类名称,规则是: Ui_objectName,例如这里指定的objectName是ColorfulBlock,那么 ui的类名为:Ui_ColorfulBlock)
B/ ui_colorfulblock.py – 从ui文件导出的界面py代码。
C/ ctrl_colorfulblock.py – 备用。 这个是类似于控制器的角色,用于手动修改更新组件状态以及数据绑定,更新或者删除数据等等。将它提取出来做控制器是因为,ui会经常变动的。每次都要自动生成以及覆盖。
创建组件:
打开 qt designer,选择好创建的组件:
然后看到:
好了,先保存一下,保存到项目的ui目录下:
然后,先关闭了qt designer先,因为接下来是要直接从home.ui复制了total targets那一部分ui代码到ui_colorfulblock.ui中(全手工操作)。
打开home.ui找到 total targets部分xml代码:
小提示:先找到 Target_QF 所在的widget标签,再整个标签都复制了:
代码应该如下:
<widget class="QFrame" name="Target_QF">
<property name="minimumSize">
<size>
<width>170</width>
<height>80</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>170</width>
<height>80</height>
</size>
</property>
<property name="toolTipDuration">
<number>0</number>
</property>
<property name="styleSheet">
<string notr="true">QFrame#Target_QF{
color: rgb(255, 255, 255);
border-radius: 15px;
background-color: qradialgradient(cx:0, cy:0, radius:1, fx:0.1, fy:0.1, stop:0 rgb(253, 139, 133), stop:1 rgb(248, 194, 152));
border: 1px outset rgb(252, 194, 149)
}
</string>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9" stretch="0,2,1">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="Target_top">
<property name="styleSheet">
<string notr="true">border:none</string>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="font">
<font>
<family>Segoe UI</family>
<pointsize>16</pointsize>
<italic>true</italic>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgba(255, 255, 255,210);
padding-left:12px;
font: 700 italic 16pt "Segoe UI";</string>
</property>
<property name="text">
<string>Total Targets</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>1</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgba(255, 255, 255, 89);</string>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="Target_bottom">
<property name="styleSheet">
<string notr="true">border:none</string>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QLabel" name="Target_num">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
<property name="font">
<font>
<family>Microsoft YaHei UI</family>
<pointsize>17</pointsize>
<italic>false</italic>
<bold>false</bold>
<underline>false</underline>
</font>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(255, 255, 255);
font: 17pt "Microsoft YaHei UI";</string>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
将这部分复制到新建的ui_colorfulblock.ui中:
例如:
把第一个widget给替换掉,结果如下:
补充一点:
class里面的是类名,你需要替换为自己的组件名称:
<class>ColorfulBlock</class>
保存下,然后使用qt designer打开,如下:
看到效果了,最初一步已经成功了。
下面要修改objectName:
修改为我们的组件名称:
针对于这个组件还有一个坑,就是它使用了样式,且样式里面指定了 Target_QF,但是我们的组件的objectName都改了,当然也要修改样式了:
修改为:
基本可用了,下面就导出一下py代码:
pyside6-uic ui_colorfulblock.ui -o ui_colorfulblock.py
然后看到:
下面就是一个关键点了。
第一,我们的组件是继承了QFrame制作的,但是这里使用的是object,所以这里要改为父类为QFrame。
第二,因为qt designer 使用自定义控件是使用 promote (提升)方式的,生成的代码会是:
## 伪代码
self.ColorfulBlock = Ui_ColorfulBlock(self.QF_Group)
self.ColorfulBlock.setObjectName(u"ColorfulBlock")
这种形式,就是说,构造器至少有一个参数,接收了被提升的qframe对象的,而组件自动生成的代码是不会有构造器,而且 setupUi也不会自动运行,就是说,我们要添加一个构造器且在构造器里面运行setupUi,下面基本是每一个生成的ui都需要用到的(当然有另一种方式是不需要的,详情请看项目的main.py是怎么做的):
下面做出如下修改:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
好了,下面来处理home.ui,导入我们抽取出去的组件。
首先需要将 Target_QF下面的所有子元素都删除掉,styleSheet也清空一下,且objectName修改为ColorfulBlock,然后,选择ColorfulBlock进行提升(promote):
1/
注意,清空属性不是删除字符串-那是变成空字符串而已,要点击后面的那个 删除按钮才行的。
下面进行提升
2、
下面说明一下,
header file 其实是 需要调用这个组件class时候的 路径,这里在ui文件夹下面的ui_colorfulblock.py所以路径是:
ui.ui_colorfulblock
class name就是类名了。
好了,可以尝试将home.ui导出一下py:
## ps:我的项目源码是改过的,请自行按照自己的源码进行修改。
pyside6-uic page_home.ui -o page_home.py
得到了:
然后得到了这部分代码。
好了,尝试运行一下:
resource_rc文件是在ui目录下面的,应该改为:
import ui.resources_rc
再尝试:
在main文件下面还有对Target_QF的调用,明显是不行的,都注释掉,还有部分代码–明显引用了组件里面的元素,也要替换掉,例如:
再尝试:
替换组件成功。