I'm a new user of this stackoverflow community. I'm struggling from days with some issue in my code that I can't really figure out. In my following code I'm trying to define a custom selector tool for my GUI. I've reproduced the "bug" and the main structure of my program). I'm trying to connect a matplotlib signal with the "mpl_connect" structor, but unfortunately the signal of button clicked,released and mouse motion and the relative methods seems to be inactive for the canvas class. Here is the not-working code.
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()
If instead I define the same methods inside the "MyCanvas" class, they'll work. The working code is this:
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()
I need to keep the "Picker" and the relative methods separated from "MyCanvas" class for the sake of clarity. But I really can't understand what's going wrong in my first code: the signal seems to be emitted but not received. No error reported in STDERR. I think I've correctly respect the scoping of classes/methods.
The problem is that the mpl callback registry only holds weak references to the callbacks it is handed (the logic being that if you are not careful about holding onto the tokens you get back from mpl_connect
you could end up with objects that you have no way to get a ref to, but will never be gc'd because mpl is holding a hard reference). So what is happening is that you are hooking up a methods from your Picker
object to the callback system, it is going out of scope, getting gc'd and then when mpl goes to run the callbacks, it discovers the object is gone and automatically removes the defunct functions.
All you have to do is hold onto a ref to the Picker
object in your toolbar class.
This should probably be more clearly documented upstream.