Skip to content

Convert the main subtitle grid to TableView (screen reader accessibility, #13015) - #13017

Merged
niksedk merged 6 commits into
mainfrom
experiment/tableview-main-grid
Jul 31, 2026
Merged

Convert the main subtitle grid to TableView (screen reader accessibility, #13015)#13017
niksedk merged 6 commits into
mainfrom
experiment/tableview-main-grid

Conversation

@niksedk

@niksedk niksedk commented Jul 31, 2026

Copy link
Copy Markdown
Member

Fixes #13015.

Why

The Avalonia DataGrid keeps keyboard focus on the grid control itself — rows/cells never become the focused UI Automation element, and its automation peers implement no UIA patterns at all, so screen readers cannot tell which line is current. Upstream has deprecated the DataGrid (bug-fix-only), so this follows the TableView pilot path instead (#12704, #13001): TableViewRow is a ListBoxItem, so keyboard focus genuinely moves to the current row, UIA focus follows automatically, and selection is exposed via ISelectionItemProvider — no accessibility-specific code needed. Rows additionally get an accessible name ("number: text") so announcements are meaningful.

What

  • Main subtitle grid rebuilt on TableView with the same columns, templates, context menu, drag & drop, width persistence and auto-fit.
  • New reusable component src/UI/Logic/TableViewExtras.cs so the DataGrid-era machinery has a shared TableView home for future conversions:
    • SeTableViewColumn + TableViewColumnManager — bindable column IsVisible (TableView has none) implemented by inserting/removing columns in order; Tag/MinWidth for width persistence + auto-fit.
    • TableViewDragSelect — drag-to-select with accelerating edge auto-scroll.
    • Scroll helpers (center row / ensure fully visible) via ScrollViewer.Offset, replacing the ProcessVerticalScroll reflection hack.
    • Row hit-testing, page size, header/scrollbar hit checks, FocusRow (focuses the selected row's container for UIA).
  • Selection goes through the ListBox SelectionModel with batch updates — replaces both the ItemsSource detach hack for large selections (SE V5.0.0-rc3 hangs when Modify Selection results in a large number of lines #11529) and the incremental drag-select bookkeeping. SelectionMode.AlwaysSelected replaces the "re-select last removed item" workaround.
  • Net −400 lines.

Behavior differences vs the DataGrid

  • No column sorting (was already disabled).
  • Duration column is fixed-width instead of content-sized (TableView has no Auto sizing).
  • Scrollbar trough paging comes from the native ScrollViewer instead of DataGridScrollBarBehavior (shift+click jump on the trough is not carried over).

Testing so far

Builds clean (0 errors, warning count unchanged vs main). Runtime smoke testing still to do — hence draft. Main things to verify by hand:

  • Column show/hide toggles from the header context menu (runtime column insert/remove)
  • Selection: click / ctrl(cmd)-click / shift-click / shift+arrows / shift+Page/Home/End / drag-select with auto-scroll
  • Scrolling: center-selected-row setting, Home/End, play-next/previous follow
  • Column widths persist across restart; auto-fit; RTL mode
  • Hidden rows (IsHidden), alternating row tint, syntax highlighting, compact mode
  • Windows + NVDA/Inspect: focus follows the current row (the actual Accessibility regression in Subtitle Edit 5 – keyboard focus is no longer exposed via UI Automation (Windows) #13015 fix)

🤖 Generated with Claude Code

niksedk and others added 6 commits July 31, 2026 10:31
TableView rows are ListBoxItems, so keyboard focus moves to the current
row and UI Automation exposes it - with the DataGrid, focus stayed on the
grid control itself, which made the grid unusable with a screen reader
(issue #13015). Rows also get an accessible name ("number: text").

The DataGrid-specific machinery is replaced by a reusable TableViewExtras
component (Logic/TableViewExtras.cs) so future TableView conversions can
share it:

- SeTableViewColumn + TableViewColumnManager: bindable column IsVisible
  (TableViewColumn has none) implemented by adding/removing columns in
  order, plus Tag/MinWidth for width persistence and auto-fit.
- TableViewDragSelect: drag-to-select with accelerating edge auto-scroll.
- Scroll helpers (center row / ensure fully visible) via ScrollViewer
  offset instead of the DataGrid reflection hack.
- Row hit-testing, page size, header/scrollbar hit checks, FocusRow.

Selection now goes through the ListBox SelectionModel with batch updates,
which replaces both the ItemsSource detach hack for large selections
(#11529) and the incremental drag-select bookkeeping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The empty trailing column existed to keep the DataGrid's overlay scrollbar
off the outermost text column (issue #12351). With TableView the scrollbar
comes from a regular ScrollViewer, so setting AllowAutoHide=false keeps it
at full width in its own reserved layout space instead of the thin
expand-on-hover overlay - no gutter column (or its stray header line)
needed, and the bar is an easier drag target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Alternating rows: the nth-child style never applied to virtualized
  TableViewRow containers; tint per container from ContainerPrepared/
  ContainerIndexChanged instead, which also survives insert/remove/recycle.
  Selection and hover still win because the row theme's pseudo-class styles
  set the template Border's background directly.
- Column headers: a bit more breathing room - 2px more above the text and
  1px more below.
- Grid lines: drawn as per-cell borders they read stronger than the old
  DataGrid's gridline pass, so the body separators now use a fainter
  brush (0.22 opacity vs 0.5); the header keeps the stronger line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The alternating row background was applied correctly (verified with a
headless Avalonia harness) but invisible: TableView's theme paints
SystemControlBackgroundChromeMediumLowBrush (~#2B2B2B in dark mode) as the
control background, and the default alternating tint is #2D2D2D - a two
shade difference. The DataGrid had no background of its own, so rows sat
on SE's darker panel background and the same tint contrasted fine.
Transparent restores that backdrop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Cell backgrounds: TableViewColumn.HorizontalContentAlignment defaults to
  Left and is copied onto each cell, so a template's colored background
  Border shrink-wrapped the text instead of filling the cell like the
  DataGrid did. SeTableViewColumn now defaults to Stretch (verified with
  the headless harness: the border fills the cell in both directions).
- Header lines: with grid lines set to None the header's always-on
  right/bottom borders are the only separators in the grid, and at the
  full 0.5-opacity border brush they read much stronger than the old
  DataGrid header - use the faint grid-line brush for them too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@niksedk
niksedk marked this pull request as ready for review July 31, 2026 09:17
@niksedk
niksedk merged commit f77d635 into main Jul 31, 2026
0 of 2 checks passed
@niksedk
niksedk deleted the experiment/tableview-main-grid branch July 31, 2026 09:17
potplayer-fanpack pushed a commit to potplayer-fanpack/subtitleedit that referenced this pull request Jul 31, 2026
Batch 1 of the TableView sweep (after the main grid in SubtitleEdit#13017): every
DataGrid without sorting becomes a TableView built from the shared
TableViewExtras component, so keyboard focus lands on rows and screen
readers can follow the current line in these dialogs too (issue SubtitleEdit#13015).

Converted: BinaryEdit, Bookmarks, ErrorList, FindText, Compare,
ExportCustomTextFormat, ExportImageBased, ImportCsvXlsxCustomColumns,
RestoreAutoBackup, BatchErrorList, FixNames, MergeContinuationLines,
MergeTwoSubtitles, SortBy, FindDoubleLines, FindDoubleWords,
AutoTranslate, CopyPasteTranslate, ShotChanges (both windows),
ActorVoiceMapping, ReviewSpeech, ReviewSpeechHistory, VideoOcr.

Notable mappings:
- Former Auto-width columns get fixed pixel widths (TableView has no
  content sizing); the main text column stays the star column.
- DataGridCheckboxMultiSelect: extended selection is native ListBox
  behavior; its Space-toggles-checkbox piece is now
  TableViewExtras.AddSpaceToggle, used by BinaryEdit, FixNames and
  MergeContinuationLines. The helper class stays for the remaining
  DataGrids.
- BinaryEdit's mode-dependent zone column uses TableViewColumnManager.
- VideoOcr's editable text column becomes an in-cell two-way TextBox
  (TableView has no cell editing); focusing it selects its row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull Bot pushed a commit to A-Archives-and-Forks/subtitleedit that referenced this pull request Jul 31, 2026
Batch 2 of the TableView sweep (SubtitleEdit#13017, SubtitleEdit#13018): every remaining DataGrid
with CanUserSortColumns=true but no deliberate SortMemberPath sorting.
Rows take keyboard focus, so the screen-reader fix (SubtitleEdit#13015) now covers
these dialogs too.

Sorting was decided per grid. In the DataGrid only plain text columns
ever sorted (a view-level sort); TableViewHeaderSorter reorders the
backing collection, so it is wired only where the list order is
presentation-only and nothing consumes collection order:

- Wired (12 grids): FontCollector, FindRule, ManualChosenEncoding,
  PickFontName, PickLayerFilter, PickRuleProfile, PickOnlineSubtitle,
  and the track lists in PickMatroskaTrack/PickMp4Track/PickTsTrack/
  PickVobSubLanguage - with typed keys (numeric stream ids, nullable
  comparers) and header-double-click guards where double-tap accepted
  the dialog.
- Dropped everywhere else: subtitle/fix previews in timeline order,
  ASSA/SSA style and attachment lists (order is written to the file),
  MultipleReplace exports, batch queues, join/split file lists, and
  embedded-track lists (order is the output track order). Each carries
  a code comment with the reason.

Other notable mappings:
- DataGridCheckboxMultiSelect replaced by native extended selection +
  TableViewExtras.AddSpaceToggle (AiReview, ApplyDurationLimits,
  ConvertActors, FixNetflixErrors incl. its canToggle semantics,
  PickLayerFilter, RemoveTextForHearingImpaired; RemoveUnicodeCharacters
  hand-rolls it to coexist with its in-cell ReplaceWith TextBox editor).
- Home/End first/last-row navigation hoisted into
  TableViewExtras.AttachHomeEndNavigation (sync + Assa/Ssa windows).
- AssaStyles' DataGridCollectionView category filter replaced by a plain
  rebuilt ObservableCollection view.
- IsFocused checks on grids became IsKeyboardFocusWithin (focus lands on
  row containers now) in CutVideo/PickMatroskaTrack/PickVobSubLanguage.
- Sync-point lists show a column header now (TableView cannot hide
  headers).

Remaining DataGrids: Shortcuts, BatchConvert and FixCommonErrors (real
SortMemberPath sorting) - batch 3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Accessibility regression in Subtitle Edit 5 – keyboard focus is no longer exposed via UI Automation (Windows)

1 participant