I would like to have a matplotlib button that can only be used once. Ideally, I could do this by disconnecting the callback. However, there is an issue of timing in having a callback disconnect itself.
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig, ax = plt.subplots()
donebutton = Button(ax, "Disconnect the button")
def donecallback(event):
donebutton.disconnect(donecid)
print("Disconnected")
donecid = donebutton.on_clicked(donecallback)
plt.show()
To disconnect the callback, I need its callback ID, donecid
, which I obtain when I connect the callback. To connect the callback, I first must define it, donecallback
. To define the callback, I must already know the CID. Therefore, I am stuck with a chicken-and-egg problem.
Trying to run the code above produces the following error:
Traceback (most recent call last):
File "C:\Users\MyName\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\matplotlib\cbook\__init__.py", line 196, in process
func(*args, **kwargs)
File "C:\Users\MyName\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\matplotlib\widgets.py", line 210, in _release
for cid, func in self.observers.items():
RuntimeError: dictionary changed size during iteration
I can try to fix the problem by making a button object so that the CID can be passed in as one of its properties, like this:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
def mycallback():
print("Hello")
class SelfDisconnectingButton:
def __init__(self, ax, callback, text):
self.button = Button(ax, text)
self.callback = callback
self.cbcid = self.button.on_clicked(self.cbmethod)
self.disconnectcid = self.button.on_clicked(self.disconnectmethod)
self.pressedyet = False
def cbmethod(self, event): # Callback method, used to get rid of event
self.callback()
def disconnectmethod(self, event):
if self.pressedyet==False:
self.pressedyet = True
self.button.disconnect(self.cbcid)
fig, ax = plt.subplots()
buttonobj = SelfDisconnectingButton(ax, mycallback, "Press here")
plt.show()
…or this:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class MyButton(Button):
def on_clicked(self, *args, **kwargs):
self.cb_id = super(MyButton, self).on_clicked(*args, **kwargs)
return self.cb_id
def disconnect(self):
return super(MyButton, self).disconnect(self.cb_id)
fig, ax = plt.subplots()
donebutton = MyButton(ax, "Disconnect the button")
def donecallback(event):
donebutton.disconnect()
print("Disconnected")
donebutton.on_clicked(donecallback)
plt.show()
…but all of these produce the same dictionary error. How can I avoid this error, and what is the proper way to disconnect this callback after it is called? I would like to understand how to disconnect the callback properly. In principle I could avoid the problem by clearing the axes and then creating a new button with no callback, but that would be running away from the problem rather than solving it.