我正在开发这个 pyside6 项目,其目标是让用户加载一些数据,然后在其上绘制一个多边形作为“门”。该程序稍后将提供门内数据点的分析。我在 MacOS 上遇到了这种有线行为。
(该示例是仅绘制一条线的简化版本)要重新创建此行为,您需要
我尝试过的事情/其他相关信息:
代码相当长,因为它是从一个大项目中减少的。我已经尽力减少了。
import sys
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
import numpy as np
from PySide6 import QtWidgets, QtCore, QtGui
matplotlib.use('QT5Agg')
class mainUi(QtWidgets.QMainWindow):
def __init__(self):
# init and setup UI
super().__init__()
self.resize(800, 800)
self.centralwidget = QtWidgets.QWidget(self)
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget, )
self.loadDataPB = QtWidgets.QPushButton('Load new random data', self)
self.gridLayout.addWidget(self.loadDataPB, 0, 0)
self.addGatePB = QtWidgets.QPushButton('Draw polygon', self)
self.gridLayout.addWidget(self.addGatePB, 0, 1, 1, 1)
self.mpl_canvas = plotCanvas()
self.gridLayout.addWidget(self.mpl_canvas, 1, 0, 1, 2)
self.action1 = QtGui.QAction('action 1')
self.mpl_canvas.addActions([self.action1])
self.mpl_canvas.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.setCentralWidget(self.centralwidget)
# connect necessary signals
self.loadDataPB.clicked.connect(self.draw_figure)
self.addGatePB.clicked.connect(self.handle_draw_polygon)
# saved polygon
self.polygon = None
self.draw_figure()
# the centre handler for updating the figure.
def draw_figure(self):
# this function is used to process info for the canvas to redraw
mock_data = np.array([10**np.random.normal(size=1000), 10**(np.random.normal(size=1000)+1)]).T
self.mpl_canvas.ax.clear()
self.mpl_canvas.ax.plot(mock_data[:, 0], mock_data[:, 1], 'o', label='Test sample')
self.mpl_canvas.ax.set_xscale('log')
self.mpl_canvas.ax.set_yscale('log')
self.mpl_canvas.ax.set_xlim([1e-3, 1e3])
self.mpl_canvas.ax.set_ylim([1e-2, 1e4])
if not self.polygon is None:
self.mpl_canvas.ax.plot([self.polygon[0], 1], [self.polygon[1], 1], marker='s', color='orange')
self.mpl_canvas.draw()
def handle_draw_polygon(self):
self.gateEditor = polygonGateEditor(self.mpl_canvas.ax)
self._disableInputForGate(True)
self.mpl_canvas.setCursor(QtCore.Qt.CrossCursor)
self.gateEditor.draw_confirmed.connect(self.polygon_return)
self.gateEditor.connectInputs()
def polygon_return(self, vert, event_button):
self._disableInputForGate(False)
self.mpl_canvas.unsetCursor()
button_name = 'right' if event_button == 3 else 'left'
# This part of the code causes the bug:
QtWidgets.QMessageBox.warning(self, 'Message box', '{0} button input detected'.format(button_name))
# If we comment out the above code and switch to the following code, bug goes away:
# gateName = 'new gate' + str(np.random.random())
self.polygon = vert
self.mpl_canvas.ax.clear()
self.draw_figure()
def _disableInputForGate(self, disable=True):
self.loadDataPB.setEnabled(not disable)
self.addGatePB.setEnabled(not disable)
class plotCanvas(FigureCanvasQTAgg):
def __init__(self):
self.fig, self.ax = plt.subplots()
self.fig.set_layout_engine("tight")
super().__init__(self.fig)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.draw()
class polygonGateEditor(QtCore.QObject):
draw_confirmed = QtCore.Signal(object, int)
def __init__(self, ax) -> None:
super().__init__()
self.ax = ax
self.canvas = self.ax.figure.canvas
self.background = None
self.pressCid, self.releaseCid = (0 ,0)
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.line = matplotlib.lines.Line2D([], [], marker='s', color='r', animated=True)
self.line.set_data([], [])
self.ax.add_line(self.line)
self.blitDraw()
def addGate_on_press(self, event):
if event.button == 1 or event.button == 3:
vert = np.array([event.xdata, event.ydata])
self.draw_confirmed.emit(vert, event.button)
self.disconnectInputs()
self.blitDraw()
def addGate_on_motion(self, event):
self.line.set_data([event.xdata, 1], [event.ydata, 1])
self.blitDraw()
def connectInputs(self):
self.pressCid = self.canvas.mpl_connect('button_press_event', self.addGate_on_press)
self.moveCid = self.canvas.mpl_connect('motion_notify_event', self.addGate_on_motion)
def disconnectInputs(self):
self.canvas.mpl_disconnect(self.pressCid)
self.canvas.mpl_disconnect(self.moveCid)
def blitDraw(self):
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.line)
self.canvas.blit(self.ax.bbox)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = mainUi()
window.show()
sys.exit(app.exec())
好吧,我在这里回答我自己的问题,以防将来有人跨过这个问题。
解决方法:连接点击事件函数时,使用
button_release_event
而不是 button_press_event
。
def connectInputs(self):
self.pressCid = self.canvas.mpl_connect('button_release_event', self.addGate_on_press)
self.moveCid = self.canvas.mpl_connect('motion_notify_event', self.addGate_on_motion)
这不是一个完美的解决方案,因为它稍微改变了行为,但它确实有效。这样做的原因如下:
当用户右键单击或按住 Ctrl 键单击画布时,三个事件将按以下特定顺序传递到小部件:
MouseButtonPress
-> ContextMenu
-> MouseButtonRelease
。 (您可以安装事件过滤器来检查)。但是,如果对话框在按下事件之后生成并获得焦点/模式,则画布小部件将仅接收前两个事件。我不确定为什么它实际上可以接收第二个事件,或者没有接收释放事件冻结它。但出于此应用程序的所有目的,在发布时触发逻辑/对话框可以解决问题。
是的,对于我来说,为什么 ctrl-click 不会遇到同样的错误也是一个谜。