Skip to main content

An accessible data grid for wxPython built on a native wx.dataview.DataViewListCtrl (NSTableView on macOS, native list view on Windows/Linux), so VoiceOver, NVDA, and JAWS read the table, its rows, and each cell with no WebView.

Project description

wx-accessible-grid

An accessible data grid for wxPython that a blind person can actually use. It is a real native wx control: a wx.dataview.DataViewListCtrl. On macOS that wraps NSTableView, so VoiceOver reads the table, its rows, and each cell value out of the box. On Windows and Linux it is the native list view, read by NVDA, JAWS, and Orca. No WebView, no HTML, and nothing to announce by hand: the screen reader does row and cell navigation itself.

The control matters. The stock wx.grid.Grid reads poorly in NVDA and JAWS. A plain wx.ListCtrl in report mode is worse: on macOS it falls back to wx's generic, custom-drawn implementation, which exposes nothing to NSAccessibility, so it is silent under VoiceOver. An earlier version of this library used a WebView to get around that. DataViewListCtrl wraps a real native table on each platform instead, so it carries the platform's own accessibility for free, no workaround needed.

It is built for data entry, not a spreadsheet engine. There are no formulas. What there is, is a native grid that is fully keyboard-operable and announced correctly, with editing that round-trips through your model.

What you get

  • A native table. Arrow up and down to move by row, and the screen reader reads the row. On macOS, VoiceOver also reads across the cells of a row by column ("Frequency, 146.520"), because NSTableView exposes every cell, not just the row.
  • Native multi-select. Selecting rows for a bulk operation (move, reorder, delete a region) is a real native selection the user already knows how to drive. The host reads the selected rows, acts through the model, and refreshes.
  • Selection and focus helpers that keep the screen reader honest. Moving to a row sets it as the current item before taking focus, so it is read once and not announced stale-then-correct.
  • An optional app-level Left/Right cell cursor, for platforms where the native control does not announce per-cell. On Windows the native list view does not speak a cell cursor as you arrow within a row, so pass an announce callback and the grid voices each cell as "value, column" itself. On macOS you leave it off: VoiceOver already reads cells with VO+Left/Right, a separate key channel, so the two never collide.
  • Editing round-trips through your model, so the value the screen reader confirms is the validated, normalized one, never the raw keystrokes.
  • A pure-Python model with no wx in it. Columns, row count, and cell text are all plain Python, so they are unit-testable headless, without a display.

Install

pip install wx-accessible-grid

That pulls in wxPython.

Use it

Describe your columns and provide the row data through a model, then drop the grid into a sizer. The grid asks your model for each cell's text as it builds.

import wx
from wx_accessible_grid import AccessibleGrid, GridModel, Column

class ChannelModel(GridModel):
    def __init__(self, rows):
        self._rows = rows
        self._cols = [
            Column("num", "#", is_row_header=True),
            Column("name", "Name"),
            Column("mode", "Mode"),
            Column("active", "Active"),
            Column("volume", "Volume"),
        ]

    def columns(self):
        return self._cols

    def row_count(self):
        return len(self._rows)

    def cell_text(self, row, column):
        # the text the table shows and the screen reader reads for this cell
        if column == "num":
            return str(row + 1)
        val = self._rows[row][column]
        if column == "active":
            return "Yes" if val else "No"
        return str(val)

grid = AccessibleGrid(panel, ChannelModel(rows), label="Memory channels")
sizer.Add(grid.control, 1, wx.EXPAND)

On Windows, where the native control does not speak a per-cell cursor, pass an announce callback to turn on Left/Right cell navigation:

grid = AccessibleGrid(panel, ChannelModel(rows), label="Memory channels",
                      announce=speak)   # speak("146.520, Frequency")
row, col = grid.current_cell()          # where the cursor is, for a per-cell edit

Leave announce off on macOS so VoiceOver stays the single voice. With it off, the grid behaves exactly as it does without the parameter (no cursor, no key handling).

The grid exposes the native selection so the host can act on it:

