PySide2 Qt Designer

Foreword

Original article:
https://www.byhy.net/tut/py/gui/qt_03/

1. Qt Designer introduction

QT program interface a window, controls, is like the above with the corresponding code created.

However, it is difficult to write the interface directly from your head in code.

A lot of times, what the runtime looks like is not what we want it to look like. We often have to modify the code to adjust the position of the controls on the interface and then run the preview. Do this many times over.

But this, really… It’s too much trouble.

In fact, we can use the QT interface generator QT Designer, dragging and dropping can intuitively create the general interface of the program.

So how do I run this tool?

On Windows, run the executable script pyside2\Scripts\pyside2-designer.exe from the Python installation directory.

You can modify the title and placeholderText in the Property Editor.

Interfaces designed through Qt Designer are ultimately saved in a UI file.

So if you go to this UI file, it’s just an XML definition of the interface.

You can preview the screen by pressing CTRL + R.

2. Load the UI file dynamically

The official documentation:
https://doc.qt.io/qtforpython-5/tutorials/basictutorial/uifiles.html

With the interface definition file, our Python program can load the UI definition from the file and dynamically create a corresponding Windows object.

As follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from PySide2.QtWidgets import QApplication, QMessageBox
from PySide2.QtUiTools import QUiLoader


class Stats:

def __init__(self):
# 从文件中加载UI定义

# 从 UI 定义中动态 创建一个相应的窗口对象
# 注意:里面的控件对象也成为窗口对象的属性了
# 比如 self.ui.button , self.ui.textEdit
self.ui = QUiLoader().load('ui/main.ui')

self.ui.button.clicked.connect(self.handleCalc)

def handleCalc(self):
info = self.ui.textEdit.toPlainText()

salary_above_20k = ''
salary_below_20k = ''
for line in info.splitlines():
if not line.strip():
continue
parts = line.split(' ')

parts = [p for p in parts if p]
name, salary, age = parts
if int(salary) >= 20000:
salary_above_20k += name + '\n'
else:
salary_below_20k += name + '\n'

QMessageBox.about(self.ui,
'统计结果',
f'''薪资20000 以上的有:\n{salary_above_20k}
\n薪资20000 以下的有:\n{salary_below_20k}'''
)


app = QApplication([])
stats = Stats()
stats.ui.show()
app.exec_()

If you are using PyQt5 instead of PySide2, the code to load the UI file is as follows:

1
2
3
4
5
6
7
from PyQt5 import uic

class Stats:

def __init__(self):
# 从文件中加载UI定义
self.ui = uic.loadUi("main.ui")

3. Convert UI files to Python code

There is another way to use the UI file: convert the UI file directly into the Python code file that contains the interface definitions, and then use the class that defines the interface in your program

Run the following command to convert the UI file directly to the Python code file that contains the interface definitions:

1
pyside2-uic main.ui > ui_main.py

If you are installing PyQt5, execute the following command conversion format:

1
pyuic5 main.ui > ui_main.py

Then use the class that defines the interface in your code file as such:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from PySide2.QtWidgets import QApplication, QMainWindow
from ui_main import Ui_Form


# 注意 这里选择的父类 要和你UI文件窗体一样的类型
# 主窗口是 QMainWindow, 表单是 QWidget, 对话框是 QDialog
class MainWindow(QMainWindow):

def __init__(self):
super().__init__()
# 使用ui文件导入定义界面类
self.ui = Ui_Form()
# 初始化界面
self.ui.setupUi(self)


app = QApplication([])
mainw = MainWindow()
mainw.show()
app.exec_()

So which method should we use better? Load dynamically or convert to Python code?

Usually use dynamic loading is more convenient, because after changing the interface, do not need to transform, direct operation, especially convenient.

However, if your application has controls that are not provided by Qt Designer, you will need to add some additional declarations to the code, and there may be some strange problems. Converting Python code is often the way to go.

4. A practice

The interface is as follows:

Reference UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HTTPClient</class>
<widget class="QWidget" name="HTTPClient">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>581</height>
</rect>
</property>
<property name="windowTitle">
<string>HTTP接口测试器</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="boxMethod"/>
</item>
<item>
<widget class="QLineEdit" name="editUrl"/>
</item>
<item>
<widget class="QPushButton" name="buttonSend">
<property name="text">
<string>发送</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>消息头</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonAddHeader">
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDelHeader">
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="headersTable">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<column>
<property name="text">
<string>名称</string>
</property>
</column>
<column>
<property name="text">
<string>值</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_2">
<property name="sizeIncrement">
<size>
<width>100</width>
<height>20</height>
</size>
</property>
<property name="baseSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>消息体</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTextEdit" name="editBody"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QTextEdit" name="outputWindow"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QPushButton" name="buttonClearLog">
<property name="maximumSize">
<size>
<width>300</width>
<height>500</height>
</size>
</property>
<property name="text">
<string>清除</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Reference code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import traceback

import requests
from PySide2.QtCore import QObject
from PySide2.QtGui import QIcon
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication


class HttpClient(QObject):

def __init__(self):
QObject.__init__(self)

# 从 UI 定义中动态 创建一个相应的窗口对象
self.ui = QUiLoader().load('ui/httpclient.ui')

# 给 boxMethod 添加选项 GET POST PUT DELETE
self.ui.boxMethod.addItems(
['GET', 'POST', 'PUT', 'DELETE'])

