PyQt5桌面应用开发(4):界面设计
PyQt5桌面应用系列
- PyQt5桌面应用开发(1):需求分析
- PyQt5桌面应用开发(2):事件循环
- PyQt5桌面应用开发(3):并行设计
- PyQt5桌面应用开发(4):界面设计
- PyQt5桌面应用开发(5):对话框
- PyQt5桌面应用开发(6):文件对话框
- PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
- PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
- PyQt5桌面应用开发(9):经典布局QMainWindow
- PyQt5桌面应用开发(10):界面布局基本支持
- PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
- PyQt5桌面应用开发(12):QFile与线程安全
- PyQt5桌面应用开发(13):QGraphicsView框架
- PyQt5桌面应用开发(14):数据库+ModelView+QCharts
- PyQt5桌面应用开发(15):界面动画
- PyQt5桌面应用开发(16):定制化控件-QPainter绘图
- PyQt5桌面应用开发(17):类结构+QWebEngineView
前言
经过需求分析、事件循环和并行设计的介绍,终于还是要写一篇界面设计的内容。很不情愿,因为我这个部分很不擅长,设计的界面很丑很丑。
其实丑并不可怕,关了灯都一样……
首先,我们再回到需求分析的话题。
为什么又是需求分析?
需求分析,这条内裤,始终是要穿上的。
那么界面设计的内裤大概是什么样的呢,有很多人是从美观的角度出发设计出漂亮的界面。我们搬砖的找了一堆支持的理论,还是只能设计出巨丑的界面。一般而言,我们是从用户体验的角度出发,结合软件需求分析的过程来确定界面。
从用户需求这里,基本上就能确定数据报表,从功能需求分析这里,就能确定用户交互。这两个部分最终构成用户界面的开发需求。
- 报表界面;
- 交互界面。
等到报表界面和交互界面确定后,就是利用PyQt5提供的工具来实现。
PyQt5的界面设计元素
在采用PyQt5设计界面时,我们常用和标准的工具就是Designer。如何安装和使用Designer,我实在不耐烦讲。网上教程(视频)和参考貌似比较多。就只是在CSDN上,就一大堆,CSDN搜索pyqt5 designer即可。如果英语好,也有更多参考内容,随便举个栗子。我就大概拉一拉界面设计元素,以作参考。
界面设计元素分类
在Designer里面,PyQt5的界面设计元素分为8个大类:
- Layouts:界面布局
- Spacers:空间间隔
- Buttons:动作按钮
- Item Views:数据视图
- Item Widgets:数据控件
- Containers:空间容器
- Input Widgets:输入控件
- Display Widgets:显示控件
直接使用的话,就大概是按照这这八类来布置。这个分类,基本上是按照控件的功能来分的。使用的时候,我们还应该根据前面所讲的开发需求来进行进一步的分类。
所以我们的分类是这样的:
a.报表功能类(共18个控件)
b.输入功能类(共22个控件)
c.布局功能类(共16个控件)
各个具体的控件功能,已经一些教程进行详细的指导。
编译为Python代码使用
转换命令行
我们很随意的假设要展示三组数据(报表1:树表列),然后用Designer画了一个控件。
然后很随意的存成文件tree_data_view.ui
,用下面的命令,就可以把界面文件转化成Python文件。这里增加一个参数-x
,就给输出的文件增加一个运行的例子。
pyuic5 .\tree_data_view.ui -o .\tree_data_view.py -x
接下来就能运行,显示界面示例。
python .\tree_data_view.ui -o .\tree_data_view.py -x
跟我们在Designer里面画的界面一模一样。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>1106</width>
<height>584</height>
</rect>
</property>
<property name="windowTitle">
<string>Item Widgets Demo</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0">
<item>
<widget class="QTreeWidget" name="treeWidget">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QTableWidget" name="tableWidget"/>
</item>
<item>
<widget class="QListWidget" name="listWidget"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
当然转换得到的Python文件更容易读懂。
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '.\tree_data_view.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(1106, 584)
self.horizontalLayout = QtWidgets.QHBoxLayout(Form)
self.horizontalLayout.setObjectName("horizontalLayout")
self.treeWidget = QtWidgets.QTreeWidget(Form)
self.treeWidget.setObjectName("treeWidget")
self.treeWidget.headerItem().setText(0, "1")
self.horizontalLayout.addWidget(self.treeWidget)
self.tableWidget = QtWidgets.QTableWidget(Form)
self.tableWidget.setObjectName("tableWidget")
self.tableWidget.setColumnCount(0)
self.tableWidget.setRowCount(0)
self.horizontalLayout.addWidget(self.tableWidget)
self.listWidget = QtWidgets.QListWidget(Form)
self.listWidget.setObjectName("listWidget")
self.horizontalLayout.addWidget(self.listWidget)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Item Widgets Demo"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
一般而言,我们使用这个代码时,有一个原则:不更改由pyuic5生成的代码。
界面设计信息唯一保存于ui文件中。
大概见到的使用这个报表的方法有3种。
组合使用
我们先看pyuic5 -x定义的使用方法,声明一个Ui_Form
,默认的名字总是这个;把一个QWidget
作为参数调用Ui_Form.setupUi
。
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
要正经的使用我们的报表,可以设计一个按钮,按按钮就显示报表。
from tree_data_view import Ui_Form
from PyQt5 import QtWidgets, QtCore
class TreeData_View(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TreeData_View, self).__init__(parent)
# 设置界面
self.ui = Ui_Form()
self.ui.setupUi(self)
# 重新定义变量,为了更好地获得IDE的支持,更容易编写程序
self.treeWidget: QtWidgets.QTreeWidget = self.ui.treeWidget
self.listWidget: QtWidgets.QListWidget = self.ui.listWidget
self.tableWidget: QtWidgets.QTableWidget = self.ui.tableWidget
# fake data
self._init_tree()
self._init_table()
self._init_list()
def _init_tree(self):
# prepare data for QTreeWidget
self.treeWidget.setHeaderLabels(["Name", "Value"])
self.treeWidget.setColumnCount(2)
self.treeWidget.setColumnWidth(0, 200)
self.treeWidget.setColumnWidth(1, 200)
self.treeWidget.setSortingEnabled(True)
self.treeWidget.setAlternatingRowColors(True)
self.treeWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.treeWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
root = QtWidgets.QTreeWidgetItem(self.treeWidget)
root.setText(0, "Root")
root.setText(1, "Root Value")
child1 = QtWidgets.QTreeWidgetItem(root)
child1.setText(0, "Child 1")
child1.setText(1, "Child 1 Value")
child2 = QtWidgets.QTreeWidgetItem(root)
child2.setText(0, "Child 2")
child2.setText(1, "Child 2 Value")
grandchild1 = QtWidgets.QTreeWidgetItem(child1)
grandchild1.setText(0, "Grandchild 1")
grandchild1.setText(1, "Grandchild 1 Value")
grandchild2 = QtWidgets.QTreeWidgetItem(child1)
grandchild2.setText(0, "Grandchild 2")
grandchild2.setText(1, "Grandchild 2 Value")
self.treeWidget.addTopLevelItem(root)
self.treeWidget.expandAll()
def _init_table(self):
# prepare data for QTableWidget
self.tableWidget.setColumnCount(2)
self.tableWidget.setRowCount(3)
self.tableWidget.setHorizontalHeaderLabels(["Name", "Value"])
self.tableWidget.setColumnWidth(0, 200)
self.tableWidget.setColumnWidth(1, 200)
self.tableWidget.setSortingEnabled(True)
self.tableWidget.setAlternatingRowColors(True)
self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.tableWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
for row in range(3):
for col in range(2):
item = QtWidgets.QTableWidgetItem(f"Item {row}, {col}")
self.tableWidget.setItem(row, col, item)
def _init_list(self):
# prepare data for QListWidget
self.listWidget.setSortingEnabled(True)
self.listWidget.setAlternatingRowColors(True)
self.listWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.listWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.listWidget.addItems([f"Item {i}" for i in range(10)])
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
win = QtWidgets.QMainWindow()
Form = TreeData_View()
button = QtWidgets.QPushButton("Show TreeData_View")
button.clicked.connect(lambda: Form.show())
win.setCentralWidget(button)
win.show()
sys.exit(app.exec_())
这个也是相当直观的。
继承使用方式
我们还可以采用继承的方式,仅有小小的不同。
class TreeData_View(QtWidgets.QWidget, Ui_Form):
def __init__(self, parent=None):
super(TreeData_View, self).__init__(parent)
self.setupUi(self)
self.treeWidget: QtWidgets.QTreeWidget
self.listWidget: QtWidgets.QListWidget
self.tableWidget: QtWidgets.QTableWidget
此时,代码稍微简单一点点,可读性也没有变差。
直接使用ui文件的方法
直接使用ui的方法也很简单,也只要更改一点点代码。
from PyQt5 import QtWidgets, QtCore, uic
class TreeData_View(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TreeData_View, self).__init__(parent)
uic.loadUi("tree_data_view.ui", self)
self.treeWidget: QtWidgets.QTreeWidget
self.listWidget: QtWidgets.QListWidget
self.tableWidget: QtWidgets.QTableWidget
这里的loadUi
函数的第二个参数是一个QWidget对象,如果不输入这个参数,就直接新建一个QWidget。这里值得注意的是,ui文件中定义的控件,直接就赋给了QWidget对象,与组合方式中略有不同,更类似与多继承的方式。
总结
- 8类控件可以分为3大类;
- 三种使用ui文件的方式;
- 每种方式都差不多,为了IDE支持代码,可以把控件的类型都声明一遍。