首先,我是Pyside的初学者,我欢迎建设性的反馈。我也感谢任何看过此内容并尝试运行我的代码的人。预先谢谢你!
我还应该提到我在代码中使用https://github.com/mottosso/Qt.py作为Qt导入。
我正在制作一个GUI以执行一些硬件单元测试。该GUI带有嵌入式FigureCanvas图,用于在用户单击按钮时显示从硬件捕获的数据。我将unittest.TestCase子类化以插入PlotCanvas(具有一些简单方法的自定义FigureCanvas类)。然后,我创建一个包含多个页面的向导,每个页面都有一个独立的图。
现在,如果我不对unittest.TestCase进行子类化,只需创建一个静态plot_canvas,这将起作用。在我的主要代码块中看到DEBUG_STATE = 1。
# Uses statically defined plot canvas, no subclassing
class TestPage1(unittest.TestCase):
PLOT_COLS = 1
PLOT_SIZE = (12, 6)
plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)
def test_01(self):
x = np.linspace(0,10,50)
self.plot_canvas.plot(x, 2*x)
但是,如果我使用StaticPlottingTestCase,代码几乎相同,则无法插入多个页面。画布没有显示在首页上。我认为这与QWizard有关,但我不确定。参见DEBUG_STATE = 3。
class StaticPlottingTestCase(unittest.TestCase):
# Static definition
PLOT_COLS = 1
PLOT_SIZE = (12, 6)
plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
为解决此问题,我制作了另一个类InstancePlottingTestCase,该类已将plot_canvas定义为类变量。现在,情节出现在两个页面中,但是情节不起作用。参见DEBUG_STATE = 4。
class InstancePlottingTestCase(unittest.TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Instance definition
self.plot_canvas = PlotCanvas()
我已经尝试了数小时的研究,尽管代码不是那么干净,但到目前为止,我几乎只使用我的原始方法。我已经广泛研究了Pyside文档。
这是我的最小工作示例。可以将我的主块中的DEBUG_STATE变量设置为不同的值,以模拟我遇到的问题。
DEBUG_STATE = 1:我的基准线方法。这可以按预期工作。
DEBUG_STATE = 2:StaticPlottingTestCase的单个页面。这行得通。
DEBUG_STATE = 3:两页的StaticPlottingTestCase。第一页没有图。第一页上的绘图似乎会影响第二页。
DEBUG_STATE = 4:两页的InstancePlottingTestCase。显示了情节,但无法绘制。
更新:我发现,如果我添加了两个页面,它们的名称相同(DEBUG_STATE = 6),则结果与DEBUG_STATE = 3是相同的。
完整代码:
import io
import sys
import unittest
from unittest.runner import TextTestResult
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_qt5agg import \
FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from Qt import QtCore, QtGui, QtWidgets
from Qt.QtCore import QCoreApplication
from Qt.QtWidgets import (QFormLayout, QPushButton, QVBoxLayout, QWizard,
QWizardPage)
def discover_tests(test_case):
"""Compile a list of tests for the given test case.
Parameters
----------
test_case : TestCase
Returns
-------
names : list
Test names
"""
return [a for a in dir(test_case) if a.startswith('test_')]
class PlotCanvas(FigureCanvas):
def __init__(self, figsize=(8,6), *args, **kwargs):
# Initialize the figure first
self.fig = Figure(figsize=figsize)
self.fig.set_facecolor('white')
# Create the canvas with the created figure
super(PlotCanvas, self).__init__(self.fig)
# Add axes to the figure
self.fig.add_subplot(111)
self.ax = self.fig.get_axes()[0]
def plot(self, x, y, *args, **kwargs):
self.ax.clear()
self.ax.plot(x, y)
self.draw()
class StaticPlottingTestCase(unittest.TestCase):
# Static definition
PLOT_COLS = 1
PLOT_SIZE = (12, 6)
plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class InstancePlottingTestCase(unittest.TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Instance definition
self.plot_canvas = PlotCanvas()
class TestPage(QWizardPage):
def __init__(self, unittest_class, static_canvas=True, *args, **kwargs):
super(TestPage, self).__init__(*args, **kwargs)
# Assign the unit test class
self.unittest_class = unittest_class
# Define overall layout as an hbox
top_hbox = QtWidgets.QHBoxLayout()
# Populate left side with unittest buttons
unittest_vbox = QVBoxLayout()
formLayout = QFormLayout()
self.buttons = {}
unittest_names = discover_tests(unittest_class)
for name in unittest_names:
button = QPushButton('Push to Start', self)
button.setCheckable(True)
button.unittest_name = name
button.setMinimumWidth(100)
button.clicked.connect(self.onButton)
self.buttons[name] = button
formLayout.addRow(name + ':', button)
unittest_vbox.addLayout(formLayout)
# Central plot
plot_vbox = QVBoxLayout()
# Static plot
if static_canvas:
self.plot_canvas = self.unittest_class.plot_canvas
else:
self.plot_canvas = self.unittest_class().plot_canvas
plot_vbox.addWidget(self.plot_canvas)
# Set overall layout
top_hbox.addLayout(unittest_vbox)
top_hbox.addLayout(plot_vbox)
self.setLayout(top_hbox)
def onButton(self):
""" Create a TestSuite with just the clicked test.
"""
name = self.sender().unittest_name
self.runTests({name : self.buttons[name]})
# print(self.plot_canvas.figure.)
def runTests(self, buttons):
""" Run the unit tests corresponding to the dict buttons.
Buttons are used instead of names because the isChecked() value provides
pass/fail information about the test, whereas a name is just...a name.
"""
suite = unittest.TestSuite()
stream = io.StringIO()
for name, button in buttons.items():
suite.addTest(self.unittest_class(name))
runner = unittest.TextTestRunner(
stream=stream,
verbosity=2,
failfast=True,
resultclass=TextTestResult,
)
runner.run(suite)
if __name__ == '__main__':
# Uses statically defined plot canvas, no subclassing
class TestPage1(unittest.TestCase):
PLOT_COLS = 1
PLOT_SIZE = (12, 6)
plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)
def test_01(self):
x = np.linspace(0,10,50)
self.plot_canvas.plot(x, 2*x)
class TestPage1b(unittest.TestCase):
PLOT_COLS = 1
PLOT_SIZE = (12, 6)
plot_canvas = PlotCanvas(size=PLOT_SIZE, cols=PLOT_COLS)
def test_01(self):
x = np.linspace(0,10,50)
self.plot_canvas.plot(x, 2*x)
# Uses statically defined canvas in subclass. Should act the same way as TestPage1.
class TestPage2(StaticPlottingTestCase):
def test_01(self):
x = np.linspace(0,10,50)
self.plot_canvas.plot(x, 3*x)
class TestPage2b(StaticPlottingTestCase):
def test_01(self):
x = np.linspace(0,10,50)
self.plot_canvas.plot(x, 3*x)
# Uses instance defined canvas. This doesn't plot anything.
class TestPage3(InstancePlottingTestCase):
def test_01(self):
x = np.linspace(0,10,50)
y = x ^ 2
self.plot_canvas.plot(x, y)
class TestPage3b(InstancePlottingTestCase):
def test_01(self):
x = np.linspace(0,10,50)
y = x ^ 2
self.plot_canvas.plot(x, y)
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication(sys.argv)
print('Conjuring test wizard...')
wizard = QWizard()
wizard.setWizardStyle(QtWidgets.QWizard.ModernStyle)
# Debugging
DEBUG_STATE = 6
# Adding TestPage1 works as expected. The plots are independent.
if DEBUG_STATE == 1:
wizard.addPage(TestPage(TestPage1, static_canvas=True))
wizard.addPage(TestPage(TestPage1b, static_canvas=True))
# A single TestPage2 works...
elif DEBUG_STATE == 2:
wizard.addPage(TestPage(TestPage2, static_canvas=True))
# ...but adding two of them does not work...
# The canvas doesn't show up on the first page.
# I think this has something to do with the widgets layout.
elif DEBUG_STATE == 3:
wizard.addPage(TestPage(TestPage2, static_canvas=True))
wizard.addPage(TestPage(TestPage2b, static_canvas=True))
# Making self.canvas a class variable generates the plot, but now plotting doesn't do anything
elif DEBUG_STATE == 4:
wizard.addPage(TestPage(TestPage3, static_canvas=False))
wizard.addPage(TestPage(TestPage3b, static_canvas=False))
# Plotting works on the statically defined canvas, but not on the class-defined one.
elif DEBUG_STATE == 5:
wizard.addPage(TestPage(TestPage2, static_canvas=True))
wizard.addPage(TestPage(TestPage3, static_canvas=False))
# A clue! Adding a page with the same name mimics DEBUG_STATE == 3...
elif DEBUG_STATE == 6:
wizard.addPage(TestPage(TestPage1, static_canvas=True))
wizard.addPage(TestPage(TestPage1, static_canvas=True))
wizard.setFixedSize(1200, 800)
wizard.show()
app.exec_()