Skip to content

Initialize Window event handlers before impl construction (#4347)#4359

Merged
freakboy3742 merged 6 commits into
beeware:mainfrom
mvanhorn:fix/4347-handler-init-before-impl
May 1, 2026
Merged

Initialize Window event handlers before impl construction (#4347)#4359
freakboy3742 merged 6 commits into
beeware:mainfrom
mvanhorn:fix/4347-handler-init-before-impl

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

Closes #4347 (and the duplicate #4357).

Summary

When toga.Window.__init__ creates self._impl, some platform layers fire resize / focus callbacks before construction returns:

Both callbacks dispatch through the interface property getters, which read self._on_resize / self._on_gain_focus / etc. directly. Those attributes are only assigned at the end of __init__ via the self.on_X = on_X lines, so the in-construction callback raises:

AttributeError: 'Window' object has no attribute '_on_resize'

Fix

Install wrapped no-op handlers (wrapped_handler(self, None)) for all six on_X attributes before constructing self._impl. The explicit self.on_X = on_X assignments at the end of __init__ still run and override these defaults with the user-supplied handlers, so behavior is unchanged for the normal flow.

This matches the maintainer's analysis on #4347 and #4357: "the on_gain_focus handler needs to exist before the window is created."

Tests

Adds test_window_handler_attrs_initialized_before_impl in core/tests/window/test_window.py that monkey-patches the dummy Window.__init__ to fire all six interface callbacks from inside its constructor. The test passes on this branch and fails on main with AttributeError: 'Window' object has no attribute '_on_resize' -- exactly the bug from the issue.

$ python -m pytest core/tests/window/ -q
109 passed

PR Checklist

  • All new features have been tested
  • All new features have been documented (changenote changes/4347.bugfix.md)
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

mvanhorn and others added 6 commits April 30, 2026 05:37
Closes beeware#4347 (and the duplicate beeware#4357).

When 'toga.Window.__init__' creates 'self._impl', some platform layers
fire resize / focus callbacks before construction returns:

- Cocoa: 'windowDidResize_' fires when the requested size has to be
  clamped to fit the screen or the contained widgets.
- WinForms .NET Framework 4.x: 'Activated' fires during 'Form' creation
  when the window becomes the active form.

Both callbacks dispatch through the interface property getters, which
read 'self._on_resize' / 'self._on_gain_focus' / etc. directly. Those
attributes are only assigned at the *end* of '__init__' via the
'self.on_X = on_X' lines, so the in-construction callback raises
'AttributeError: "Window" object has no attribute "_on_resize"'
(see issue beeware#4347 traceback).

Fix: install wrapped no-op handlers ('wrapped_handler(self, None)') for
all six on_X attributes before constructing 'self._impl'. The explicit
'self.on_X = on_X' assignments at the end of '__init__' still run and
override these defaults with the user-supplied handlers.

Adds a regression test in 'tests/window/test_window.py' that monkey-
patches the dummy Window impl to fire all six callbacks from inside its
'__init__'. The new test passes on this branch and fails on main with
AttributeError.
Copy link
Copy Markdown
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I've cleaned up a couple of pieces in the implementation (details inline); but this was really helpful as a starting point (especially the test case).

Comment thread core/src/toga/window.py Outdated
Comment on lines +263 to +268
self._on_close = wrapped_handler(self, None)
self._on_gain_focus = wrapped_handler(self, None)
self._on_lose_focus = wrapped_handler(self, None)
self._on_show = wrapped_handler(self, None)
self._on_hide = wrapped_handler(self, None)
self._on_resize = wrapped_handler(self, None)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wrapping can be done automatically by assigning None as the actual handler (and that also ensures that cleanup behaviour is installed correctly - e.g., in the case of on_close)

Suggested change
self._on_close = wrapped_handler(self, None)
self._on_gain_focus = wrapped_handler(self, None)
self._on_lose_focus = wrapped_handler(self, None)
self._on_show = wrapped_handler(self, None)
self._on_hide = wrapped_handler(self, None)
self._on_resize = wrapped_handler(self, None)
self.on_close = None
self.on_gain_focus = None
self.on_lose_focus = None
self.on_show = None
self.on_hide = None
self.on_resize = None

Comment thread core/tests/window/test_window.py Outdated
"on_show",
"on_hide",
"on_resize",
):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A loop through a number of different test assertions is a likely indicator that a test should be parameterised. That way we'll get an independent failure signal if one of the handler is modified unexpectedly.

Comment thread changes/4347.bugfix.md Outdated
@@ -0,0 +1 @@
Window event handlers are now initialized to no-op defaults before the platform implementation is created, so resize and focus callbacks that fire during impl construction (Cocoa ``windowDidResize_``, WinForms .NET Framework 4.x ``Activated``) no longer raise ``AttributeError`` on ``_on_resize`` / ``_on_gain_focus`` / etc.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be a bit more terse and user-focused here. The final release note will reference the ticket number, so someone wanting more detail can find out more; so we only indicate the nature of the problem at a high level in the release note.

Suggested change
Window event handlers are now initialized to no-op defaults before the platform implementation is created, so resize and focus callbacks that fire during impl construction (Cocoa ``windowDidResize_``, WinForms .NET Framework 4.x ``Activated``) no longer raise ``AttributeError`` on ``_on_resize`` / ``_on_gain_focus`` / etc.
Runtime errors caused by Window event handlers firing as part of the window initialization process have now been silenced.

@freakboy3742 freakboy3742 merged commit 61cdd7f into beeware:main May 1, 2026
122 of 123 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Window resize during init fails on macOS

2 participants