如果 PyQt window 打开,则拖动 matplotlib 绘图时出现致命 Python 错误

Fatal Python Error when dragging matplotlib plot if PyQt window is opened

我 运行 遇到了以下问题。 (Python 3.8) 我用 2 个 GUI 按钮制作了 matplotlib 散点图。 在单独的模块中,我制作了 PyQt5 GUI 应用程序,我希望它能够用作 GUI 界面,让用户使用小部件以舒适的方式修改生成的绘图,以设置各种绘图参数。 我已经将 PyQt5 GUI window 打开绑定到绘图上的一个按钮。 当 PyQT GUI window 保持打开状态时,我无法与绘图 window 交互。 更重要的是,如果我尝试拖动绘图 window,程序会崩溃:

Fatal Python error: PyEval_RestoreThread: NULL tstate

Python runtime state: initialized

Current thread 0x00000674 (most recent call first)

按照 Python 库的一些路径...

下面是模拟我的程序并产生相同问题的示例代码:

情节:my_plot.py

from matplotlib.widgets import Button
import random
import my_qt_app

def MyPlot():
    data_x = [random.randint(-100, 100) for n in range(20)]
    data_y = [random.randint(-100, 100) for n in range(20)]

    plt.scatter(data_x, data_y)
    #some irrelevant bs data to put on plot
    ax_button_1 = plt.axes([0.68, 0.01, 0.12, 0.05])
    ax_button_2 = plt.axes([0.81, 0.01, 0.1, 0.05])
    button_1 = Button(ax_button_1, "TXT")
    button_2 = Button(ax_button_2, "Qt_GUI")
    button_1.on_clicked(lambda event: SaySomething(event))
    button_2.on_clicked(lambda event: my_qt_app.OpenPyQtGUI(event))

    plt.show()

def SaySomething(event):
    print(["SOMTHING" for n in range(10)])

MyPlot()

...和 ​​PyQt5 gui 应用程序:my_qt_app.py

#common
from PyQt5.QtWidgets import QApplication, QWidget
#layouts
from PyQt5.QtWidgets import QGridLayout, QHBoxLayout, QGroupBox
#widgets
from PyQt5.QtWidgets import QLabel, QPushButton, QComboBox, QCheckBox

class PlotEditor(QWidget):
    def __init__(self):
        super().__init__()

        self.initGUI()

    def initGUI(self):
        #example gui elements
        self.main_grid = QGridLayout()
        self.setLayout(self.main_grid)
        self.setWindowTitle("test window")
        self.groupbox_1 = QGroupBox()
        self.groupbox_1.setCheckable(True)
        group_1_layout = QHBoxLayout()
        self.groupbox_1.setLayout(group_1_layout)

        group_1_layout.addWidget(QLabel("#placeholder1"))
        group_1_layout.addWidget(QPushButton("my_button"))

        self.groupbox_2 = QGroupBox()
        self.groupbox_2.setCheckable(True)
        group_2_layout = QHBoxLayout()
        self.groupbox_2.setLayout(group_2_layout)
        combo_1 = QComboBox()
        group_2_layout.addWidget(QComboBox(combo_1))
        group_2_layout.addWidget(QCheckBox("toggle"))

        self.main_grid.addWidget(self.groupbox_1, 0, 0)
        self.main_grid.addWidget(self.groupbox_2, 1, 0)

# called in my_plot.py
def OpenPyQtGUI(event):
    app = QApplication(sys.argv)
    gui_object = PlotEditor()
    gui_object.show()
    app.exec()

将 matplotlib 后端设置为:matplotlib.use('Qt5Agg') 导致 The event loop is already running 错误。

非常感谢suggestions/help。

创建 Qt 应用程序并启动其循环会导致其任何小部件 modal 成为绘图。

您可以使用“Qt5Agg”引擎,在函数中创建小部件,然后在单击按钮时调用 show()

matplotlib.use('Qt5Agg')

# ...

def MyPlot():
    # ...
    button_1.on_clicked(lambda event: SaySomething(event))

    gui_object = PlotEditor()
    button_2.on_clicked(lambda *args: gui_object.show())

    plt.show()

如果您需要使用小部件来更改绘图的各个方面,那么我建议您添加一个带有基本确定按钮的 QDialogButtonBox,并将其 accepted 信号连接到一个函数更新情节。

def MyPlot():
    # ...
    button_1.on_clicked(lambda event: SaySomething(event))

    gui_object = PlotEditor()
    button_2.on_clicked(lambda *args: gui_object.show())
    gui_object.accepted.connect(lambda: doSomething(gui_object))

    plt.show()

def doSomething(gui_object):
    print(gui_object.check_1.isChecked())


class PlotEditor(QDialog):
    # ...
    def initGUI(self):
        #example gui elements
        self.main_grid = QGridLayout(self)
        self.setWindowTitle("test window")
        self.groupbox_1 = QGroupBox()
        self.groupbox_1.setCheckable(True)
        group_1_layout = QHBoxLayout(self.groupbox_1)

        group_1_layout.addWidget(QLabel("#placeholder1"))
        group_1_layout.addWidget(QPushButton("my_button"))

        self.groupbox_2 = QGroupBox()
        self.groupbox_2.setCheckable(True)
        group_2_layout = QHBoxLayout()
        self.groupbox_2.setLayout(group_2_layout)
        self.combo_1 = QComboBox() # <- instance attribute
        group_2_layout.addWidget(self.combo_1)
        self.check_1 = QCheckBox("toggle") # <- instance attribute
        group_2_layout.addWidget(self.check_1)

        self.main_grid.addWidget(self.groupbox_1, 0, 0)
        self.main_grid.addWidget(self.groupbox_2, 1, 0)

        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
        self.main_grid.addWidget(buttonBox)
        self.accepted = buttonBox.accepted
        self.accepted.connect(self.hide)

注意:创建一个组合作为另一个组合的子组合既错误又毫无意义(参见 group_2_layout.addWidget(QComboBox(combo_1)))。
此外,如果您需要这些小部件来获取配置你必须像我上面那样为它们创建实例属性。