Install any skill in seconds. Free to start, no credit card required.
Get Started Free →wxPython GUI expert covering sizer layouts, event handling, AUI framework, custom controls, threading (wx.CallAfter/wx.PostEvent), dialog design, menu/toolbar construction, and desktop accessibility (screen readers, keyboard navigation). Covers cross-platform gotchas for Windows and macOS.
.claude/skills/community-access-wxpython-specialist-075923/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 17% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 62% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 53% | 0% |
wxPython GUI expert covering sizer layouts, event handling, AUI framework, custom controls, threading (wx.CallAfter/wx.PostEvent), dialog design, menu/toolbar construction, and desktop accessibility (screen readers, keyboard navigation). Covers cross-platform gotchas for Windows and macOS.
wx.CallAfter() or wx.PostEvent() from worker threads.wx.BoxSizer(wx.VERTICAL/wx.HORIZONTAL) for stack or row layoutswx.GridBagSizer(vgap, hgap) for form layoutswx.SizerFlags(proportion).Expand().Border(wx.ALL, border) modern APIwx.EXPAND fills the non-main axisself.SetSizerAndFit(sizer)self.Bind(wx.EVT_BUTTON, self.handler, self.btn) standard bindingwx.lib.newevent.NewEvent() for custom event typeswx.PostEvent(target, evt) for thread-safe event postingevent.Skip() to let other handlers also process the eventwx.EVT_CLOSE for cleanupScreen readers like NVDA and JAWS install a low-level keyboard hook (WH_KEYBOARD_LL) that intercepts every keystroke system-wide before any window message reaches the application. When the screen reader consumes a key (e.g., Enter on a focused wx.ListBox), the WM_KEYDOWN message never arrives -- so EVT_KEY_DOWN and EVT_CHAR handlers silently fail.
Why EVT_CHAR_HOOK works: Even when WM_KEYDOWN does arrive, native Win32 controls (ListBox, TreeView, ListView) may process the message in their own WndProc before wxPython generates EVT_KEY_DOWN. EVT_CHAR_HOOK fires at the top-level window within wxWidgets' own event processing, before the native control handler runs.
Event priority order:
EVT_CHAR_HOOK -- fires first, before native control processingEVT_KEY_DOWN -- may never fire if control consumes the messageEVT_CHAR -- may never fireEVT_KEY_UP -- fires on key releaseCorrect pattern:
pythonclass MyFrame(wx.Frame): def __init__(self, parent): super().__init__(parent, title="Example") self.list_box = wx.ListBox(self, choices=["Item 1", "Item 2"]) # WRONG -- silently fails when NVDA/JAWS is active # self.list_box.Bind(wx.EVT_KEY_DOWN, self.on_key) # CORRECT -- fires before the native control handler self.Bind(wx.EVT_CHAR_HOOK, self.on_char_hook) def on_char_hook(self, event): key = event.GetKeyCode() focused = wx.Window.FindFocus() if focused == self.list_box and key == wx.WXK_RETURN: self.activate_selected_item() return # consume the key if key == wx.WXK_ESCAPE: self.Close() return event.Skip() # let other keys propagate
Prefer semantic events when available:
| Widget | Semantic Event | Use Instead Of | |---|---|---| | wx.ListCtrl | EVT_LIST_ITEM_ACTIVATED | EVT_KEY_DOWN for Enter | | wx.TreeCtrl | EVT_TREE_ITEM_ACTIVATED | EVT_KEY_DOWN for Enter | | wx.Button | EVT_BUTTON | EVT_KEY_DOWN for Enter/Space | | wx.CheckBox | EVT_CHECKBOX | EVT_KEY_DOWN for Space |
Semantic events fire regardless of activation method (keyboard, mouse, or assistive technology), making them inherently screen-reader-safe.
> wx.ListBox does not provide EVT_LISTBOX_ACTIVATED in most wxPython versions. Use EVT_CHAR_HOOK for ListBox, or migrate to wx.ListCtrl.
How screen readers get labels from wxPython controls:
wx.StaticText as the label. Add a wx.StaticText immediately before the control in the sizer -- sizer/HWND sibling order determines the association.label= constructor parameter is already the accessible name. No extra work needed.SetToolTip() to provide descriptive text. For a programmatic accessible name, subclass wx.Accessible.> Common Mistake to Avoid: wx.Window.SetName() sets an internal widget name used by FindWindowByName() for programmatic widget lookup. It has no effect on screen readers. NVDA, VoiceOver, and JAWS do not read SetName() values as accessible labels.
python# CORRECT -- StaticText immediately before the control in the sizer label = wx.StaticText(panel, label="Username:") ctrl = wx.TextCtrl(panel) sizer.Add(label, 0, wx.ALL, 5) sizer.Add(ctrl, 0, wx.EXPAND | wx.ALL, 5) # CORRECT -- button label= is already the accessible name btn = wx.Button(panel, label="Save document") # WRONG -- SetName() does NOT make controls accessible to screen readers ctrl.SetName("Username") # Only affects FindWindowByName() -- screen readers ignore it
MoveAfterInTabOrder() to overridewx.AcceleratorTable for keyboard shortcutsCreateStdDialogButtonSizer() auto-handles platform button orderEVT_CHAR_HOOK (not EVT_KEY_DOWN/EVT_CHAR)| ID | Severity | What to Flag | |---|---|---| | WX-A11Y-001 | Critical | Control without a preceding wx.StaticText label (inputs/selects) and without a label= parameter (buttons) | | WX-A11Y-002 | Critical | Window with no wx.AcceleratorTable | | WX-A11Y-003 | Critical | Mouse event without equivalent keyboard event | | WX-A11Y-004 | Serious | Dialog without CreateStdDialogButtonSizer() or Escape handling | | WX-A11Y-005 | Serious | ShowModal() without SetFocus() on a meaningful control | | WX-A11Y-006 | Serious | Bitmap/BitmapButton without SetToolTip() or wx.Accessible subclass | | WX-A11Y-007 | Moderate | Color as sole state indicator | | WX-A11Y-008 | Moderate | Status change without accessible announcement | | WX-A11Y-009 | Moderate | Custom-drawn panel without wx.Accessible subclass | | WX-A11Y-010 | Minor | Tab order mismatches visual reading order | | WX-A11Y-011 | Serious | Virtual list/tree without meaningful GetItemText override | | WX-A11Y-012 | Moderate | Menu item without accelerator key | | WX-A11Y-013 | Critical | EVT_KEY_DOWN/EVT_CHAR on ListBox/ListCtrl/TreeCtrl/DataViewCtrl for Enter/Space/Escape -- use EVT_CHAR_HOOK or semantic events | | WX-A11Y-014 | Serious | wx.ListCtrl with EVT_KEY_DOWN for Enter instead of EVT_LIST_ITEM_ACTIVATED |
| Area | Windows | macOS | |---|---|---| | Menu bar | Window title bar | Global top bar | | Button order | OK / Cancel | Cancel / OK (auto) | | DPI | Per-monitor aware | Retina auto |
| Need | Route To | |------|----------| | Python language / packaging / testing | python-specialist | | Platform a11y APIs (UIA, MSAA, NSAccessibility) | desktop-a11y-specialist | | Screen reader testing (NVDA, JAWS) | desktop-a11y-testing-coach | | Build a11y scanner / rule engine | a11y-tool-builder | | Web accessibility audit | web-accessibility-wizard | | Document accessibility audit | document-accessibility-wizard |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 22,558 | 19,045 | -16% | 1 | 1 | 0% | 3,941 | 5,847 | +48% | 0 | 0 | — |
case-02 | fail→fail | 19,223 | 16,650 | -13% | 1 | 1 | 0% | 3,413 | 5,348 | +57% | 0 | 0 | — |
case-03 | fail→pass | 31,002 | 21,316 | -31% | 1 | 1 | 0% | 5,550 | 6,517 | +17% | 0 | 0 | — |
case-04 | fail→fail | 18,286 | 20,704 | +13% | 1 | 1 | 0% | 2,848 | 5,640 | +98% | 0 | 0 | — |
case-05 | fail→pass | 20,562 | 17,429 | -15% | 1 | 1 | 0% | 4,121 | 5,468 | +33% | 0 | 0 | — |
case-06 | fail→fail | 19,974 | 24,260 | +21% | 1 | 1 | 0% | 3,478 | 6,625 | +90% | 0 | 0 | — |
case-07 | pass→fail | 8,985 | 4,478 | -50% | 1 | 1 | 0% | 1,640 | 2,930 | +79% | 0 | 0 | — |
case-08 | pass→pass | 10,997 | 9,507 | -14% | 1 | 1 | 0% | 1,976 | 3,888 | +97% | 0 | 0 | — |
case-09 | pass→pass | 12,356 | 6,915 | -44% | 1 | 1 | 0% | 2,241 | 3,517 | +57% | 0 | 0 | — |
case-10 | fail→pass | 11,064 | 6,120 | -45% | 1 | 1 | 0% | 2,001 | 3,235 | +62% | 0 | 0 | — |
case-11 | pass→pass | 15,161 | 11,388 | -25% | 1 | 1 | 0% | 2,438 | 4,080 | +67% | 0 | 0 | — |
case-12 | pass→pass | 13,052 | 9,771 | -25% | 1 | 1 | 0% | 2,092 | 3,938 | +88% | 0 | 0 | — |
case-13 | fail→pass | 17,358 | 12,022 | -31% | 1 | 1 | 0% | 3,029 | 4,646 | +53% | 0 | 0 | — |
case-14 | pass→pass | 11,958 | 10,478 | -12% | 1 | 1 | 0% | 2,177 | 4,122 | +89% | 0 | 0 | — |
case-15 | pass→pass | 11,745 | 9,072 | -23% | 1 | 1 | 0% | 1,982 | 3,799 | +92% | 0 | 0 | — |
case-16 | pass→pass | 9,088 | 6,354 | -30% | 1 | 1 | 0% | 1,495 | 3,175 | +112% | 0 | 0 | — |
case-17 | pass→pass | 6,089 | 4,857 | -20% | 1 | 1 | 0% | 1,111 | 3,056 | +175% | 0 | 0 | — |
case-18 | pass→pass | 5,400 | 6,545 | +21% | 1 | 1 | 0% | 914 | 3,428 | +275% | 0 | 0 | — |
case-19 | pass→pass | 3,799 | 5,316 | +40% | 1 | 1 | 0% | 613 | 3,154 | +415% | 0 | 0 | — |
case-20 | pass→pass | 10,730 | 10,659 | -1% | 1 | 1 | 0% | 2,058 | 4,207 | +104% | 0 | 0 | — |
case-21 | fail→fail | 11,165 | 13,576 | +22% | 1 | 1 | 0% | 2,101 | 4,646 | +121% | 0 | 0 | — |
case-22 | fail→pass | 5,940 | 6,230 | +5% | 1 | 1 | 0% | 1,097 | 3,293 | +200% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.