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