MacOS 中右键单击与 Ctrl 单击的不同行为(由 QInputDialog 触发),并且它部分冻结了 pyside6

问题描述 投票:0回答:1

我正在开发这个 pyside6 项目,其目标是让用户加载一些数据,然后在其上绘制一个多边形作为“门”。该程序稍后将提供门内数据点的分析。我在 MacOS 上遇到了这种有线行为。

(该示例是仅绘制一条线的简化版本)要重新创建此行为,您需要

  1. 单击“绘制多边形”按钮,然后将光标移动到绘图区域。
  2. 您可以左键单击右键单击确认该行(完全相同的代码)。将显示一个警告对话框。
  3. 如果您在上一步中使用右键单击,某些 UI 将被冻结,包括“加载新随机数据”按钮。但是左键单击绝对可以。
  4. 最有趣的是,如果您在步骤 2 中使用“ctrl + 单击”,用户界面将按预期运行。

我尝试过的事情/其他相关信息:

  1. 此错误可能是由警告对话框引起的(QDialog 类也导致该错误)。如果我跳过该对话框(代码中的第 77 行左右),错误就会消失。
  2. 当 UI 冻结时,您可以通过在绘图区域中右键调出上下文菜单来解冻它。
  3. 这是 MacOS 特定的错误,因为相同的代码(非简化版本)在 Windows 和 Linux 上按预期工作。我尝试过的几乎(?)所有 Mac 上都会发生这种情况。
  4. 我有一个与pyqt5类似的代码版本,它有相同的错误。

代码相当长,因为它是从一个大项目中减少的。我已经尽力减少了。

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())

macos matplotlib pyqt
1个回答
0
投票

好吧,我在这里回答我自己的问题,以防将来有人跨过这个问题。

解决方法:连接点击事件函数时,使用

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 不会遇到同样的错误也是一个谜。

© www.soinside.com 2019 - 2024. All rights reserved.