|
32 | 32 | _UIA_TEXT_PATTERN_ID = 10014 |
33 | 33 | _UIA_ITEMCONTAINER_PATTERN_ID = 10019 |
34 | 34 | _UIA_VIRTUALIZEDITEM_PATTERN_ID = 10020 |
| 35 | +_UIA_TABLE_PATTERN_ID = 10012 |
| 36 | +_UIA_GRIDITEM_PATTERN_ID = 10007 |
35 | 37 | _UIA_AUTOMATIONID_PROPERTY = 30011 |
36 | 38 | _EXPAND_STATES = {0: "collapsed", 1: "expanded", 2: "partial", 3: "leaf"} |
37 | 39 |
|
@@ -305,6 +307,37 @@ def get_properties(self, name=None, role=None, app_name=None, |
305 | 307 | return None |
306 | 308 | return _read_properties(raw) |
307 | 309 |
|
| 310 | + def get_table_headers(self, name=None, role=None, app_name=None, |
| 311 | + automation_id=None) -> Optional[Dict[str, Any]]: |
| 312 | + raw = self._find_raw(name, role, app_name, automation_id) |
| 313 | + pattern = self._pattern(raw, _UIA_TABLE_PATTERN_ID, |
| 314 | + "IUIAutomationTablePattern") if raw else None |
| 315 | + if pattern is None: |
| 316 | + return None |
| 317 | + try: |
| 318 | + columns = pattern.GetCurrentColumnHeaders() |
| 319 | + rows = pattern.GetCurrentRowHeaders() |
| 320 | + except (OSError, AttributeError): |
| 321 | + return None |
| 322 | + return {"columns": _header_names(columns), "rows": _header_names(rows)} |
| 323 | + |
| 324 | + def get_grid_cell(self, row=0, column=0, name=None, role=None, |
| 325 | + app_name=None, automation_id=None) -> Optional[Dict[str, Any]]: |
| 326 | + raw = self._find_raw(name, role, app_name, automation_id) |
| 327 | + grid = self._pattern(raw, _UIA_GRID_PATTERN_ID, |
| 328 | + "IUIAutomationGridPattern") if raw else None |
| 329 | + if grid is None: |
| 330 | + return None |
| 331 | + try: |
| 332 | + cell = grid.GetItem(int(row), int(column)) |
| 333 | + except (OSError, AttributeError): |
| 334 | + return None |
| 335 | + if not cell: |
| 336 | + return None |
| 337 | + return _read_cell(self._pattern(cell, _UIA_GRIDITEM_PATTERN_ID, |
| 338 | + "IUIAutomationGridItemPattern"), |
| 339 | + cell, int(row), int(column)) |
| 340 | + |
308 | 341 | def _text_pattern(self, name, role, app_name, automation_id): |
309 | 342 | """Find a control and return its IUIAutomationTextPattern, or None.""" |
310 | 343 | raw = self._find_raw(name, role, app_name, automation_id) |
@@ -373,6 +406,45 @@ def _read_row(pattern, row: int, cols: int): |
373 | 406 | return cells |
374 | 407 |
|
375 | 408 |
|
| 409 | +def _header_names(array) -> List[str]: |
| 410 | + """Read an IUIAutomationElementArray of header elements into name strings.""" |
| 411 | + names: List[str] = [] |
| 412 | + try: |
| 413 | + count = int(array.Length or 0) |
| 414 | + except (OSError, AttributeError): |
| 415 | + return names |
| 416 | + for index in range(count): |
| 417 | + try: |
| 418 | + names.append(str(array.GetElement(index).CurrentName or "")) |
| 419 | + except (OSError, AttributeError): |
| 420 | + names.append("") |
| 421 | + return names |
| 422 | + |
| 423 | + |
| 424 | +def _read_cell(item_pattern, cell, row: int, column: int) -> Dict[str, Any]: |
| 425 | + """Build a cell record, enriching with GridItemPattern row/col/span if present.""" |
| 426 | + info: Dict[str, Any] = { |
| 427 | + "value": _safe_name(cell), "row": row, "column": column, |
| 428 | + "row_span": 1, "column_span": 1, |
| 429 | + } |
| 430 | + if item_pattern is not None: |
| 431 | + for key, attr in (("row", "CurrentRow"), ("column", "CurrentColumn"), |
| 432 | + ("row_span", "CurrentRowSpan"), |
| 433 | + ("column_span", "CurrentColumnSpan")): |
| 434 | + try: |
| 435 | + info[key] = int(getattr(item_pattern, attr)) |
| 436 | + except (OSError, AttributeError, ValueError, TypeError): |
| 437 | + pass |
| 438 | + return info |
| 439 | + |
| 440 | + |
| 441 | +def _safe_name(raw) -> str: |
| 442 | + try: |
| 443 | + return str(raw.CurrentName or "") |
| 444 | + except (OSError, AttributeError): |
| 445 | + return "" |
| 446 | + |
| 447 | + |
376 | 448 | def _as_text(value) -> str: |
377 | 449 | return str(value or "") |
378 | 450 |
|
|
0 commit comments