PyQt4 "mpl_connect" 构造无法通过 类

PyQt4 "mpl_connect" construct doesn't work through classes

我是这个 Whosebug 社区的新用户。我正在挣扎 从我的代码中出现一些我无法真正弄清楚的问题开始。 在我的以下代码中,我试图为我的 GUI 定义一个自定义选择器工具。 我已经复制了 "bug" 和我的程序的主要结构)。 我正在尝试将 matplotlib 信号与 "mpl_connect" 构造函数连接起来,但不幸的是 单击、释放和鼠标移动的信号以及相关方法似乎是 canvas class 无效。这是不起作用的代码。

    import sys
    from PyQt4 import QtGui,QtCore
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar

    class Example(QtGui.QMainWindow):
        def __init__(self, parent=None):
            super(Example, self).__init__(parent)
            self.setupUi(self)

        def setupUi(self,parent):
            self.canvas=MyCanvas(self)
            self.toolbar=MyToolBar(self.canvas,self)
            self.addToolBar(QtCore.Qt.BottomToolBarArea,parent.toolbar)

    class MyCanvas(FigureCanvas):
        def __init__(self,parent):
            self.fig=Figure() 
            FigureCanvas.__init__(self, self.fig)
            FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
            self.setParent(parent)

    class MyToolBar(QtGui.QToolBar):
        def __init__(self,canvas,parent):
            super(MyToolBar,self).__init__(parent)
            #
            self.PICK_act=QtGui.QAction("PUSH ME!!",parent,checkable=True)
            self.PICK_act.toggled.connect(lambda: self.pickData(canvas))
            #
            self.addAction(self.PICK_act)
            self.addSeparator()
            ## Creating the matplotlib toolbar
            self.mpl_tool=NavigationToolbar(canvas,parent)
            ## Merge the two toolbar 
            self.addWidget(self.mpl_tool)

        def pickData(self,canvas):
            P=Picker(canvas)     
            if self.PICK_act.isChecked():
                print "CHECKED"
                P._activation(True)
            else:
                print "NOT CHECKED" 
                P._activation(False)

    class Picker(object):
        def __init__(self,canvas):
            self.index=None
            self.is_pressed=None     
            # To define the event of PickData_ACTION
            self.canvas=canvas 
            ###
            self.selPressEvent=None      
            self.selReleaseEvent=None
            self.selMoveEvent=None           

        def _activation(self,condition):
            if condition==True:
                self.selPressEvent=self.canvas.mpl_connect('button_press_event',self.onpress)
                self.selReleaseEvent=self.canvas.mpl_connect('button_release_event',self.onrelease)
                self.selMoveEvent=self.canvas.mpl_connect('motion_notify_event',self.onmotion)
                print "Picker ON"
                return True
            else:
                self.canvas.mpl_disconnect(self.selPressEvent)
                self.canvas.mpl_disconnect(self.selReleaseEvent)
                self.canvas.mpl_disconnect(self.selMoveEvent)
                print "Picker OFF"
                return False

        def onpress(self,event):
            print "..clicked"
            self.x0 = event.xdata
            self.y0 = event.ydata
            self.is_pressed=True

        def onrelease(self,event):
            print "...released"
            self.x1 = event.xdata
            self.y1 = event.ydata
            self.is_pressed=False

        def onmotion(self,event):
            if self.is_pressed==True:
                print "moving"

    # =========================================================================        
    def run():
        App=QtGui.QApplication(sys.argv)
        GUI=Example()
        GUI.show()
        sys.exit(App.exec_())
    #
    run()    