nums = grid.selected_rows()      # selected rows, or the focused row if none
grid.select_rows([3, 4, 5])      # replace the selection
grid.focus_row(3)                # make that the current row and ensure it is read
grid.refresh_rows([3])           # update just those rows' cells after an edit
grid.refresh()                   # update every cell (or rebuild if rows changed)
grid.set_columns()               # rebuild columns when the dataset's shape changes

The dev extra (pip install "wx-accessible-grid[dev]") adds pytest for the model tests, which run without wx.

Keyboard

  • Arrow up and down: move the current row. The screen reader reads the row.
  • On macOS, VoiceOver reads across the cells of the focused row by column using its own table navigation (VO+Left/Right); the app does not have to do anything for that.
  • On Windows, with an announce callback passed in, Left and Right move a cell cursor across the columns of the focused row and each cell is spoken as "value, column". The cursor stops at the first and last columns; it does not wrap.
  • Standard native DataViewListCtrl selection (Shift and Ctrl with arrows or click) extends or toggles the multi-selection.
  • Editing and row actions are wired by the host through the selection and focus helpers (for example a native edit dialog, or a context menu on Shift+F10), so they use real native controls that read correctly.

How it works

AccessibleGrid wraps a wx.dataview.DataViewListCtrl. On macOS that is a NSTableView; on Windows and Linux it is the platform's native list view. Because it is a real native table, the platform accessibility layer (NSAccessibility, UIA, AT-SPI) exposes the table, its rows, and each cell to the screen reader directly. No HTML, no WebView bridge, no injected JavaScript, and no hand-rolled announcements.

DataViewListCtrl is not a virtual control: it stores the rows, so the grid reads your model's cell_text(row, column) once per cell when it builds, and again only for the rows you refresh. For very large data sets you would move to a DataViewCtrl with a custom model; that is out of scope here. The model carries all the data in plain Python with no wx, so it can be tested headless. Editing is host-driven: read the selection, edit through your model with a native control, then call refresh_rows.

Status

Version 0.8.0. The library is built on DataViewListCtrl (since 0.7.0) after the earlier wx.ListCtrl version was found to be silent under VoiceOver on macOS (a structural limitation of wx's generic list on macOS, not a bug that could be patched). 0.8.0 adds the optional Left/Right cell cursor back on top of that backend, gated on the announce callback, so Windows/NVDA users get cell-by-cell reading while macOS/VoiceOver keeps its native default untouched. The native channel grid in Versatile Radio Programmer proved this control out on real hardware. Tested here with headless unit tests for the model and wx smoke tests for the widget. A full manual pass with VoiceOver on macOS and NVDA/JAWS on Windows is the next milestone; reports welcome.

License

MIT. A Community Access open-source project, created by Taylor Arndt.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wx_accessible_grid-0.8.0.tar.gz (44.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wx_accessible_grid-0.8.0-py3-none-any.whl (11.5 kB view details)

Uploaded Python 3

File details

Details for the file wx_accessible_grid-0.8.0.tar.gz.

File metadata

  • Download URL: wx_accessible_grid-0.8.0.tar.gz
  • Upload date:
  • Size: 44.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for wx_accessible_grid-0.8.0.tar.gz
Algorithm Hash digest
SHA256 ea6d9e710e7b37f8afa9ef64a34a1ed86ac1c6725435c932fa8074a2a1b82b23
MD5 326ad340f65e6ca73f76dfc181b6e0d8
BLAKE2b-256 ac80e624ebe8b7600f05c3df2fd0b75167ed07f91a5a11d9785d885540757e17

See more details on using hashes here.

File details

Details for the file wx_accessible_grid-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: wx_accessible_grid-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 11.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for wx_accessible_grid-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 753d926bd68cec723feaaafdfa5cd625136234940ae74f58f417df79045cc9ad
MD5 e2c89368259404a9ff2c68cb192ed6e3
BLAKE2b-256 b353892ec76fefa6822a1bc5e51b85d223045ee621da1824ddee000f8299f241

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page