之前我们成功运行了一个空白的窗体,我们需要继续添加内容到这个窗口内,才能让程序和我们交互起来,才能得到一个图形用户界面(Graphic User Interface,GUI)
标签(QLabel)
标签通常用于显示一些只读的字符,当然也可以用来显示图片!
其实这些话说的都有些绝对,在 Qt
的世界了,控件可以变成任何样子,标签也可以改造成一个按钮,如果 Qt
中的控件只能按照固有的方式使用的话,那失去了很多灵活性。
首先我们建立一个窗体,设置好我们的窗体标题,然后添加标签到其中。
if __name__ == '__main__':
app = QApplication()
widget = QWidget()
widget.setWindowTitle("SerialTool")
lab_port = QLabel("串口", widget)
lab_baudrate = QLabel("波特率", widget)
widget.show()
app.exec()
可以看到,在主窗体中,两个标签都出现了,但是他们的位置好像有点问题,因为他们都传入了父对象,就是主窗体。
因此对他们来说,默认会设置自己的位置为父对象内部的左上角,也就是 Qt 中坐标系统的 (0, 0) 位置。
在 Qt
中,从左往右,x
逐渐增加,从上往下,y
逐渐增大
修改位置我们可以直接使用设置位置的函数,但是手动设置不利于维护,如果需要添加一个控件,或者屏幕的分辨率不一,修改起来极其麻烦。
因此,为了方便管理,Qt
提供了布局管理器
布局 (QLayout)
QLayout
是个基类,我们一般使用 QVBoxLayout
QHboxLayout
QGridLayout
来做布局管理,它们分别用于纵向布局、横向布局和网格布局。
我们先使用横向布局,修复之前的标签问题:
from PySide6.QtWidgets import (QApplication, QWidget, QLabel, QHBoxLayout) # 修改
if __name__ == '__main__':
app = QApplication()
widget = QWidget()
widget.setWindowTitle("SerialTool")
layout = QHBoxLayout(widget) # 修改
lab_port = QLabel("串口")
lab_baudrate = QLabel("波特率")
layout.addWidget(lab_port) # 修改
layout.addWidget(lab_baudrate) # 修改
widget.show()
app.exec()
我们首先创建了一个横向布局管理器,注意我们直接将它的父类设置为主窗体,这代表主窗体的布局交给它管理。
我们也可以不传入父类,然后通过父类的 setLayout
来设置某个布局管理器作为窗体的布局管理器,注意一个窗体或控件只能设置一个 Layout
。
代码中还有两处修改,我们在创建标签的时候没有传入父类窗体,但是窗体中也显示了标签。
这是因为我们将标签控件添加到了布局管理器,而布局管理器又被设置为主窗体的布局管理器,即主窗体 widget
包含了 layout
,layout
又包含了两个 QLabel
。
因此,主窗体可以显示这两个 QLabel
,并且主窗体的大小被设置为两个 QLabel
的大小,这就是 Layout
的作用。
如果我们在这个例子中创建 Layout
的时候,不传入父类 widget
为发生什么?上面说的包含链就不存在了,窗体就不会显示任何东西。
下拉框(QComboBox)
现在给串口和波特率添加下拉框用于选择不同的设置:
我们使用 QComboBox
创建两个下拉框,一个用于选择串口,一个用于选择波特率。
由于我们目前还没有拿到真实的串口数据,因此这里用假的端口做示例。
QComboBox
的 addItems
接口可以为控件添加一个列表,这个列表会在下拉时显示出来。
我们按照顺序将控件添加到横向布局管理器中,然后运行这个示例,下拉框和我们设想的一样,出现了。
from PySide6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, QHBoxLayout) # 修改
if __name__ == '__main__':
app = QApplication()
widget = QWidget()
widget.setWindowTitle("SerialTool")
layout = QHBoxLayout(widget)
lab_port = QLabel("串口")
lab_baudrate = QLabel("波特率")
cbox_port = QComboBox() # 修改
cbox_port.addItems(['COM3', 'COM4', 'COM5']) # 修改
cbox_baudrate = QComboBox() # 修改
cbox_baudrate.addItems(['9600', '38400', '115200', '921600']) # 修改
layout.addWidget(lab_port)
layout.addWidget(cbox_port)
layout.addWidget(lab_baudrate) # 修改
layout.addWidget(cbox_baudrate) # 修改
widget.show()
app.exec()
按钮(QPushButton)
按钮是非常常见的交互控件,下面我们添加一个按钮用于连接和断开串口。
本节我们只搭建图形界面,并不建立任何用户使用的逻辑,因此按钮点击并没有作用。
from PySide6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, QPushButton, QHBoxLayout) # 修改
if __name__ == '__main__':
app = QApplication()
widget = QWidget()
widget.setWindowTitle("SerialTool")
layout = QHBoxLayout(widget)
lab_port = QLabel("串口")
lab_baudrate = QLabel("波特率")
cbox_port = QComboBox()
cbox_port.addItems(['COM3', 'COM4', 'COM5'])
cbox_baudrate = QComboBox()
cbox_baudrate.addItems(['9600', '38400', '115200', '921600'])
btn_conn = QPushButton("连接") # 修改
layout.addWidget(lab_port)
layout.addWidget(cbox_port)
layout.addWidget(lab_baudrate)
layout.addWidget(cbox_baudrate)
layout.addWidget(btn_conn) # 修改
widget.show()
app.exec()
文本框(QLineEdit、QTextEdit 和 QPlainTextEdit)
文本框一般用于展示或输入文字,在串口通讯时,我们需要一个接收窗用来显示收到的数据,一个发送窗用于发送数据。
Qt 提供了三种常用的文本框,他们的区别是:
- QLineEdit
- 单行文本输入,一般用于用户名、密码等少量文本交互地方
- QTextEdit
- 用于多行文本,也可以显示HTML格式文本。QTextEdit多用于显示
- QPlainTextEdit
- 与 QTextEdit 类似,但相比于 QTextEdit 少了富文本显示,因此在处理大量字符时,会少一些开销
由于发送时基本用不到多行,因此我们选择 QLineEdit,接收时基本是纯文本,而且可能出现大量滚屏,因此选择 QPlainTextEdit。
实际上没有太多规则,都可以使用,当然可以选择 QTextEdit 作为发送窗,根据需要选择合适的即可。
from PySide6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, QPushButton, QLineEdit, # 修改
QPlainTextEdit, QVBoxLayout, QHBoxLayout) # 修改
if __name__ == '__main__':
app = QApplication()
widget = QWidget()
widget.setWindowTitle("SerialTool")
layout_main = QVBoxLayout(widget) # 修改
layout = QHBoxLayout() # 修改
lab_port = QLabel("串口")
lab_baudrate = QLabel("波特率")
cbox_port = QComboBox()
cbox_port.addItems(['COM3', 'COM4', 'COM5'])
cbox_baudrate = QComboBox()
cbox_baudrate.addItems(['9600', '38400', '115200', '921600'])
btn_conn = QPushButton("连接")
lab_rx = QLabel("接收:") # 修改
text_rx = QPlainTextEdit() # 修改
lab_tx = QLabel("发送:") # 修改
text_tx = QLineEdit() # 修改
btn_clear = QPushButton("清空")
btn_send = QPushButton("发送")
layout.addWidget(lab_port)
layout.addWidget(cbox_port)
layout.addWidget(lab_baudrate)
layout.addWidget(cbox_baudrate)
layout.addWidget(btn_conn)
layout_main.addLayout(layout) # 修改
layout_main.addWidget(lab_rx) # 修改
layout_main.addWidget(text_rx) # 修改
layout_main.addWidget(btn_clear) # 修改
layout_main.addWidget(lab_tx) # 修改
layout_main.addWidget(text_tx) # 修改
layout_main.addWidget(btn_send) # 修改
widget.show()
app.exec()
为发送和接收增加了按键,一个用于发送字符出去,一个用于清空接收窗口。
这里将原来主窗体的横向布局修改为了纵向布局,因为总体上,主窗体内的布局是上下结构的,只是在串口的配置区域,是左右结构。
因此我们给主窗体创建了一个纵向布局,然后将原来的横向布局添加到纵向布局中,然后添加其他控件到纵向布局中,这样就得到了上图所示的界面。
到这里我们基本上有了雏形,界面没有多好看,慢慢优化。
我们目前使用了横向布局、纵向布局和一些必要的控件,发送和接收按钮的显示略微有些别扭。
下一节我们先介绍实际的串口逻辑,界面等功能实现后再来优化。