beeware / toga

A Python native, OS native GUI toolkit.

Home Page:https://toga.readthedocs.io/en/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

  1. Create any main window with some widget that instantiates and shows another window when the widget is pressed, clicked, etc.
  2. 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).
  3. 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 :)