不是CS专业,从来没做过界面,入职就被分配了写一个上位机的工作。幸亏读master的时候被操练得盲目自信不怕困难,说干就干。边学边写用Matlab写了个很烂的上位机,一旦启动阻塞电脑其他所有动作…… 但是也算能用了,空闲了几天,这时候有了新需求,那么就趁机学一下Qt吧。开始的时候,决定用PyQt 5 + Python 3,因为感觉比C++开发速度快。新需求完成后,就决定继续把旧的功能移植过来了,最后……又做了一个很烂的上位机。“很烂”主要是性能问题,后面再细说。
请多指教。
笔者也不知道Python 2用来开发会怎样,选Python 3一是因为看到Python 2将停止维护还是什么的,二是用Vim的时候不少插件要Python 3支持。笔者是在Windows 7上开发,安装Python 3之后,就可以用pip安装需要的包了,比如说最主要的PyQt 5。中途从Python 3.5换到3.6,因为更新了Vim,结果是并无影响,至少是在这个项目涉及到的范围内没有影响。
不需要安装Qt 5。一开始以为使用PyQt 5要先安装Qt 5,后来在另外一台电脑上搭环境的时候尝试了一下,发现并不需要。开始用的时候是PyQt/Qt 5.9,写这篇文章的时候是5.10了。PyQt 5相对于4,很多类都被移动了,目前来说网上的PyQt 5的教程比4的少很多,虽然可以参考着来开发,但是import的时候就要小心了,要去PyQt 5的网站或Qt官网查看一下。
既然提到了,就稍微说一下,并不是要引战。笔者是学ROS的时候突然想起有这么个编辑器,怀着一颗好奇心就入坑了,花了一个星期入门——“入门”的意思是知道:wq等等。笔者脑子不好使,.h和.c(pp)分开看不行,界面和逻辑分开看也不行,也尝试过学习Emacs之类,无奈再也没有这么多时间了,所以一直用Vim。另外通过配置vimrc也可以直接在Vim中通过快捷键运行Python代码。
Vim用了快3年,配置上基本满足当前需求了,不能说是高端配置。先挖个坑,后面补一篇指南,比如语法检查插件ALE我就搞了很久。
还是先上点入门资料吧。
(1) First Programs in PyQt 5 - 建议先看这个。
(2) 《Matplotlib for Python Developers》的Chapter No. 6 - 第6章讲的是怎么把matplotlib的坐标嵌入到PyQt中去,但例子用的都是PyQt 4,需要读者自行移植。其他matplotlib的内容,笔者都是通过其官方文档及Stack Overflow学习的。
也许主文件为其他命名也可以,但是笔者的主文件就是一锅粥,以main概括就是了;里面是各个面板的实例化,以及最重要的——整个程序的实例化。这里要用到的是:
from PyQt5.QtWidgets import QApplication
PyQt 4的界面有很多种风格可选,但是PyQt 5中删减了。可通过setStyle函数来设置:
- if __name__ == '__main__':
- app = QApplication(sys.argv)
- app.setStyle('fusion')
在看过的教程里,似乎主窗口都要用到QMainWindow:
- from PyQt5.QtWidgets import QMainWindow
- class MainWindow(QMainWindow):
- def __init__(self):
- super().__init__()
- # Your code afterwards
用的时候并没有深究为什么一定要用QMainWindow,后来其他部件全都是用QWidget。写到这里,笔者去查了一下,有兴趣的可以看一下——链接一,链接二,链接三。(“待审核”了,删了,不好意思。)
QWidget用法和以上代码差不多,就改个类名。主要用来做了一些从主窗口分离出去的小面板,例如嵌入了matplotlib的面板。
笔者写的软件只定义了窗口初始大小和位置,以及图标:
- from PyQt5.QtGui import QIcon
- from PyQt5.QtWidgets import QDesktopWidget
- from PyQt5.QtWidgets import QWidget
- class Panel(QWidget):
- def __init__(self):
- super().__init__()
- # Your code afterwards.
- def init_panel(self):
- screen = QDesktopWidget().availableGeometry()
- width = screen.width() * 0.5 # Change the scaler (0.5 here) at your will.
- height = screen.height() * 0.5 # Same as above.
- x = screen.width() * 0.01 # Same as above.
- y = screen.height() * 0.03 # Same as above.
- self.setGeometry(x, y, width, height)
- self.setWindowTitle('Panel')
- self.setWindowIcon(QIcon('icon.png'))
先把用到的组件列一下:
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
- from matplotlib.figure import Figure
- from PyQt5.QtWidgets import QSizePolicy
- from PyQt5.QtWidgets import QCheckBox, \
- QComboBox, \
- QDial, \
- QGroupBox, \
- QLabel, \
- QLineEdit, \
- QProgressBar, \
- QPushBotton
- from PyQt5.QtWidgets import QStyleFactory
(1) 1至3行是用来嵌入matplotlib的,QSizePolicy的用法,笔者也是对着教程依样画葫芦:
- class Canvas(FigureCanvasQTAgg):
- def __init__(self, parent):
- self.figure = Figure()
- FigureCanvasQTAgg.__init__(self, self.figure)
- self.axes = self.figure.add_subplot(1, 1, 1)
- self.setParent(parent)
- FigureCanvasQTAgg.setSizePolicy(self,
- QSizePolicy.Expanding,
- QSizePolicy.Expanding)
- FigureCanvasQTAgg.updateGeometry(self)
(2) 14行的QStyleFactory是与8行的QDial配合用的,笔者用QDial简单封装了一个罗盘,但是QDial的指针样式在PyQt 5里默认是一个圆点,需要用QStyleFactory强制显示为针状(只列出部分代码,其他属性请自行设置):
- class Compass(QDial):
- def __init__(self, parent):
- super().__init__()
- self.setStyle(QStyleFactory.create('windows'))
(3) 第6行QCheckBox:很直接,用于勾选某些选项
(4) 第7行QComboBox:用于创建下拉菜单
- from PyQt5.QtWidgets import QComboBox
- from PyQt5.QtWidgets import QWidget
- class Panel(QWidget):
- def __init__(self):
- super().__init__()
- def create_widget(self):
- self.menu = QComboBox(self)
- def init_widget(self):
- self.menu.addItem('1', 1)
- self.menu.addItem('2', 2)
- self.menu.addItem('3', 3)
addItem的第一个参数为菜单显示的字符串,第二个参数为item里对应的数据,可通过itemData来获取,例如,在上述例子的前提下,以下语句为True:
- self.menu.itemData(0) == 1
- self.menu.itemData(1) == 2
- self.menu.itemData(2) == 3
(5) 第9行QGroupBox:就是在窗口里的一个小区域,视觉上将一些关系紧密组件放在一起,可以为这个group设置一个名字。QGroupBox在面板的QGridLayout上用addWidget从(0,0)开始摆放后,组内要另外定义QGridLayout,从(0,0)开始摆放组件。其他细节请自行参考官方文档。
(6) 第10、11行配合使用:QLabel即创建标签,通常用来标记QLineEdit里显示的数据是什么东西。QLineEdit中,用setText来设置要显示的数据,同时可用setReadOnly防止数据被误改;如果是作为输入框,用getText然后转换数据类型。其他细节请自行参考官方文档。
(7) 第12行QProgressBar:用来当作电池显示条。随便鼓捣自己想要的功能,其中还用到一个网站(uiGradient)提供的渐变色配色方案。
(8) 第13行QPushButton:创建按钮,可用setCheckable(True)来使得按钮可以保持按下(Checked)的状态。
笔者只用了QGridLayout,因为感觉这个布局便于安排组件。虽然冥冥中感觉QVBoxLayout和QHBoxLayout能够让界面在resize的过程中使组件的位置保持得更好(如使用addStrecth等,若有误恳请读者赐教),但由于并不是产品级的软件,所以什么方便快捷就用什么了。QGridLayout中的setRowStrecth和setColumnStretch只是用来对齐不同QBoxGroup中的组件,但效果也并不完美。
原理上,setRowStrecth和setColumnStretch可以用来调整不同组件所占空间的大小,但是笔者直接用addWidget来调整了:
- from PyQt5.QtWidgets import QGridLayout
- from PyQt5.QtWidgets import QLineEdit
- from PyQt5.QtWidgets import QWidget
- class Panel(QWidget):
- def __init__(self):
- super().__init__()
- self.layout = QGridLayout(self)
- # Your code afterwards
- def create_widgets(self):
- self.fat_box = QLineEdit(self)
- self.thin_box = QLineEdit(self)
- def add_widgets(self):
- self.layout.addWidget(self.fat_box, 0, 0, 3, 3)
- self.layout.addWidget(self.thin_box, 3, 0, 1, 1)
联系客服