# 让 表格控件宽度随着父窗口的缩放自动缩放
self.ui.headersTable.horizontalHeader().setStretchLastSection(True)
# 设定第1列的宽度为 180像素
self.ui.headersTable.setColumnWidth(0, 180)

# 信号处理: 发送请求
self.ui.buttonSend.clicked.connect(self.sendRequest)
# 信号处理: 添加消息头
self.ui.buttonAddHeader.clicked.connect(self.addOneHeader)
# 信号处理: 删除消息头
self.ui.buttonDelHeader.clicked.connect(self.delOneHeader)
# 信号处理: 清空显示窗口
self.ui.buttonClearLog.clicked.connect(self.clearLog)

def addOneHeader(self):

# rowCount = self.ui.headersTable.rowCount()
# 要插入的行始终是当前行 的下一行
addRowNumber = self.ui.headersTable.currentRow() + 1
self.ui.headersTable.insertRow(addRowNumber)

def delOneHeader(self):

self.ui.headersTable.removeRow(
self.ui.headersTable.currentRow()
)

def clearLog(self):
self.ui.outputWindow.clear()

def sendRequest(self):

method = self.ui.boxMethod.currentText()
url = self.ui.editUrl.text()
payload = self.ui.editBody.toPlainText()

# 获取消息头
headers = {}
ht = self.ui.headersTable
for row in range(ht.rowCount()):
k = ht.item(row, 0).text()
v = ht.item(row, 1).text()
if k.strip() == '':
continue
headers[k] = v

req = requests.Request(method,
url,
headers=headers,
data=payload
)

prepared = req.prepare()

self.pretty_print_request(prepared)
s = requests.Session()

try:
r = s.send(prepared)
self.pretty_print_response(r)
except:
self.ui.outputWindow.append(
traceback.format_exc())

def pretty_print_request(self, req):

if req.body == None:
msgBody = ''
else:
msgBody = req.body

self.ui.outputWindow.append(
'{}\n{}\n{}\n\n{}'.format(
'\n\n----------- 发送请求 -----------',
req.method + ' ' + req.url,
'\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
msgBody,
))

def pretty_print_response(self, res):
self.ui.outputWindow.append(
'{}\nHTTP/1.1 {}\n{}\n\n{}'.format(
'\n\n----------- 得到响应 -----------',
res.status_code,
'\n'.join('{}: {}'.format(k, v) for k, v in res.headers.items()),
res.text,
))


app = QApplication([])
httpClient = HttpClient()
httpClient.ui.show()
app.exec_()

5. Layout

The most common Layout we use is the following four types,

  • QHBoxLayout (Horizontal)

  • QVBoxLayout (Vertical)

  • QGridLayout

  • GFormLayout

6. Ajust control position and size

6.1 Ajust the size ratio of the controls in the layout

This can be adjusted by setting the control’s sizePolicy.

6.2 Ajust control spacing

To adjust the upper and lower spacing of the control, you can add a layout to the control,
and then adjust the spacing by setting the layoutTopMargin and layoutBottomMargin of the layout.

6.3 Suggested interface layout steps

  • Don't use any Layout first, put all the controls on the interface by position.

  • Then start from the innermost layer to set the Layout of the control.

  • Gradually expand to the outer layer to set the Layout of the control.

  • Finally, adjust the size ration of the controls in the layout, and preferentially use the layoutStrentch property of the Layout to control.

7. Jump from one window to another

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from PySide2 import QtWidgets
import sys


class Window2(QtWidgets.QMainWindow):

def __init__(self):
super().__init__()
self.setWindowTitle('窗口2')

centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)

button = QtWidgets.QPushButton('按钮2')

grid = QtWidgets.QGridLayout(centralWidget)
grid.addWidget(button)


class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('窗口1')

centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)

button = QtWidgets.QPushButton('打开新窗口')
button.clicked.connect(self.open_new_window)

grid = QtWidgets.QGridLayout(centralWidget)
grid.addWidget(button)

def open_new_window(self):
# 实例化另外一个窗口
self.window2 = Window2()
# 显示新窗口
self.window2.show()
# 关闭自己
self.close()


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

8. Popup modal dialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from PySide2 import QtWidgets
import sys


class MyDialog(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('模式对话框')

self.resize(500, 400)
self.textEdit = QtWidgets.QPlainTextEdit(self)
self.textEdit.setPlaceholderText("请输入薪资表")
self.textEdit.move(10, 25)
self.textEdit.resize(300, 350)

self.button = QtWidgets.QPushButton('统计', self)
self.button.move(380, 80)


class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('主窗口')

centralWidget = QtWidgets.QWidget()
self.setCentralWidget(centralWidget)

button = QtWidgets.QPushButton('打开模式对话框')
button.clicked.connect(self.open_new_window)

grid = QtWidgets.QGridLayout(centralWidget)
grid.addWidget(button)

def open_new_window(self):
# 实例化一个对话框类
self.dlg = MyDialog()
# 显示对话框,代码阻塞在这里,
# 等待对话框关闭后,才能继续往后执行
self.dlg.exec_()


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

PySide2 Qt Designer
https://www.hardyhu.cn/2022/08/05/PySide2-Qt-Designer/
Author
John Doe
Posted on
August 5, 2022
Licensed under