如何防止 Qscrollbar 的箭头调用两次相同的函数?
how to prevent the arrow of a Qscrollbar from calling twice the same function?
我制作了一个 pyqt window 以在 matplotlib 图形中显示信号,并使用 QScrollBar
查看信号的不同部分。
我的问题是当我绘制 100 信号时,QScrollBar
的箭头调用了 valueChanged
信号上调用的函数的两倍。
当我只输入 10 个信号 (self.Sigs_dict = np.random.rand(10,105*self.Fs)
) 时,我在打印时间信息的 update_plot
函数中只传递了一次:
first 1572956286.183867
set 0.0
但是如果我将信号数量增加到 100 (self.Sigs_dict = np.random.rand(100,105*self.Fs)
),我会在 update_plot
函数中传递两次。然后我得到:
first 1572956174.959317
set 0.009981632232666016
first 1572956175.6289513
set 0.0
我不明白为什么会这样,也不知道我该如何解决这个问题。
这是我的问题的最小示例:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import time
class Viewer(QMainWindow):
def __init__(self, parent=None):
super(Viewer, self).__init__()
self.parent = parent
#######################################
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainVBOX_param_scene = QVBoxLayout()
self.mascene = plot(self)
self.paramPlotV = QVBoxLayout()
self.horizontalSliders = QScrollBar(Qt.Horizontal)
self.horizontalSliders.valueChanged.connect(self.update_plot)
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(1)
self.horizontalSliders.setPageStep(1)
self.paramPlotV.addWidget(self.horizontalSliders)
self.mainVBOX_param_scene.addWidget(self.mascene)
self.mainVBOX_param_scene.addLayout(self.paramPlotV)
self.centralWidget.setLayout(self.mainVBOX_param_scene)
self.Fs = 1024
self.Sigs_dict = np.random.rand(100,105*self.Fs)
self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
self.update()
def updateslider(self):
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(10))-1)
def update_plot(self):
t = time.time()
print('first',t)
self.mascene.update_set_data()
print('set',time.time() - t)
def update(self):
self.updateslider()
self.mascene.modify_sigs()
self.mascene.update()
class plot(QGraphicsView):
def __init__(self, parent=None):
super(plot, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')#Figure()
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
def modify_sigs(self):
self.Sigs_dict = self.parent.Sigs_dict
self.t = self.parent.t
self.Fs= self.parent.Fs
def update_set_data(self):
ts, te = self.get_ts_te()
t = self.t[ts:te]
for i,line in enumerate(self.Lines):
line.set_data(t, (self.Sigs_dict[i, ts:te]) + i)
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + 10 ))
self.canvas.draw_idle()
def update(self):
self.figure.clear()
plt.figure(self.figure.number)
self.axes = plt.subplot(1, 1, 1)
ts, te = self.get_ts_te()
self.Lines = []
for i in range(self.Sigs_dict.shape[0]):
line, = plt.plot(self.t[ts:te], (self.Sigs_dict[i,ts:te]-np.mean(self.Sigs_dict[i,ts:te]))+i)
self.Lines.append(line)
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100))
self.canvas.draw_idle()
def get_ts_te(self):
win_num = self.parent.horizontalSliders.value()
ts = int(10 * (win_num) * self.Fs)
te = ts + int(10 * self.Fs)
if te > len(self.t):
diff = te - len(self.t)
ts = ts - diff
te = len(self.t)
return ts, te
def main():
app = QApplication(sys.argv)
ex = Viewer(app)
ex.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()
我发现当您尝试将滑块移动超过一个增量时会发生这种情况。一旦值改变 1 个增量,你就会得到一个更新,然后当值再次改变时另一个。因为 100 个信号的绘制时间明显比 10 个慢,GUI 响应更慢,因此滑块倾向于提供多次更新而不是跳转到最终值。
解决此问题的简单方法是将连接的信号更改为
self.horizontalSliders.sliderReleased.connect(self.update_plot)
sliderReleased
信号将确保您在执行更新之前完成移动滑块。
这种方法的缺点是鼠标单击箭头不会被捕获,只能拖动条。如果您想在防止多次更新的同时处理这两种情况,则需要创建状态变量来决定您处于哪种模式。您可以查看 sliderPressed
信号以查看用户是否正在拖动滑块。如果是,请查看 sliderReleased
完成信号。如果不是,请使用 valueChanged
信号。
顺便说一句...QT 的信号和插槽很棒,但是这种类型的信号递归很常见,您经常需要进行额外的检查以防止多次调用昂贵的重绘函数等。
我找到了解决这个问题的方法。
我在 matplotlib 图更新之前添加 self.horizontalSliders.setEnabled(False)
并在更新完成后将其设置为 True 。所以我有:
def update_plot(self):
self.horizontalSliders.setEnabled(False)
t = time.time()
print('first',t, self.horizontalSliders.value())
self.mascene.update_set_data()
print('set',time.time() - t)
self.horizontalSliders.setEnabled(True)
我不知道这样做是否正确,但它有效
我制作了一个 pyqt window 以在 matplotlib 图形中显示信号,并使用 QScrollBar
查看信号的不同部分。
我的问题是当我绘制 100 信号时,QScrollBar
的箭头调用了 valueChanged
信号上调用的函数的两倍。
当我只输入 10 个信号 (self.Sigs_dict = np.random.rand(10,105*self.Fs)
) 时,我在打印时间信息的 update_plot
函数中只传递了一次:
first 1572956286.183867
set 0.0
但是如果我将信号数量增加到 100 (self.Sigs_dict = np.random.rand(100,105*self.Fs)
),我会在 update_plot
函数中传递两次。然后我得到:
first 1572956174.959317
set 0.009981632232666016
first 1572956175.6289513
set 0.0
我不明白为什么会这样,也不知道我该如何解决这个问题。
这是我的问题的最小示例:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import time
class Viewer(QMainWindow):
def __init__(self, parent=None):
super(Viewer, self).__init__()
self.parent = parent
#######################################
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainVBOX_param_scene = QVBoxLayout()
self.mascene = plot(self)
self.paramPlotV = QVBoxLayout()
self.horizontalSliders = QScrollBar(Qt.Horizontal)
self.horizontalSliders.valueChanged.connect(self.update_plot)
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(1)
self.horizontalSliders.setPageStep(1)
self.paramPlotV.addWidget(self.horizontalSliders)
self.mainVBOX_param_scene.addWidget(self.mascene)
self.mainVBOX_param_scene.addLayout(self.paramPlotV)
self.centralWidget.setLayout(self.mainVBOX_param_scene)
self.Fs = 1024
self.Sigs_dict = np.random.rand(100,105*self.Fs)
self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
self.update()
def updateslider(self):
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(10))-1)
def update_plot(self):
t = time.time()
print('first',t)
self.mascene.update_set_data()
print('set',time.time() - t)
def update(self):
self.updateslider()
self.mascene.modify_sigs()
self.mascene.update()
class plot(QGraphicsView):
def __init__(self, parent=None):
super(plot, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')#Figure()
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
def modify_sigs(self):
self.Sigs_dict = self.parent.Sigs_dict
self.t = self.parent.t
self.Fs= self.parent.Fs
def update_set_data(self):
ts, te = self.get_ts_te()
t = self.t[ts:te]
for i,line in enumerate(self.Lines):
line.set_data(t, (self.Sigs_dict[i, ts:te]) + i)
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + 10 ))
self.canvas.draw_idle()
def update(self):
self.figure.clear()
plt.figure(self.figure.number)
self.axes = plt.subplot(1, 1, 1)
ts, te = self.get_ts_te()
self.Lines = []
for i in range(self.Sigs_dict.shape[0]):
line, = plt.plot(self.t[ts:te], (self.Sigs_dict[i,ts:te]-np.mean(self.Sigs_dict[i,ts:te]))+i)
self.Lines.append(line)
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100))
self.canvas.draw_idle()
def get_ts_te(self):
win_num = self.parent.horizontalSliders.value()
ts = int(10 * (win_num) * self.Fs)
te = ts + int(10 * self.Fs)
if te > len(self.t):
diff = te - len(self.t)
ts = ts - diff
te = len(self.t)
return ts, te
def main():
app = QApplication(sys.argv)
ex = Viewer(app)
ex.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()
我发现当您尝试将滑块移动超过一个增量时会发生这种情况。一旦值改变 1 个增量,你就会得到一个更新,然后当值再次改变时另一个。因为 100 个信号的绘制时间明显比 10 个慢,GUI 响应更慢,因此滑块倾向于提供多次更新而不是跳转到最终值。
解决此问题的简单方法是将连接的信号更改为
self.horizontalSliders.sliderReleased.connect(self.update_plot)
sliderReleased
信号将确保您在执行更新之前完成移动滑块。
这种方法的缺点是鼠标单击箭头不会被捕获,只能拖动条。如果您想在防止多次更新的同时处理这两种情况,则需要创建状态变量来决定您处于哪种模式。您可以查看 sliderPressed
信号以查看用户是否正在拖动滑块。如果是,请查看 sliderReleased
完成信号。如果不是,请使用 valueChanged
信号。
顺便说一句...QT 的信号和插槽很棒,但是这种类型的信号递归很常见,您经常需要进行额外的检查以防止多次调用昂贵的重绘函数等。
我找到了解决这个问题的方法。
我在 matplotlib 图更新之前添加 self.horizontalSliders.setEnabled(False)
并在更新完成后将其设置为 True 。所以我有:
def update_plot(self):
self.horizontalSliders.setEnabled(False)
t = time.time()
print('first',t, self.horizontalSliders.value())
self.mascene.update_set_data()
print('set',time.time() - t)
self.horizontalSliders.setEnabled(True)
我不知道这样做是否正确,但它有效