新手写给新手的PyQt 5开发心得(上)

0. 废话

不是CS专业,从来没做过界面,入职就被分配了写一个上位机的工作。幸亏读master的时候被操练得盲目自信不怕困难,说干就干。边学边写用Matlab写了个很烂的上位机,一旦启动阻塞电脑其他所有动作…… 但是也算能用了,空闲了几天,这时候有了新需求,那么就趁机学一下Qt吧。开始的时候,决定用PyQt 5 + Python 3,因为感觉比C++开发速度快。新需求完成后,就决定继续把旧的功能移植过来了,最后……又做了一个很烂的上位机。“很烂”主要是性能问题,后面再细说。

请多指教。



1. 开发环境

1.1 Python 3

笔者也不知道Python 2用来开发会怎样,选Python 3一是因为看到Python 2将停止维护还是什么的,二是用Vim的时候不少插件要Python 3支持。笔者是在Windows 7上开发,安装Python 3之后,就可以用pip安装需要的包了,比如说最主要的PyQt 5。中途从Python 3.5换到3.6,因为更新了Vim,结果是并无影响,至少是在这个项目涉及到的范围内没有影响。


1.2 PyQt 5

不需要安装Qt 5。一开始以为使用PyQt 5要先安装Qt 5,后来在另外一台电脑上搭环境的时候尝试了一下,发现并不需要。开始用的时候是PyQt/Qt 5.9,写这篇文章的时候是5.10了。PyQt 5相对于4,很多类都被移动了,目前来说网上的PyQt 5的教程比4的少很多,虽然可以参考着来开发,但是import的时候就要小心了,要去PyQt 5的网站或Qt官网查看一下。


1.3 Vim

既然提到了,就稍微说一下,并不是要引战。笔者是学ROS的时候突然想起有这么个编辑器,怀着一颗好奇心就入坑了,花了一个星期入门——“入门”的意思是知道:wq等等。笔者脑子不好使,.h和.c(pp)分开看不行,界面和逻辑分开看也不行,也尝试过学习Emacs之类,无奈再也没有这么多时间了,所以一直用Vim。另外通过配置vimrc也可以直接在Vim中通过快捷键运行Python代码。

Vim用了快3年,配置上基本满足当前需求了,不能说是高端配置。先挖个坑,后面补一篇指南,比如语法检查插件ALE我就搞了很久。



2. 开始

2.1 入门资料

还是先上点入门资料吧。

(1) First Programs in PyQt 5 - 建议先看这个。

(2) 《Matplotlib for Python Developers》的Chapter No. 6 - 第6章讲的是怎么把matplotlib的坐标嵌入到PyQt中去,但例子用的都是PyQt 4,需要读者自行移植。其他matplotlib的内容,笔者都是通过其官方文档及Stack Overflow学习的。


2.2 main.py

也许主文件为其他命名也可以,但是笔者的主文件就是一锅粥,以main概括就是了;里面是各个面板的实例化,以及最重要的——整个程序的实例化。这里要用到的是:

from PyQt5.QtWidgets import QApplication

PyQt 4的界面有很多种风格可选,但是PyQt 5中删减了。可通过setStyle函数来设置:

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('fusion')


2.3 界面

2.3.1 窗口:QMainWindow, QWidgets

在看过的教程里,似乎主窗口都要用到QMainWindow:

from PyQt5.QtWidgets import QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        # Your code afterwards

用的时候并没有深究为什么一定要用QMainWindow,后来其他部件全都是用QWidget。写到这里,笔者去查了一下,有兴趣的可以看一下——链接一,链接二,链接三。(“待审核”了,删了,不好意思。)

QWidget用法和以上代码差不多,就改个类名。主要用来做了一些从主窗口分离出去的小面板,例如嵌入了matplotlib的面板。

2.3.2 窗口属性

笔者写的软件只定义了窗口初始大小和位置,以及图标:

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'))

2.3.3 创建组件

先把用到的组件列一下:

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)的状态。

2.3.4 组件布局

笔者只用了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)

你可能感兴趣的:(PyQt)