Widget IDs are not cleared when a window closes
ethindp opened this issue · comments
Describe the bug
When a window is created upon the click of a button or other widget in a main window, IDs of widgets within that sub-window are not cleared, causing window creation and displaying to fail when the window is closed and re-opened.
Steps to reproduce
- Create any main window with some widget that instantiates and shows another window when the widget is pressed, clicked, etc.
- The sub-window should use widget IDs, such as paths (I use this format for determining which widget to update when a network event is received in my application).
- Close the window and try to re-open it. You will get an error along the lines of
KeyError: "There is already a widget with the id '/devices/0/inputs/0/sends/0/Gain/value'"
Expected behavior
Widget IDs should be cleared when the window is either closed, deleted, or otherwise ceases to exist.
Screenshots
No response
Environment
- Operating System: Windows 10 22H2 (AMD64) build 19045.4291
- Python version: 3.12.2
- Software versions:
- Briefcase: 0.3.17
- Toga: 0.4.2+
Logs
[19:49:53] Error in handler: "There is already a widget with the id '/devices/0/inputs/0/sends/0/Gain/value'" subprocess.py:681
Traceback (most recent call last): subprocess.py:681
File "C:\Users\ethin\source\venvs\uaccess\Lib\site-packages\toga\handlers.py", line 80, in _handler subprocess.py:681
result = handler(interface, *args, **kwargs) subprocess.py:681
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ subprocess.py:681
File "C:\Users\ethin\source\uaccess\src\uaccess\app.py", line 216, in open_sends subprocess.py:681
self.sends_dialogs[widget.id].build() subprocess.py:681
File "C:\Users\ethin\source\uaccess\src\uaccess\dialogs\sends_dialog.py", line 27, in build subprocess.py:681
self.sends_content.add(edit) subprocess.py:681
File "C:\Users\ethin\source\venvs\uaccess\Lib\site-packages\toga\widgets\base.py", line 100, in add subprocess.py:681
child.window = self.window subprocess.py:681
^^^^^^^^^^^^ subprocess.py:681
File "C:\Users\ethin\source\venvs\uaccess\Lib\site-packages\toga\widgets\base.py", line 227, in window subprocess.py:681
window.app.widgets._add(self) subprocess.py:681
File "C:\Users\ethin\source\venvs\uaccess\Lib\site-packages\toga\app.py", line 192, in _add subprocess.py:681
raise KeyError(f"There is already a widget with the id {widget.id!r}") subprocess.py:681
KeyError: "There is already a widget with the id '/devices/0/inputs/0/sends/0/Gain/value'" subprocess.py:681
Additional context
No response
Thanks for the report. If you're able to provide actual code for a minimal reproduction example, rather than a "word description" of the problem, that would be a lot more helpful for debugging purposes, as it guarantees we're looking at the same problem you are.
The repro is something along the lines:
import toga
class SendsDialog(toga.Window):
def __init__(self, device_id, input_id):
super().__init__(title="Edit Sends", size=(400, 200))
self.sends_content = toga.Box()
self.input = input_id
self.device = device_id
self.content = self.sends_content
def build(self):
path = f"/devices/0/inputs/0/sends/0/Gain/value"
sendname = "AUX 1"
val = -144.0
default = -144.0
min = -144.0
max = 12.0
label = toga.Label(f"{sendname} Gain")
edit = toga.NumberInput(id=path, step=1.0, min=min, max = max, value = val if val is not None else default, on_change=self.on_prop_float_change)
self.sends_content.add(label)
self.sends_content.add(edit)
self.sends_content.add(toga.Button("&Close", on_press=self.close_window))
async def on_prop_float_change(self, widget, *args, **kwargs):
pass # Do nothing
def close_window(self, widget, *args, **kwargs):
self.close()
class Repro(toga.App):
def startup(self):
self.on_exit = self.handle_exit
self.main_window = toga.MainWindow(title=f"{self.formal_name} [Loading]")
self.main_container = toga.Box(
style=Pack(direction=COLUMN, padding=10),
)
self.sends_btn = toga.Button("&Sends", on_press=self.open_sends)
self.main_container.content.append(self.sends_btn)
self.main_window.content = self.main_container
self.main_window.show()
async def close_app(self, widget, **kwargs):
self.exit()
def open_sends(self, widget, *args, **kwargs):
dialog = SendsDialog(0, 0)
dialog.build()
dialog.show()
def main():
return Repro()
A simplified reproducible example of the above would be:
import toga
class HelloWorld(toga.App):
def startup(self):
self.main_window = toga.MainWindow(title=f"{self.formal_name}")
self.main_window.content = toga.Box(
children=[toga.Button("Open Second Window", on_press=self.do_open_window)]
)
self.main_window.show()
def do_open_window(self, widget, *args, **kwargs):
window = toga.Window()
window.content = toga.Box(
children=[toga.Label(text="Sample Label", id="sample_label")]
)
window.show()
def main():
return HelloWorld()
The title should be changed to clarify that the bug is with widget IDs not with window IDs.
@proneon267 Thanks for that simplification, I wasn't sure if I'd done it right. I've updated both the issue title and the original comment to specifically indicate that this occurred with widget IDs -- somehow I didn't notice that or my brain filtered it out :P
No worries :)