PyQt5 的文档相对较少,在自己的实际开发使用中,遇到了些许的坑。网上也没有大把的例子供我参考,还有好多例子停留在Qt4这个版本。所以将调bug的过程记录于此,均为自己探索的较为良好的解决方案。包括:

  • qtpandas的版本问题
  • 数据库使用
  • pyqtgraph绘图
  • 提升窗口(matplotlib)
  • Html的动态绘图
  • 同样,代码太长,不能全部放上来,只放了核心代码,稍微有GUI开发基础的人都能看懂。或者,你可以按照目录观看自己想要的部分。或者,去给出的github链接内拿代码。

    代码链接

    https://github.com/muyuuuu/PyQt-learn/tree/master/GUI/Basic-train

    布局相关

    还是使用 Qt designer 进行布局管理较为方便,写代码一行一行的调试实在是费劲。而且,可以在布局管理器中进行嵌套布局,如先对几个小控件进行水平布局,在对另外的空间垂直布局,最后整体网格布局,这样会较为便捷。效果如下所示:

    verticalLayout_5 下面有多个布局。此外, Qt designer 里面设置的布局不一定为软件最终的布局,还是要实际运行一下,看看到底软件长啥样。如,在designer里面每个控件都很大,实际运行却可能很小,不过大体相同。

    代码结构

    QWidget 是所有类的基类,也包括 QMainWindow ,而在设置窗口风格时,只能在 QWidget 里面使用 self.setStyleSheet(self.style) , 在 QMainWindow 里面则无效(这里的无效是指:切换主题后会有一些设置无法更改,导致界面很丑)。

    但是,一个完整的窗口程序(包括任务栏,菜单栏,状态栏等)是离不开 QMainWindow 的,所以我们可以在 QMainWindow 设置中心区域为 QWidget ,然后在 QWidget 里面添加控件即可,即将控件的实现和布局委托给 QWidget 。

    所以,我们需要两个 ui 文件。与控件相关的东西,如添加控件,修改控件可以在 QWidget 里面完成,而有关窗口的设置,如菜单栏,设置透明度,居中显示等可以在 QMainWindow 里面设置,最后将 QWidget 添加即可。

    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
    from mainwidget import Ui_Form #widget的ui
    from mainwindow import Ui_MainWindow #mainwindow的ui

    # 继承QWidget
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)

    self.setLayout(self.gridLayout)

    # 退出窗口
    self.quit_btn.clicked.connect(self.quit_act)


    def quit_act(self):
    # sender 是发送信号的对象
    sender = self.sender()
    print(sender.text() + '键被按下')
    qApp = QApplication.instance()
    qApp.quit()

    # 继承QMainWindow
    class MyMainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self, parent = None):
    super(MyMainWindow, self).__init__(parent)
    self.setupUi(self)

    # 创建实例,并添加
    q = MyMainWidget()
    self.setCentralWidget(q)

    # 设置窗口透明
    self.setWindowOpacity(0.9)

    # self.resize(1000, 700)

    # 默认的状态栏
    # 可以设置其他按钮点击 参考多行文本显示 然而不行
    self.status = self.statusBar()
    self.status.showMessage("你在主页面~")

    # 标题栏
    self.setWindowTitle("建模协会录入信息")

    # 窗口居中
    self.center()

    def center(self):
    '''
    获取桌面长宽
    获取窗口长宽
    移动
    '''
    screen = QDesktopWidget().screenGeometry()
    size = self.geometry()
    self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)

    if __name__ == "__main__":
    # 在shell中执行
    app = QApplication(sys.argv)
    mywin = MyMainWindow()
    mywin.show()
    # 开始主循环,直到退出
    sys.exit(app.exec())

    qtpandas的使用

    pandas 是比较好好用的数据分析的库,而与 PyQt5 的结合,最方便的方法是使用 qtpandas 。这个库会将 pandas 的数据显示在 QTableWidget 上,自动实现 QTableWidget 的各种功能,如增加,排序,保存,删除等。

    但是, pip install qtpandas 暂时的版本只支持 PyQt4 ,如果想支持 PyQt5 ,需要去github下载最新版。下载好后进入目录,命令行内 python setup.py install 即可。

    而且 qtpandas 依赖于 pandas 0.23 ,需要提前安装 pandas 0.23 。如果直接 pip install pandas 是不可以的,因为直接pip会到最新版0.25(写这篇博客的时候), qtpandas 不能使用,会直接报错:

    1
    No module named 'pandas.tslib

    因此我创建了虚拟环境,在虚拟环境内直接 pip install pandas==0.23 即可,但是还不能放心使用。

    通过这几天的使用发现, PyQt5 的依赖库做的都不是很好。

    有些库 pip 后可以放心使用;
    有些库 pip 后关机开机电脑才能生效,比如 pyqtgraph ;
    有些库 pip 后关机开机还是用不了,比如 qtpandas 。

    于是,我在github上下载了 qtpandas ,将文件夹放在代码的同级目录下,便可以引用进来,然后使用。期待开发者做后后面的完善吧。

    https://github.com/draperjames/qtpandas

    库的安装很费劲,但是,真的好用,代码与截图如下:

    文件结构:(qtpandas的文件夹在这,调用里面的代码,库的本质还是代码啊)

    执行效果:

    代码如下:

    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
    from qtpandas.views.DataTableView import DataTableWidget
    from qtpandas.models.DataFrameModel import DataFrameModel
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)

    self.setLayout(self.gridLayout)

    # 退出窗口
    self.quit_btn.clicked.connect(self.quit_act)

    # qtpandas
    model = DataFrameModel()
    # 空模型那个用于存储和处理数据
    # print(type(self.widget_2))
    self.widget_2.setViewModel(model)
    data = {
    'A': [10, 11, 12],
    'B': [12, 11, 10],
    'C': ['a', 'b', 'c']
    }
    self.df = pandas.DataFrame(data)
    self.df['A'] = self.df['A'].astype(np.int8) # 委托,规定某一列的类型
    model.setDataFrame(self.df)

    # 保存数据
    self.quit_btn_7.clicked.connect(self.save_data)

    def save_data(self):
    self.df.to_csv('data.csv')

    def quit_act(self):
    # sender 是发送信号的对象
    sender = self.sender()
    print(sender.text() + '键被按下')
    qApp = QApplication.instance()
    qApp.quit()

    切换主题

    对于我这种不会(不想写)前端的人来说,写 QSS(Qt Style Sheet) 无疑在要我命,不如用他人写好的代替一下,在代码中引入样式即可。这样也能实现界面和逻辑的代码分离。

    别人写好的:
    https://blog.csdn.net/liang19890820/article/details/52384042

    每个 Widget 都可以设置风格,因此建议主要界面使用 QWidget 作为窗口的主要区域用于设背景风格。同样,如上文提到的, QMainWindow 里切换风格效果很不好,不如把所有控件放在 QWidget 里面,切换风格也方便。black.qss还有white.qss都是从上面的CSDN里的博客下载的。

    文件结构如下:

    切换风格的代码如下:

    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
    # 样式的类
    class StyleFile:
    def __init__(self):
    pass
    # @staticmethod
    def readQSS(style):
    with open (style, 'r') as f:
    return f.read()

    # 在QWidget里面切换风格才有效
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)
    self.quit_btn_3.clicked.connect(self.style_change)
    self.quit_btn_4.clicked.connect(self.style_change1)

    def style_change1(self):
    style_file = 'white.qss'
    self.style = StyleFile.readQSS(style_file)
    self.setStyleSheet(self.style)

    def style_change(self):
    style_file = 'black.qss'
    self.style = StyleFile.readQSS(style_file)
    self.setStyleSheet(self.style)

    效果如下:

    黑色主题

    白色主题

    而设置窗口的无边框风格、透明度风格则必须要在 QMainWindow 里面设置,因为这是窗口的特有方法。

    数据库的使用

    数据库的使用还是很常见的,对于开发小型的应用程序,使用 SQLite 即可(Manjaro环境下已经内置),以及可视化的管理软件SQLiteStudio。 PyQt 使用 QSqlDataBase 连接数据库,一个实例表示了一次连接(连接到IP地址,本地数据库均可),连接、操作完毕后记得关闭,否则会占用数据库的资源。

    此外, QSqlTableModel 提供了可读写的数据模型,链接到数据库后可设置查询的表。设置查询的过滤条件,执行查询语句。之后,将数据模型填充到 QTableView 中,可滚动,可编辑(编辑后改变数据库中的存储)。

    放一次完整的代码吧,但是copy下来不能运行,因为没有 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
    #!/bin/bash
    # -*- coding: UTF-8 -*-
    import sys
    import time
    import PyQt5
    # 基本控件都在这里面
    from PyQt5.QtWidgets import (QApplication, QMainWindow, QDesktopWidget, QStyleFactory, QWidget,
    QMessageBox, QTableView)
    from PyQt5.QtGui import QPalette, QColor
    from PyQt5.QtCore import Qt
    # 导入数据库
    from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlTableModel
    from mainwidget import Ui_Form
    from mainwindow import Ui_MainWindow

    # 继承主窗口 Qmainwindow 和 自己画的界面 Ui_MainWindow
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)

    self.setLayout(self.gridLayout)

    # 链接sql与查询
    self.db = QSqlDatabase.addDatabase("QSQLITE")
    self.db.setDatabaseName('table/test.db')
    self.db.open()
    if not self.db.open():
    QMessageBox.critical(None, ("无法打开数据库"), ("SQlite支持"), QMessageBox.Cancel)
    return False

    self.model = QSqlTableModel()

    self.pushButton.clicked.connect(self.insert)
    self.pushButton_2.clicked.connect(self.query)
    self.pushButton_3.clicked.connect(self.insert_oneline)
    self.pushButton_4.clicked.connect(self.del_oneline)
    self.pushButton_5.clicked.connect(self.find_row)

    # 打印行列号
    def find_row(self):
    print(self.view.currentIndex().row() + 1, self.view.currentIndex().column() + 1)

    # 删除一行
    def del_oneline(self):
    self.model.removeRow(self.view.currentIndex().row())
    self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
    self.model.select()
    self.view.setModel(self.model)
    self.view.show()

    # 插入一行
    def insert_oneline(self):
    self.model.insertRows(self.model.rowCount(), 1)
    # print(str(ret))

    def query(self):
    self.model.setTable("people")
    self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
    # self.model.setFilter("id > 1")
    self.model.select()
    self.model.setHeaderData(0, Qt.Horizontal, "ID")
    self.model.setHeaderData(1, Qt.Horizontal, "Name")
    self.model.setHeaderData(2, Qt.Horizontal, "Address")
    self.view.setModel(self.model)
    self.view.show()

    def insert(self):
    query = QSqlQuery()
    # query.exec_("create table people(id int primary key, name varchar(20), address varchar(30))")
    # query.exec_("insert into people values(1, 'one', 'test1')")
    # query.exec_("insert into people values(2, 'two', 'test2')")
    # query.exec_("insert into people values(3, 'three', 'test3')")
    # query.exec_("inse![](pyqt-pit/7.png)rt into people values(4, 'four', 'test4')")
    for i in range (6, 100):
    query.exec_("insert into people values({}, 'test', 'test{}')".format(i, i))

    class MyMainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self, parent = None):
    super(MyMainWindow, self).__init__(parent)
    self.setupUi(self)

    self.q = MyMainWidget()
    self.setCentralWidget(self.q)

    # 设置窗口透明
    self.setWindowOpacity(0.9)

    # 关闭时,默认关闭数据库
    def closeEvent(self, e):
    self.q.db.close()


    if __name__ == "__main__":
    # 在shell中执行
    app = QApplication(sys.argv)
    mywin = MyMainWindow()
    mywin.show()
    # 开始主循环,直到退出
    sys.exit(app.exec())

    pyqtgraph使用

    这个库,简直有毒,总以为我装失败了,没想到关机开机后这个库能用了。

    这个绘图库是纯 python 图形 GUI 库,由于是基于 pyqt 开发的集成绘图模块,所以 PyQtGraph 绘图与底层方式实现绘图功能在速度上的区别不大。这个库的好处是,可以写两行代码,点击 run example 查看所有的绘图效果。

    1
    2
    import pyqtgraph.examples
    pyqtgraph.examples.run()

    绘图效果,看下图,个人感觉还可以,虽然比不上 matlab 的工具箱和 matplotlib 。

    模仿上述的例子进行简单的集成。这里用到了提升窗口的技术,如果不了解,可以先看后文的提升窗口。基类: QWidget ,头文件: pyqtgraph , 名称: GraphicsLayoutWidget ,一个字母都不要错哦。在本代码中,使用了提升窗口的对象名为: pyqtgraph2 , pyqtgraph 。

    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
    import pyqtgraph as pg 
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):

    super(MyMainWidget, self).__init__(parent)
    # 在初始化之前设置pg,因为初始化会写死配置,无法在更改
    pg.setConfigOption('background', '#f0f0f0')
    pg.setConfigOption('foreground', 'd')
    # 使曲线看起来光滑
    pg.setConfigOptions(antialias = True)

    self.setupUi(self)
    self.setLayout(self.gridLayout)

    # 退出窗口
    self.quit_btn.clicked.connect(self.quit_act)

    # 绘图
    self.pushButton.clicked.connect(self.graph_plot)
    self.pushButton_2.clicked.connect(self.graph_plot1)

    # 多曲线绘图
    def graph_plot1(self):
    self.pyqtgraph2.clear()
    plt = self.pyqtgraph2.addPlot(title="test")
    x = np.random.normal(size=20)
    y1 = np.sin(x)
    y2 = 1.1 * np.sin(x + 1)
    bg1 = pg.BarGraphItem(x = x, height = y1, width = 2, brush = 'r')
    bg2 = pg.BarGraphItem(x = x, height = y2, width = 2, brush = 'b')
    plt.addItem(bg1)
    plt.addItem(bg2)
    # 新的一列还是新的一行
    # self.pyqtgraph2.nextColumn()
    self.pyqtgraph2.nextRow()
    plt2 = self.pyqtgraph2.addPlot(title="test1")
    x = np.linspace(1, 20, 20)
    y3 = np.random.normal(size=20)
    plt2.plot(x, y3, pen = pg.mkPen(width = 2, color = 'd'))
    plt2.showGrid(x=True, y=True)

    # 单标题,多曲线绘图
    def graph_plot(self):
    self.pyqtgraph.clear()
    # 两种绘图方式
    # self.pyqtgraph.addPlot(y = np.random.normal(size=100),
    # pen = pg.mkPen(color='b', width=2))
    plt2 = self.pyqtgraph.addPlot(title="multi rules")
    plt2.plot(np.random.normal(size=150),
    pen = pg.mkPen(color='r', width=2))
    plt2.plot(np.random.normal(size=150) + 5,
    pen = pg.mkPen(color='b', width=2))

    Matplotlib与PyQt的结合

    谈到绘图,不得不提一下 Matplotlib ,很出名的python绘图库。如果可以把 Matplotlib 绘制的图形嵌入到 PyQt 中,也就避免了底层绘制图像的繁琐。

    说干就干。(官网也有实例)

    工具库的引入:

    1
    2
    3
    4
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar

    创建空白的绘图界面的类,实现静态绘图和动态绘图两种方法

    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
    # 绘图的空白界面
    class MymplCanvas(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
    self.fig = Figure(figsize=(width, height), dpi=dpi)
    self.axes = self.fig.add_subplot(111) # 多界面绘图
    FigureCanvas.__init__(self, self.fig)
    self.setParent(parent)
    FigureCanvas.setSizePolicy(self,
    QSizePolicy.Expanding, QSizePolicy.Expanding
    )
    FigureCanvas.updateGeometry(self)

    def static_plot(self):
    self.axes.clear()
    self.fig.suptitle("static FIG")
    t = np.linspace(1, 10, 10)
    s = np.sin(np.pi * t)
    self.axes.plot(t, s)
    self.axes.grid(True)
    self.draw()

    # 为何要加参数
    def dynamic_plot(self, *args, **kwargs):
    timer = QTimer(self)
    timer.timeout.connect(self.update_fig)
    timer.start(1000)

    def update_fig(self):
    self.axes.clear()
    self.fig.suptitle("dynamic FIG")
    l = np.random.randint(1, 10, 4)
    self.axes.plot([0, 1, 2, 3], l, 'r')
    self.axes.grid(True)
    self.draw()

    之后,封装绘图类。将绘图类封装到 MatplotlibWidget 类,调用 MatplotlibWidget 类直接实现绘图功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 实现绘图类
    class MatplotlibWidget(QWidget):
    def __init__(self, parent=None):
    super(MatplotlibWidget, self).__init__(parent)

    # 封装绘图类
    self.gridLayout = QGridLayout()
    self.mpl = MymplCanvas(self)
    # 添加工具栏
    self.mpl_tool = NavigationToolbar(self.mpl, self)
    self.setLayout(self.gridLayout)
    self.gridLayout.addWidget(self.mpl)
    self.gridLayout.addWidget(self.mpl_tool)

    def static(self):
    self.mpl.static_plot()

    def dynamic(self):
    self.mpl.dynamic_plot()

    之后在窗口 QWidget 里面调用 MatplotlibWidget 即可。

    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
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)

    self.setLayout(self.gridLayout)

    # 退出窗口
    self.quit_btn.clicked.connect(self.quit_act)

    # 提升后
    self.widget = MatplotlibWidget()
    self.widget.setVisible(False)
    self.widget_2 = MatplotlibWidget()
    self.widget_2.setVisible(False)
    self.gridLayout.addWidget(self.widget)
    self.gridLayout.addWidget(self.widget_2)
    self.pushButton.clicked.connect(self.dynamic)
    self.pushButton_2.clicked.connect(self.static)

    def static(self):
    self.widget.setVisible(True)
    self.widget.mpl.static_plot()

    def dynamic(self):
    self.widget_2.setVisible(True)
    self.widget_2.mpl.dynamic_plot()

    效果如下所示:

    提升窗口

    已经含有的类

    在上文已经提到了一次提升窗口。再说这个之前,确保已经看懂了上面 pyqtgraph 和 matplotlib 的例子,不然这里更加看不懂。之后会在说明自定义和引用已有类的角度说明如何提升窗口。(毕竟我之前也以为这个没多大用)

    基类: QWidget ,头文件: pyqtgraph , 名称: GraphicsLayoutWidget 。

    因为pyqtgraph是已经安装好的库,所以可以设置头文件为 pyqtgraph ,相当与传统的 import 。

    而在Qt Designer内,首先将 Widget 托入界面,右键,提升为。(不好意思这里没办法截图,linux下的截图可真难用,wine QQ的截图也不能全局有效)

    输入:头文件: pyqtgraph , 名称: GraphicsLayoutWidget 。(不要输入错误,一个字母都不要错)

    输入后以此点击添加、提升即可。

    之后 pyuic5 -o mainwidget.py mainwidget.ui 后,会在辅助的 mainwidget.py 文件里发现: from pyqtgraph import GraphicsLayoutWidget 这么一句话,也就是,上面说的传统的import。手工也可写这行代码,为什么大费周折呢?因为提升了窗口,会发现,这个类有了自己的界面,而不在是简单的导入类,之后可以使用这个特殊的界面进行随意的拖动、布局、使用,会比纯代码方便很多。

    相当与在库 pyqtgraph 中引入了一个布局,并且给了这个布局一个界面,这个界面可以实现绘图功能,那么以后在想绘图的时候就可以复制、拖动这个界面,更加轻松的布局和设置大小。此时提升完毕。

    自己写的类

    以上是在有相应的库和方法的前提下设置提升类。如果没有相应的库,如上文的类 MatplotlibWidget ,如何将自己写的类关联到 Widget 里面进行提升,形成容易操作的控件呢?且看下文。

    这个时候需要将自己写的类单独拿出来成为一个文件,在同级目录下引用即可。

    文件结构:

    提升方法

    自行托入 Widget ,右键提升,输入名称和头文件,名称是类名,文件就是同级目录下的文件,最后添加,提升即可。

    相关代码:

    MatplotlibWidget.py

    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
    #!/bin/bash
    # -*- coding: UTF-8 -*-
    import sys
    import numpy as np
    import PyQt5
    # 基本控件都在这里面
    from PyQt5.QtWidgets import (QApplication, QMainWindow, QDesktopWidget, QStyleFactory, QWidget,
    QSizePolicy, QPushButton, QGridLayout)
    from PyQt5.QtGui import QPalette, QColor
    from PyQt5.QtCore import Qt, QTimer
    from mainwidget import Ui_Form
    from mainwindow import Ui_MainWindow

    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
    # 绘图的空白界面
    class MymplCanvas(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
    self.fig = Figure(figsize=(width, height), dpi=dpi)
    self.axes = self.fig.add_subplot(111) # 多界面绘图
    FigureCanvas.__init__(self, self.fig)
    self.setParent(parent)
    FigureCanvas.setSizePolicy(self,
    QSizePolicy.Expanding, QSizePolicy.Expanding
    )
    FigureCanvas.updateGeometry(self)

    def static_plot(self):
    self.axes.clear()
    self.fig.suptitle("static FIG")
    t = np.linspace(1, 10, 10)
    s = np.sin(np.pi * t)
    self.axes.plot(t, s)
    self.axes.grid(True)
    self.draw()

    # 为何要加参数
    def dynamic_plot(self, *args, **kwargs):
    timer = QTimer(self)
    timer.timeout.connect(self.update_fig)
    timer.start(1000)

    def update_fig(self):
    self.axes.clear()
    self.fig.suptitle("dynamic FIG")
    l = np.random.randint(1, 10, 4)
    self.axes.plot([0, 1, 2, 3], l, 'r')
    self.axes.grid(True)
    self.draw()

    # 实现绘图类
    class MatplotlibWidget(QWidget):
    def __init__(self, parent=None):
    super(MatplotlibWidget, self).__init__(parent)

    # 封装绘图类
    self.gridLayout = QGridLayout()
    self.mpl = MymplCanvas(self)
    self.mpl_tool = NavigationToolbar(self.mpl, self)
    self.setLayout(self.gridLayout)
    self.gridLayout.addWidget(self.mpl)
    self.gridLayout.addWidget(self.mpl_tool)

    def static(self):
    self.mpl.static_plot()

    def dynamic(self):
    self.mpl.dynamic_plot()

    引用

    最终呈现的效果和上面matplotlib绘图一样。这里只给出对比部分的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)

    self.setLayout(self.gridLayout)
    # 提升前
    # self.widget = MatplotlibWidget()
    # self.widget.setVisible(False)
    # self.widget_2 = MatplotlibWidget()
    # self.widget_2.setVisible(False)
    # print(type(self.widget_2))

    # 提升后 不用创建实例,直接拖一拖,pyuic5一下即可
    self.gridLayout.addWidget(self.widget)
    self.gridLayout.addWidget(self.widget_2)
    self.pushButton.clicked.connect(self.dynamic)
    self.pushButton_2.clicked.connect(self.static)

    Html的动态绘图

    直接引入画好的图片即可,不过图片的后缀是html,使用 QWebEngineView 加载即可,一个坑是:必须使用绝对路径,相对路径是找不到的。另一个坑是,Qt4 Designer里面没有 QWebEngineView 这个类,因为这个类是新的,Qt4 Designer 里面只有旧的类 QWebView 。别问我为什么不用新的,因为linux下还没有Qt5 designer。

    旧的类不再维护,新的类使用chromium内核,如果不想写代码实现(布局,定位,大小,这里写代码很麻烦的),那么怎么在Qt4 Designer使用新的类呢?
    很简单,使用上文说的提升窗口,设置好基类,头文件,名称就很容易在旧界面中使用新的类了。

    提升窗口为 QWebEngineView , 同样不要写错一个字母。

    Html的绘图技术的强大之处在于炫酷、可交互。而python也不缺少html的绘图库,如 plotly (javascript的绘图库,python调用接口),还有pyecharts,也很炫酷。(pyecharts版本做了更新,之前就版本的代码无法运行,我一年前的时候还用的旧版本,新版本的用法参考github)

    echarts的(我写的时候服务器维修,无法下载图片,使用的pyecharts的):
    https://gallery.echartsjs.com/explore.html#sort=rank~timeframe=all~author=all

    pyecharts的:
    https://github.com/pyecharts/pyecharts/tree/master/example

    我随意挑选了一个,执行后生成本地的html文件(可交互的图形)。

    文件结构:

    效果如下所示:

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 继承主窗口 Qmainwindow 和 自己画的界面 Ui_MainWindow
    class MyMainWidget(QWidget, Ui_Form):

    def __init__(self, parent = None):
    super(MyMainWidget, self).__init__(parent)
    self.setupUi(self)

    self.setLayout(self.gridLayout)

    # html 绘图
    self.quit_btn_2.clicked.connect(self.html_plot)

    def html_plot(self):
    # 必须用绝对路径
    url = "/home/lanling/Srcode/python/tools/GUI/practice/Pyecharts/render.html"
    # web_view 是 QWebEngineView 对象的一个实例
    self.web_view.load(QUrl.fromLocalFile(url))

    结束语

    因为这里给的都是核心代码,没有给全部代码,如果你是在弄不出来,底下留言给下邮箱,我给你代码。

    不过还是建议多折腾,多走两步,多研究,伸手党不好。

    下一步,自定义信号与槽,类与函数进阶,多线程。