如果我在 "MyCanvas" class 中定义相同的方法,它们就会起作用。 工作代码是这样的:

    import sys
    from PyQt4 import QtGui,QtCore
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar

    class Example(QtGui.QMainWindow):
        def __init__(self, parent=None):
            super(Example, self).__init__(parent)
            self.setupUi(self)

        def setupUi(self,parent):
            self.canvas=MyCanvas(self)
            self.toolbar=MyToolBar(self.canvas,self)
            self.addToolBar(QtCore.Qt.BottomToolBarArea,parent.toolbar)

    class MyCanvas(FigureCanvas):
        def __init__(self,parent):
            self.fig=Figure() 
            FigureCanvas.__init__(self, self.fig)
            FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
            self.setParent(parent)
            ###
            self.selPressEvent=None      
            self.selReleaseEvent=None
            self.selMoveEvent=None          

        def onpress(self,event):
            print "MyCanvas::onpress ---> clicked"

        def onrelease(self,event):
            print "MyCanvas::onrelease ---> release"

        def onmotion(self,event):
            print "MyCanvas::onmotion ---> motion"                

    class MyToolBar(QtGui.QToolBar):
        def __init__(self,canvas,parent):
            super(MyToolBar,self).__init__(parent)
            #
            self.PICK_act=QtGui.QAction("PUSH ME!!",parent,checkable=True)
            self.PICK_act.toggled.connect(lambda: self.pickData(canvas))
            #
            self.addAction(self.PICK_act)
            self.addSeparator()
            ## Creating the matplotlib toolbar
            self.mpl_tool=NavigationToolbar(canvas,parent)
            ## Merge the two toolbar 
            self.addWidget(self.mpl_tool)

        def pickData(self,canvas):
            P=Picker(canvas)     
            if self.PICK_act.isChecked():
                print "CHECKED"
                P._activation(True)
            else:
                print "NOT CHECKED" 
                P._activation(False)

    class Picker(object):
        def __init__(self,canvas):
            self.index=None
            self.is_pressed=None     
            # To define the event of PickData_ACTION
            self.canvas=canvas


        def _activation(self,condition):
            if condition==True:
                self.canvas.selPressEvent=self.canvas.mpl_connect('button_press_event',self.canvas.onpress)
                self.canvas.selReleaseEvent=self.canvas.mpl_connect('button_release_event',self.canvas.onrelease)
                self.canvas.selMoveEvent=self.canvas.mpl_connect('motion_notify_event',self.canvas.onmotion)
                print "Picker ON"
                return True
            else:
                self.canvas.mpl_disconnect(self.canvas.selPressEvent)
                self.canvas.mpl_disconnect(self.canvas.selReleaseEvent)
                self.canvas.mpl_disconnect(self.canvas.selMoveEvent)
                print "Picker OFF"
                return False

        def onpress(self,event):
            print "..clicked"
            self.x0 = event.xdata
            self.y0 = event.ydata
            self.is_pressed=True

        def onrelease(self,event):
            print "...released"
            self.x1 = event.xdata
            self.y1 = event.ydata
            self.is_pressed=False

        def onmotion(self,event):
            if self.is_pressed==True:
                print "moving"

    # =========================================================================        
    def run():
        App=QtGui.QApplication(sys.argv)
        GUI=Example()
        GUI.show()
        sys.exit(App.exec_())
    #
    run()

我需要将 "Picker" 和相关方法与 "MyCanvas" 分开 class 为了清楚起见。但我真的不明白什么是 我的第一个代码出错了:信号似乎已发出但未收到。 STDERR 中没有错误报告。我想我正确地尊重了 classes/methods 的范围。 任何帮助,将不胜感激。非常感谢。

编辑解决方案: 对于可能涉及的人,我解决了初始化选择器的问题 "MyToolbar" class:

    self.Picker=Picker(canvas)

    def pickData(self,canvas):    
        if self.PICK_act.isChecked():
            print "CHECKED"
            self.Picker._activation(True)
        else:
            print "NOT CHECKED" 
            self.Picker._activation(False)

问题是 mpl 回调注册表只持有对它所传递的回调的弱引用(逻辑是,如果你不小心持有你从 mpl_connect 取回的令牌,你可能会结束处理你无法获得引用但永远不会被 gc 的对象,因为 mpl 持有硬引用)。所以发生的事情是你正在将一个方法从你的 Picker 对象连接到回调系统,它超出范围,得到 gc'd 然后当 mpl 转到 运行 回调时,它发现对象已经消失并自动删除已失效的函数。

您所要做的就是在工具栏 class.

中保持对 Picker 对象的引用

这应该在上游有更清楚的记录。