# Hierarchy navigationobj.parent # Parent objectobj.firstChild # First child objectobj.lastChild # Last child objectobj.children # List of all childrenobj.next # Next siblingobj.previous # Previous sibling# Simplified navigation (filters out unimportant objects)obj.simpleParentobj.simpleFirstChildobj.simpleLastChildobj.simpleNextobj.simplePrevious
Override property getters to customize object information:
from NVDAObjects.IAccessible import IAccessibleimport controlTypesclass CustomControl(IAccessible): """Custom control with modified properties.""" def _get_name(self): """Override name property.""" # Get name from custom location name = self._getCustomName() return name or super().name def _get_role(self): """Override role.""" return controlTypes.Role.BUTTON def _get_description(self): """Override description.""" return "This is a custom control" def _get_value(self): """Override value.""" return self._getCustomValue() def _get_states(self): """Override states.""" states = super().states if self._isCustomSelected(): states.add(controlTypes.State.SELECTED) return states
When events are defined on NVDA Objects (not app modules/global plugins), the signature is different:
# In overlay class: only takes selfdef event_gainFocus(self): # No obj or nextHandler parameters super().event_gainFocus()# In app module/global plugin: takes obj and nextHandler def event_gainFocus(self, obj, nextHandler): # obj is the NVDA Object nextHandler()
Implement text support for controls by providing a TextInfo class:
from NVDAObjects.IAccessible import IAccessibleimport textInfos.offsetsclass CustomControl(IAccessible): """Control with custom text support.""" TextInfo = CustomTextInfoclass CustomTextInfo(textInfos.offsets.OffsetsTextInfo): """TextInfo for custom control.""" def _getStoryText(self): """Get all text content.""" # Extract text from control return self.obj._getCustomText() def _getStoryLength(self): """Get total length.""" return len(self._getStoryText()) def _getLineOffsets(self, offset): """Get start and end offsets for line at offset.""" text = self._getStoryText() start = text.rfind('\n', 0, offset) + 1 end = text.find('\n', offset) if end == -1: end = len(text) return (start, end)
from NVDAObjects.IAccessible import IAccessibleimport controlTypesimport uiclass IconButton(IAccessible): """Button that displays only an icon, needs text name.""" def _get_name(self): """Get name from tooltip or aria-label.""" # Try aria-label first ariaLabel = self._getAriaLabel() if ariaLabel: return ariaLabel # Fall back to tooltip tooltip = self.description if tooltip: return tooltip # Last resort: use role return "Button" def _get_role(self): """Ensure role is button.""" return controlTypes.Role.BUTTON def _getAriaLabel(self): """Extract aria-label attribute.""" # Implementation depends on control type return None
from NVDAObjects.IAccessible import IAccessibleimport controlTypesimport uiclass EnhancedListItem(IAccessible): """List item with rich content.""" def _get_name(self): """Construct name from multiple elements.""" parts = [] # Get main text mainText = self._getMainText() if mainText: parts.append(mainText) # Get secondary info secondary = self._getSecondaryInfo() if secondary: parts.append(secondary) # Get badge/status status = self._getStatus() if status: parts.append(f"Status: {status}") return ", ".join(parts) def _get_positionInfo(self): """Provide position in list.""" parent = self.parent if not parent: return None index = self._getIndex() total = parent.childCount return { 'indexInGroup': index, 'similarItemsInGroup': total } def _getMainText(self): """Get main text from control.""" return super().name def _getSecondaryInfo(self): """Get secondary information.""" # Extract from child elements return None def _getStatus(self): """Get status indicator.""" return None def _getIndex(self): """Get item index.""" return 1
from NVDAObjects.IAccessible import IAccessibleclass ExpensiveControl(IAccessible): """Control with expensive property calculations.""" def _get_name(self): """Name with caching.""" try: return self._cache['name'] except KeyError: name = self._calculateExpensiveName() self._cache['name'] = name return name def _calculateExpensiveName(self): """Expensive name calculation.""" # Complex processing return "Calculated name" def event_nameChange(self): """Clear cache when name changes.""" if 'name' in self._cache: del self._cache['name'] super().event_nameChange()