Skip to content

Win32COM Automation

Python
# =============================================================================
# DEVELOPMENT MODE TOGGLE
# =============================================================================
USE_LOCAL_SOURCE = False  # <-- TOGGLE THIS

if USE_LOCAL_SOURCE:
    import sys
    from pathlib import Path
    local_path = str(Path.cwd().parent)
    if local_path not in sys.path:
        sys.path.insert(0, local_path)
    print(f"📁 LOCAL SOURCE MODE: Loading from {local_path}/ras_commander")
else:
    print("📦 PIP PACKAGE MODE: Loading installed ras-commander")

# Import ras-commander
from ras_commander import RasExamples, RasGuiAutomation, RasMap, init_ras_project, ras

# Additional imports
import os
import sys
from pathlib import Path

# Verify which version loaded
import ras_commander
print(f"✓ Loaded: {ras_commander.__file__}")
Text Only
📦 PIP PACKAGE MODE: Loading installed ras-commander


✓ Loaded: <workspace>\ras_commander\__init__.py

When GUI Automation Is Actually Needed

Scenario 1: Testing GUI Features - Verifying menu items exist - Testing dialog box behavior - Debugging GUI-specific bugs

Scenario 2: Tasks Without API - Exporting to Google Earth (no API yet) - Creating schematic plots (limited API) - Accessing advanced RAS Mapper features

Scenario 3: Legacy Integration - Integrating with non-Python tools that expect GUI - Screen recording demonstrations - Training materials creation

RasGuiAutomation Helper Functions

ras-commander provides GUI automation utilities:

Python
from ras_commander import RasGuiAutomation

# Find HEC-RAS window
hwnd = RasGuiAutomation.find_hecras_window()

# Get window handle by title
window = RasGuiAutomation.get_window_by_title("HEC-RAS")

# Send keyboard input
RasGuiAutomation.send_keys(hwnd, "Alt+F")  # File menu

Debugging GUI Automation

Common Issues:

  1. Window Not Found: HEC-RAS not running or title changed

    Python
    import win32gui
    
    def list_windows():
        def callback(hwnd, windows):
            if win32gui.IsWindowVisible(hwnd):
                windows.append((hwnd, win32gui.GetWindowText(hwnd)))
            return True
    
        windows = []
        win32gui.EnumWindows(callback, windows)
        return windows
    
    # Find HEC-RAS window
    all_windows = list_windows()
    ras_windows = [w for w in all_windows if 'RAS' in w[1]]
    print(ras_windows)
    

  2. Timing Issues: GUI not ready for next action

    Python
    import time
    
    # Click button
    button.click()
    time.sleep(1)  # Wait for action to complete
    
    # Better: Wait for element to appear
    app.window().wait('ready', timeout=10)
    

  3. Element Not Found: Control identifiers changed

    Python
    # Use pywinauto's print_control_identifiers()
    app = Application().connect(title_re=".*HEC-RAS.*")
    app.window().print_control_identifiers()
    

Prerequisites

Before running this notebook, ensure you have:

  1. ras-commander installed: pip install ras-commander
  2. Python 3.10+: Check with python --version
  3. HEC-RAS installed: GUI automation requires running application
  4. pywin32: pip install pywin32
  5. pywinauto: pip install pywinauto (for advanced automation)
  6. Windows OS: GUI automation is Windows-specific

What You'll Learn

This notebook demonstrates Windows GUI automation for HEC-RAS:

  • win32gui: Low-level window manipulation
  • pywinauto: High-level GUI automation
  • RasGuiAutomation: ras-commander's GUI helper functions
  • 121_legacy_hecrascontroller_and_rascontrol.ipynb - COM-based automation (HEC-RAS 3.x-5.x)
  • 110_single_plan_execution.ipynb - Preferred API-based execution (no GUI)

Critical Warning

GUI automation is fragile and NOT recommended for production workflows.

Use GUI automation only when: 1. API-based methods unavailable (rare in ras-commander) 2. Testing GUI-specific features 3. Debugging HEC-RAS GUI issues

Prefer API-based methods (RasCmdr.compute_plan(), etc.) for: - Reliability (no GUI dependencies) - Speed (no window manipulation overhead) - Headless execution (servers, CI/CD) - Maintainability (less brittle than GUI automation)

Alternative: API-Based Automation

For most tasks, use ras-commander's API instead:

Python
# ❌ GUI Automation (fragile)
# Use pywinauto to click "Run" button, wait for completion

# ✅ API-Based (robust)
from ras_commander import RasCmdr, init_ras_project

init_ras_project(project_folder, "6.6")
RasCmdr.compute_plan("01")  # No GUI required!

Parameters

Configure these values to customize the notebook for your project.

Python
# =============================================================================
# PARAMETERS - Edit these to customize the notebook
# =============================================================================
from pathlib import Path

# Project Configuration
PROJECT_NAME = "Balde Eagle Creek"           # Example project to extract
RAS_VERSION = "7.0"               # HEC-RAS version (6.3, 6.5, 6.6, etc.)

print(f"Project: {PROJECT_NAME}")
print(f"HEC-RAS Version: {RAS_VERSION}")
Text Only
Project: Balde Eagle Creek
HEC-RAS Version: 7.0
Python
# =============================================================================
# PROJECT SETUP
# =============================================================================
import os
import sys
from pathlib import Path

# HEC-RAS version to use

# Extract example project for this notebook
project_path = RasExamples.extract_project(PROJECT_NAME, suffix="16")
init_ras_project(project_path, RAS_VERSION)

# Construct HEC-RAS executable path from version
ras_exe = fr'"C:\Program Files (x86)\HEC\HEC-RAS\{RAS_VERSION}\Ras.exe"'

print(f"Project: {project_path}")
print(f"HEC-RAS: {ras_exe}")
Text Only
Project: <workspace>\examples\example_projects\Balde Eagle Creek_16
HEC-RAS: "<HEC-RAS>\7.0\Ras.exe"
Python
# =============================================================================
# LAUNCH HEC-RAS (GUI Required)
# =============================================================================
# NOTE: This cell opens the HEC-RAS GUI window. Skip this cell and subsequent
# GUI automation cells if running in automated testing mode.
# =============================================================================

import subprocess
import sys

# Print instructions to the user
print("\n" + "="*60)
print("MANUAL STEP REQUIRED: Update .rasmap to Version 6.x")
print("This project was created in HEC-RAS 5.0.7. To generate stored maps in HEC-RAS 6.x, you must:")
print("1. HEC-RAS will now be opened with this project.")
print("2. In HEC-RAS, open RAS Mapper (from the main toolbar).")
print("3. When prompted, allow RAS Mapper to update the .rasmap file to the new version.")
print("4. Once the update is complete, close RAS Mapper and exit HEC-RAS.")
print("\nAfter closing HEC-RAS, return here and continue running the notebook.")
print("="*60 + "\n")

# Use ras_exe from cell 2 and project file from initialized ras object
prj_path = f'"{str(ras.prj_file)}"'

command = f"{ras_exe} {prj_path}"
print(f"Command: {command}")

# Capture the process object so we can get its PID
if sys.platform == "win32":
    hecras_process = subprocess.Popen(command)
else:
    hecras_process = subprocess.Popen([ras_exe.strip('"'), prj_path.strip('"')])

# Store the process ID for use in the next cell
hecras_pid = hecras_process.pid
print(f"Opened HEC-RAS with Process ID: {hecras_pid}")
print("Please wait for the next cell to automate RAS Mapper...")
Text Only
============================================================
MANUAL STEP REQUIRED: Update .rasmap to Version 6.x
This project was created in HEC-RAS 5.0.7. To generate stored maps in HEC-RAS 6.x, you must:
1. HEC-RAS will now be opened with this project.
2. In HEC-RAS, open RAS Mapper (from the main toolbar).
3. When prompted, allow RAS Mapper to update the .rasmap file to the new version.
4. Once the update is complete, close RAS Mapper and exit HEC-RAS.

After closing HEC-RAS, return here and continue running the notebook.
============================================================

Command: "<HEC-RAS>\7.0\Ras.exe" "<workspace>\examples\example_projects\Balde Eagle Creek_16\BaldEagle.prj"
Opened HEC-RAS with Process ID: 20204
Please wait for the next cell to automate RAS Mapper...
Python
!pip install pywinauto
Text Only
Requirement already satisfied: pywinauto in %USERPROFILE%\anaconda3\envs\symphony-dev\Lib\site-packages (0.6.9)
Requirement already satisfied: six in %USERPROFILE%\anaconda3\envs\symphony-dev\Lib\site-packages (from pywinauto) (1.17.0)
Requirement already satisfied: comtypes in %USERPROFILE%\anaconda3\envs\symphony-dev\Lib\site-packages (from pywinauto) (1.4.16)
Requirement already satisfied: pywin32 in %USERPROFILE%\anaconda3\envs\symphony-dev\Lib\site-packages (from pywinauto) (311)
Python
# This cell will inspect the HEC-RAS window and list all interactable elements.
# It also provides an example of how to use the pywinauto library for more robust automation,
# including interaction with 64-bit processes like RAS Mapper.

import win32gui
import win32con
import win32api
import win32process
import time
import ctypes
from ctypes import wintypes

# ==============================================================================
# Part 1: Inspecting the HEC-RAS Window with pywin32
# ==============================================================================
# This section uses the original pywin32 approach to list all menus and
# child controls (buttons, text boxes, etc.) of the main HEC-RAS window.

# Constants from original script
MF_BYPOSITION = 0x00000400

def get_windows_by_pid(pid):
    """Find all windows belonging to a specific process ID"""
    def callback(hwnd, hwnds):
        if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
            _, window_pid = win32process.GetWindowThreadProcessId(hwnd)
            if window_pid == pid:
                window_title = win32gui.GetWindowText(hwnd)
                if window_title:
                    hwnds.append((hwnd, window_title))
        return True
    hwnds = []
    win32gui.EnumWindows(callback, hwnds)
    return hwnds

def find_main_hecras_window(windows):
    """Find the main HEC-RAS window from a list of windows"""
    for hwnd, title in windows:
        if "HEC-RAS" in title and win32gui.GetMenu(hwnd):
            return hwnd, title
    return None, None

def get_menu_string(menu_handle, pos):
    """Get menu item string at position"""
    buf_size = 256
    buf = ctypes.create_unicode_buffer(buf_size)
    user32 = ctypes.windll.user32
    result = user32.GetMenuStringW(menu_handle, pos, buf, buf_size, MF_BYPOSITION)
    if result:
        return buf.value
    return ""

def enumerate_all_menus(hwnd):
    """Enumerate all menus and their items in great detail."""
    menu_bar = win32gui.GetMenu(hwnd)
    if not menu_bar:
        print("No menu bar found on the window.")
        return

    menu_count = win32gui.GetMenuItemCount(menu_bar)
    print(f"\n--- Enumerating Menus ({menu_count} top-level) ---")

    for i in range(menu_count):
        menu_text = get_menu_string(menu_bar, i).replace('&', '')
        submenu = win32gui.GetSubMenu(menu_bar, i)
        print(f"\nMenu {i}: '{menu_text}'")

        if submenu:
            item_count = win32gui.GetMenuItemCount(submenu)
            print(f"  Contains {item_count} items:")
            for j in range(item_count):
                item_text = get_menu_string(submenu, j)
                menu_id = win32gui.GetMenuItemID(submenu, j)

                id_str = f"(ID: {menu_id})" if menu_id != -1 and menu_id != 0 else ""

                sub_submenu = win32gui.GetSubMenu(submenu, j)
                if sub_submenu:
                    print(f"    Item {j}: '{item_text}' -> [Submenu]")
                    sub_item_count = win32gui.GetMenuItemCount(sub_submenu)
                    for k in range(sub_item_count):
                        sub_item_text = get_menu_string(sub_submenu, k)
                        sub_menu_id = win32gui.GetMenuItemID(sub_submenu, k)
                        sub_id_str = f"(ID: {sub_menu_id})" if sub_menu_id != -1 and sub_menu_id != 0 else ""
                        print(f"      - '{sub_item_text}' {sub_id_str}")
                else:
                    print(f"    Item {j}: '{item_text}' {id_str}")
        else:
            print("  (This top-level item is not a menu)")

def enumerate_child_controls(hwnd):
    """Enumerates all child controls (widgets) of a window."""
    child_windows = []
    def callback(child_hwnd, _):
        child_windows.append(child_hwnd)
        return True

    win32gui.EnumChildWindows(hwnd, callback, None)

    print(f"\n--- Enumerating Child Controls ({len(child_windows)} found) ---")
    if not child_windows:
        print("No child controls found.")
        return

    for i, child_hwnd in enumerate(child_windows):
        class_name = win32gui.GetClassName(child_hwnd)
        window_text = win32gui.GetWindowText(child_hwnd)
        control_id = win32gui.GetDlgCtrlID(child_hwnd)

        style = win32gui.GetWindowLong(child_hwnd, win32con.GWL_STYLE)
        is_visible = (style & win32con.WS_VISIBLE) != 0

        rect = win32gui.GetWindowRect(child_hwnd)

        print(f"\nControl {i}:")
        print(f"  - HWND:        {child_hwnd}")
        print(f"  - Class Name:  '{class_name}'")
        print(f"  - Text/Caption: '{window_text}'")
        print(f"  - Control ID:  {control_id}")
        print(f"  - Visible:     {is_visible}")
        print(f"  - Position:    (L: {rect[0]}, T: {rect[1]}, R: {rect[2]}, B: {rect[3]})")

# Main execution for pywin32 inspection
if 'hecras_pid' not in globals() or hecras_pid is None:
    print("ERROR: HEC-RAS process ID not found. Please run the previous cell to launch HEC-RAS first.")
else:
    print(f"Looking for HEC-RAS windows for process ID: {hecras_pid}")
    time.sleep(2)

    windows = get_windows_by_pid(hecras_pid)
    if not windows:
        print(f"Could not find any windows for process ID {hecras_pid}")
    else:
        hec_ras_hwnd, title = find_main_hecras_window(windows)
        if not hec_ras_hwnd:
            print("Could not identify the main HEC-RAS window from the found windows:")
            for hwnd, title in windows:
                print(f"  - {title} (HWND: {hwnd})")
        else:
            print(f"\nFound main HEC-RAS window: '{title}' (HWND: {hec_ras_hwnd})")
            print("="*60)

            enumerate_all_menus(hec_ras_hwnd)
            enumerate_child_controls(hec_ras_hwnd)

            print("\n" + "="*60)
            print("Inspection complete. The lists above show all menus and controls discoverable with pywin32.")
Text Only
Looking for HEC-RAS windows for process ID: 20204



Found main HEC-RAS window: 'HEC-RAS 7.0' (HWND: 591894)
============================================================

--- Enumerating Menus (7 top-level) ---

Menu 0: 'File'
  Contains 25 items:
    Item 0: '&New Project ...' (ID: 2)
    Item 1: '&Open Project ...' (ID: 3)
    Item 2: '&Save Project' (ID: 4)
    Item 3: 'Save Project &As ...' (ID: 5)
    Item 4: '&Rename Project Title ...' (ID: 6)
    Item 5: '&Delete Project ...' (ID: 7)
    Item 6: '' (ID: 8)
    Item 7: '&Project Summary ...' (ID: 9)
    Item 8: 'Compare Model Data ...' (ID: 10)
    Item 9: '' (ID: 11)
    Item 10: '&Import HEC-2 Data ...' (ID: 12)
    Item 11: 'I&mport HEC-RAS Data ...' (ID: 13)
    Item 12: '&Generate Report ...' (ID: 14)
    Item 13: '&Export GIS Data ...' (ID: 15)
    Item 14: 'Export to HEC-&DSS ...' (ID: 16)
    Item 15: 'Restore Backup Data ' -> [Submenu]
      - 'Restore Geometry ...' (ID: 18)
      - 'Restore Steady Flow ...' (ID: 19)
      - 'Restore Unsteady Flow ...' (ID: 20)
      - 'Restore Plan ...' (ID: 21)
      - 'Restore Hydr Design ...' (ID: 22)
    Item 16: '' (ID: 23)
    Item 17: 'Zip Plan(s) or Archive Project...' (ID: 24)
    Item 18: '' (ID: 25)
    Item 19: 'E&xit' (ID: 26)
    Item 20: '' (ID: 27)
    Item 21: '<workspace>\examples\example_projects\Balde Eagle Creek_16\BaldEagle.prj' (ID: 28)
    Item 22: '<repo-cache>\working\notebook_runs\122_full_live_20260429_085928\example_projects\Muncie_122_rasmapper_updates_rm12_muncie_create_block\Muncie.prj' (ID: 29)
    Item 23: '<repo-cache>\working\notebook_runs\122_full_live_20260429_085928\working\notebook_runs\example_projects\A120-00-00_122_rasmapper_updates_rm11_hcfcd_m3_clear_cree\A120_00_00.prj' (ID: 30)
    Item 24: '<repo-cache>\working\notebook_runs\122_full_live_20260429_085928\example_projects\Muncie_122_rasmapper_updates_rm10_muncie_create_edge_\Muncie.prj' (ID: 31)

Menu 1: 'Edit'
  Contains 8 items:
    Item 0: '&Geometric Data ...' (ID: 37)
    Item 1: '' (ID: 38)
    Item 2: '&Steady Flow Data ...' (ID: 39)
    Item 3: '&Quasi Unsteady Flow (Sediment) ...' (ID: 40)
    Item 4: '&Unsteady Flow Data ...' (ID: 41)
    Item 5: '' (ID: 42)
    Item 6: 'Se&diment Data ...' (ID: 43)
    Item 7: '&Water Quality Data ...' (ID: 44)

Menu 2: 'Run'
  Contains 9 items:
    Item 0: '&Steady Flow Analysis ...' (ID: 46)
    Item 1: '&Unsteady Flow Analysis ...' (ID: 47)
    Item 2: 'Quasi-Unsteady Analysis (Sediment)...' (ID: 48)
    Item 3: 'Water Quality Analysis ...' (ID: 49)
    Item 4: '&Hydraulic Design Functions ...' (ID: 50)
    Item 5: '' (ID: 51)
    Item 6: 'Run Multiple Plans ...' (ID: 52)
    Item 7: '' (ID: 54)
    Item 8: 'Uncertainty Analysis' -> [Submenu]
      - 'Setup Parameters ...' (ID: 56)
      - 'Run Monte Carlo Analysis ...' (ID: 57)
      - 'Summarize Results' (ID: 58)

Menu 3: 'View'
  Contains 23 items:
    Item 0: '&Cross-Sections ...' (ID: 61)
    Item 1: '&Water Surface Profiles ...' (ID: 62)
    Item 2: '&General Profile Plot ...' (ID: 63)
    Item 3: '&Rating Curves ...' (ID: 64)
    Item 4: '3D View ...' (ID: 65)
    Item 5: '&X-Y-Z Perspective Plots (Classic) ...' (ID: 66)
    Item 6: '&Stage and Flow Hydrographs ...' (ID: 67)
    Item 7: '&Hydraulic Property Tables ...' (ID: 68)
    Item 8: '' (ID: 69)
    Item 9: '&Detailed Output Tables ...' (ID: 70)
    Item 10: '&Profile Summary Table ...' (ID: 71)
    Item 11: '&Summary Err,Warn, Notes ...' (ID: 72)
    Item 12: '' (ID: 73)
    Item 13: 'DSS Data ...' (ID: 74)
    Item 14: '' (ID: 75)
    Item 15: 'Unsteady Flow Spatial Plot (computation interval) ...' (ID: 76)
    Item 16: 'Unsteady Flow Time Series Plot (computation interval) ...' (ID: 77)
    Item 17: '' (ID: 78)
    Item 18: 'WQ Spatial Plot ...' (ID: 79)
    Item 19: 'WQ Time Series Plot ...' (ID: 80)
    Item 20: '' (ID: 81)
    Item 21: 'Sediment Output ...' (ID: 82)
    Item 22: 'Legacy Sediment Output' -> [Submenu]
      - 'Sediment Output (5.0)...' (ID: 84)
      - '4.x - Sediment Spatial Plot ...' (ID: 85)
      - '4.x - Sediment Time Series Plot ...' (ID: 86)
      - '4.x - XS Bed Change Plot...' (ID: 87)

Menu 4: 'Options'
  Contains 5 items:
    Item 0: '&Program Setup' -> [Submenu]
      - 'Default File &Viewer ...' (ID: 91)
      - 'Default Project Folder ...' (ID: 92)
      - '&Open last project on startup' (ID: 93)
      - 'Automatically Backup Data' (ID: 94)
      - 'Set Time for Automatic Backup ...' (ID: 95)
      - 'Parallelization CPU Affinity ...' (ID: 96)
    Item 1: '&Default Parameters' -> [Submenu]
      - '&Expansion and Contraction Coef ...' (ID: 98)
    Item 2: '&Unit system (US Customary/SI) ...' (ID: 99)
    Item 3: '&Convert Project Units ...' (ID: 100)
    Item 4: 'Convert &Horizontal Coordinate Systems ...' (ID: 101)

Menu 5: 'GIS Tools'
  Contains 1 items:
    Item 0: 'RAS Mapper ...' (ID: 103)

Menu 6: 'Help'
  Contains 21 items:
    Item 0: 'HEC-RAS Online Documentation ...   F1' (ID: 105)
    Item 1: 'Release Notes ...' (ID: 106)
    Item 2: 'Known Issues ...' (ID: 107)
    Item 3: 'Community Support on Discourse ...' (ID: 108)
    Item 4: '' (ID: 109)
    Item 5: 'Users Manual ...' (ID: 110)
    Item 6: '2D Modeling User's Manual …' (ID: 111)
    Item 7: 'RAS Mapper User's Manual …' (ID: 112)
    Item 8: 'Hydraulic Reference …' (ID: 113)
    Item 9: 'Applications Guide …' (ID: 114)
    Item 10: '1D Sediment User's Manual …' (ID: 115)
    Item 11: '2D Sediment User's Manual …' (ID: 116)
    Item 12: '2D Sediment Technical Reference …' (ID: 117)
    Item 13: 'Mud and Debris Manuals …' (ID: 118)
    Item 14: '' (ID: 119)
    Item 15: 'Download Example Projects ...' (ID: 120)
    Item 16: '' (ID: 121)
    Item 17: 'HEC-RAS Webpage ...' (ID: 122)
    Item 18: '' (ID: 123)
    Item 19: 'View Terms and Conditions of Use ...' (ID: 124)
    Item 20: '&About HEC-RAS ...' (ID: 125)

--- Enumerating Child Controls (36 found) ---

Control 0:
  - HWND:        591896
  - Class Name:  'ThunderRT6Timer'
  - Text/Caption: ''
  - Control ID:  1
  - Visible:     False
  - Position:    (L: 199, T: 391, R: 199, B: 391)

Control 1:
  - HWND:        526340
  - Class Name:  'ThunderRT6TextBox'
  - Text/Caption: ''
  - Control ID:  2
  - Visible:     False
  - Position:    (L: 827, T: 155, R: 852, B: 172)

Control 2:
  - HWND:        526344
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  3
  - Visible:     True
  - Position:    (L: 623, T: 151, R: 649, B: 177)

Control 3:
  - HWND:        461070
  - Class Name:  'ThunderRT6TextBox'
  - Text/Caption: ''
  - Control ID:  4
  - Visible:     False
  - Position:    (L: 795, T: 155, R: 824, B: 172)

Control 4:
  - HWND:        526622
  - Class Name:  'ThunderRT6Timer'
  - Text/Caption: ''
  - Control ID:  5
  - Visible:     False
  - Position:    (L: 171, T: 391, R: 171, B: 391)

Control 5:
  - HWND:        592086
  - Class Name:  'ThunderRT6TextBox'
  - Text/Caption: ''
  - Control ID:  6
  - Visible:     False
  - Position:    (L: 763, T: 155, R: 792, B: 171)

Control 6:
  - HWND:        592182
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  7
  - Visible:     True
  - Position:    (L: 759, T: 151, R: 785, B: 177)

Control 7:
  - HWND:        526668
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  8
  - Visible:     True
  - Position:    (L: 733, T: 151, R: 759, B: 177)

Control 8:
  - HWND:        461134
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  9
  - Visible:     True
  - Position:    (L: 707, T: 151, R: 733, B: 177)

Control 9:
  - HWND:        592166
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  10
  - Visible:     True
  - Position:    (L: 681, T: 151, R: 707, B: 177)

Control 10:
  - HWND:        526666
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  11
  - Visible:     True
  - Position:    (L: 655, T: 151, R: 681, B: 177)

Control 11:
  - HWND:        460784
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  12
  - Visible:     True
  - Position:    (L: 597, T: 151, R: 623, B: 177)

Control 12:
  - HWND:        591826
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  13
  - Visible:     True
  - Position:    (L: 571, T: 151, R: 597, B: 177)

Control 13:
  - HWND:        461006
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  14
  - Visible:     True
  - Position:    (L: 545, T: 151, R: 571, B: 177)

Control 14:
  - HWND:        460882
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  15
  - Visible:     True
  - Position:    (L: 519, T: 151, R: 545, B: 177)

Control 15:
  - HWND:        526634
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  16
  - Visible:     True
  - Position:    (L: 493, T: 151, R: 519, B: 177)

Control 16:
  - HWND:        460854
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  17
  - Visible:     True
  - Position:    (L: 467, T: 151, R: 493, B: 177)

Control 17:
  - HWND:        460856
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  18
  - Visible:     True
  - Position:    (L: 433, T: 151, R: 462, B: 177)

Control 18:
  - HWND:        460830
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  19
  - Visible:     True
  - Position:    (L: 402, T: 151, R: 428, B: 177)

Control 19:
  - HWND:        460832
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  20
  - Visible:     True
  - Position:    (L: 376, T: 151, R: 402, B: 177)

Control 20:
  - HWND:        460834
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  21
  - Visible:     True
  - Position:    (L: 350, T: 151, R: 376, B: 177)

Control 21:
  - HWND:        460836
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  22
  - Visible:     True
  - Position:    (L: 324, T: 151, R: 350, B: 177)

Control 22:
  - HWND:        460838
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  23
  - Visible:     True
  - Position:    (L: 298, T: 151, R: 324, B: 177)

Control 23:
  - HWND:        460866
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  24
  - Visible:     True
  - Position:    (L: 266, T: 151, R: 292, B: 177)

Control 24:
  - HWND:        460768
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  25
  - Visible:     True
  - Position:    (L: 240, T: 151, R: 266, B: 177)

Control 25:
  - HWND:        460844
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  26
  - Visible:     True
  - Position:    (L: 209, T: 151, R: 235, B: 177)

Control 26:
  - HWND:        460842
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  27
  - Visible:     True
  - Position:    (L: 183, T: 151, R: 209, B: 177)

Control 27:
  - HWND:        460870
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  28
  - Visible:     True
  - Position:    (L: 157, T: 151, R: 183, B: 177)

Control 28:
  - HWND:        460852
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  29
  - Visible:     True
  - Position:    (L: 131, T: 151, R: 157, B: 177)

Control 29:
  - HWND:        460788
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  30
  - Visible:     True
  - Position:    (L: 100, T: 151, R: 126, B: 177)

Control 30:
  - HWND:        460860
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  31
  - Visible:     True
  - Position:    (L: 74, T: 151, R: 100, B: 177)

Control 31:
  - HWND:        460858
  - Class Name:  'ThunderRT6CheckBox'
  - Text/Caption: ''
  - Control ID:  32
  - Visible:     True
  - Position:    (L: 774, T: 287, R: 791, B: 308)

Control 32:
  - HWND:        460864
  - Class Name:  'ThunderRT6CommandButton'
  - Text/Caption: ''
  - Control ID:  33
  - Visible:     True
  - Position:    (L: 891, T: 186, R: 912, B: 205)

Control 33:
  - HWND:        460880
  - Class Name:  'ThunderRT6PictureBoxDC'
  - Text/Caption: ''
  - Control ID:  34
  - Visible:     False
  - Position:    (L: 135, T: 391, R: 164, B: 413)

Control 34:
  - HWND:        460868
  - Class Name:  'ThunderRT6Timer'
  - Text/Caption: ''
  - Control ID:  35
  - Visible:     False
  - Position:    (L: 111, T: 391, R: 111, B: 391)

Control 35:
  - HWND:        787736
  - Class Name:  'ThunderRT6TextBox'
  - Text/Caption: ''
  - Control ID:  36
  - Visible:     True
  - Position:    (L: 159, T: 287, R: 770, B: 308)

============================================================
Inspection complete. The lists above show all menus and controls discoverable with pywin32.
Python
# ==============================================================================
# Part 1b: Deeper Menu and Window Object Enumeration using ctypes
# ==============================================================================

import win32gui
import win32con
import win32api
import win32process
import ctypes
from ctypes import wintypes

# Define MENUITEMINFO structure using ctypes
class MENUITEMINFO(ctypes.Structure):
    _fields_ = [
        ("cbSize", wintypes.UINT),
        ("fMask", wintypes.UINT),
        ("fType", wintypes.UINT),
        ("fState", wintypes.UINT),
        ("wID", wintypes.UINT),
        ("hSubMenu", wintypes.HMENU),
        ("hbmpChecked", wintypes.HBITMAP),
        ("hbmpUnchecked", wintypes.HBITMAP),
        ("dwItemData", ctypes.POINTER(ctypes.c_ulong)),
        ("dwTypeData", wintypes.LPWSTR),
        ("cch", wintypes.UINT),
        ("hbmpItem", wintypes.HBITMAP)
    ]

def get_menu_string(menu_handle, pos):
    """Get menu item string at position"""
    buf_size = 256
    buf = ctypes.create_unicode_buffer(buf_size)
    user32 = ctypes.windll.user32
    result = user32.GetMenuStringW(menu_handle, pos, buf, buf_size, MF_BYPOSITION)
    if result:
        return buf.value
    return ""

def enumerate_menu_item_details(menu_handle, item_index):
    """Get detailed information about a menu item using ctypes"""
    # Create and initialize MENUITEMINFO structure
    mii = MENUITEMINFO()
    mii.cbSize = ctypes.sizeof(MENUITEMINFO)
    mii.fMask = win32con.MIIM_STATE | win32con.MIIM_ID | win32con.MIIM_TYPE | win32con.MIIM_SUBMENU

    # Call GetMenuItemInfo using ctypes
    user32 = ctypes.windll.user32
    result = user32.GetMenuItemInfoW(
        menu_handle, 
        item_index, 
        True,  # fByPosition
        ctypes.byref(mii)
    )

    if result:
        # Parse state flags
        state_flags = []
        if mii.fState & win32con.MFS_CHECKED:
            state_flags.append("CHECKED")
        if mii.fState & win32con.MFS_DISABLED:
            state_flags.append("DISABLED")
        if mii.fState & win32con.MFS_GRAYED:
            state_flags.append("GRAYED")
        if mii.fState & win32con.MFS_HILITE:
            state_flags.append("HIGHLIGHTED")
        if mii.fState & win32con.MFS_DEFAULT:
            state_flags.append("DEFAULT")

        # Parse type flags
        type_flags = []
        if mii.fType & win32con.MFT_STRING:
            type_flags.append("STRING")
        if mii.fType & win32con.MFT_SEPARATOR:
            type_flags.append("SEPARATOR")
        if mii.fType & win32con.MFT_BITMAP:
            type_flags.append("BITMAP")
        if mii.fType & win32con.MFT_OWNERDRAW:
            type_flags.append("OWNERDRAW")

        return {
            "id": mii.wID,
            "type_flags": type_flags,
            "state": state_flags,
            "text": get_menu_string(menu_handle, item_index),
            "has_submenu": bool(mii.hSubMenu)
        }
    else:
        # Fallback to simpler approach if GetMenuItemInfo fails
        menu_id = win32gui.GetMenuItemID(menu_handle, item_index)
        menu_state = win32gui.GetMenuState(menu_handle, item_index, win32con.MF_BYPOSITION)

        state_flags = []
        if menu_state & win32con.MF_CHECKED:
            state_flags.append("CHECKED")
        if menu_state & win32con.MF_DISABLED:
            state_flags.append("DISABLED")
        if menu_state & win32con.MF_GRAYED:
            state_flags.append("GRAYED")
        if menu_state & win32con.MF_SEPARATOR:
            state_flags.append("SEPARATOR")

        return {
            "id": menu_id if menu_id != -1 else None,
            "state": state_flags,
            "text": get_menu_string(menu_handle, item_index),
            "has_submenu": win32gui.GetSubMenu(menu_handle, item_index) is not None,
            "fallback": True
        }

def enumerate_window_details(hwnd, indent=0):
    """Recursively enumerate window details including styles and extended styles"""
    if not hwnd:
        return

    # Get basic window info
    class_name = win32gui.GetClassName(hwnd)
    window_text = win32gui.GetWindowText(hwnd)

    # Get window styles
    style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
    ex_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)

    # Get window metrics
    rect = win32gui.GetWindowRect(hwnd)

    # Print window details
    indent_str = "  " * indent
    print(f"{indent_str}Window Handle: {hwnd}")
    print(f"{indent_str}Class: {class_name}")
    print(f"{indent_str}Text: {window_text}")
    print(f"{indent_str}Position: {rect}")
    print(f"{indent_str}Style: 0x{style:08X}")
    print(f"{indent_str}Extended Style: 0x{ex_style:08X}")

    # Enumerate child windows recursively (limit depth to avoid too much output)
    if indent < 3:  # Limit recursion depth
        child_windows = []
        win32gui.EnumChildWindows(
            hwnd,
            lambda child_hwnd, windows: windows.append(child_hwnd) or True,
            child_windows
        )

        if child_windows:
            print(f"{indent_str}Children: {len(child_windows)} found")
            for child_hwnd in child_windows[:5]:  # Show first 5 children
                print(f"{indent_str}---")
                enumerate_window_details(child_hwnd, indent + 1)
            if len(child_windows) > 5:
                print(f"{indent_str}... and {len(child_windows) - 5} more children")

def enumerate_full_menu_tree(hwnd):
    """Enumerate complete menu tree with all available details"""
    menu_bar = win32gui.GetMenu(hwnd)
    if not menu_bar:
        print("No menu bar found")
        return

    print("\n=== Complete Menu Tree Analysis ===\n")

    menu_count = win32gui.GetMenuItemCount(menu_bar)
    for i in range(menu_count):
        menu_text = get_menu_string(menu_bar, i)
        menu_details = enumerate_menu_item_details(menu_bar, i)
        print(f"\nTop Level Menu {i}: {menu_text}")
        print(f"Details: {menu_details}")

        submenu = win32gui.GetSubMenu(menu_bar, i)
        if submenu:
            submenu_count = win32gui.GetMenuItemCount(submenu)
            print(f"Contains {submenu_count} items:")

            for j in range(min(submenu_count, 10)):  # Show first 10 items
                submenu_text = get_menu_string(submenu, j)
                submenu_details = enumerate_menu_item_details(submenu, j)
                print(f"  └─ Item {j}: {submenu_text}")
                print(f"     Details: {submenu_details}")

                # Check for sub-submenus
                sub_submenu = win32gui.GetSubMenu(submenu, j)
                if sub_submenu:
                    sub_count = win32gui.GetMenuItemCount(sub_submenu)
                    print(f"     Has submenu with {sub_count} items:")
                    for k in range(min(sub_count, 5)):  # Show first 5 sub-items
                        sub_text = get_menu_string(sub_submenu, k)
                        sub_details = enumerate_menu_item_details(sub_submenu, k)
                        print(f"       └─ Sub-item {k}: {sub_text}")
                        print(f"          Details: {sub_details}")

            if submenu_count > 10:
                print(f"  ... and {submenu_count - 10} more items")

# Main execution
if 'hecras_pid' in globals() and hecras_pid is not None:
    # Wait for windows to be ready
    import time
    time.sleep(1)

    # Find HEC-RAS windows
    windows = get_windows_by_pid(hecras_pid)
    hec_ras_hwnd, title = find_main_hecras_window(windows)

    if hec_ras_hwnd:
        print("\n" + "="*80)
        print("Performing detailed menu enumeration...")
        enumerate_full_menu_tree(hec_ras_hwnd)

        print("\n" + "="*80)
        print("Performing detailed window hierarchy enumeration...")
        print(f"Main window: {title}")
        enumerate_window_details(hec_ras_hwnd)

        print("\n" + "="*80)
        print("Detailed enumeration complete.")
    else:
        print("Could not find main HEC-RAS window")
else:
    print("HEC-RAS process ID not found. Please run the cell that launches HEC-RAS first.")
Text Only
================================================================================
Performing detailed menu enumeration...

=== Complete Menu Tree Analysis ===


Top Level Menu 0: &File
Details: {'id': 1509301, 'type_flags': [], 'state': [], 'text': '&File', 'has_submenu': True}
Contains 25 items:
  └─ Item 0: &New Project ...
     Details: {'id': 2, 'type_flags': [], 'state': [], 'text': '&New Project ...', 'has_submenu': False}
  └─ Item 1: &Open Project ...
     Details: {'id': 3, 'type_flags': [], 'state': [], 'text': '&Open Project ...', 'has_submenu': False}
  └─ Item 2: &Save Project
     Details: {'id': 4, 'type_flags': [], 'state': [], 'text': '&Save Project', 'has_submenu': False}
  └─ Item 3: Save Project &As ...
     Details: {'id': 5, 'type_flags': [], 'state': [], 'text': 'Save Project &As ...', 'has_submenu': False}
  └─ Item 4: &Rename Project Title ...
     Details: {'id': 6, 'type_flags': [], 'state': [], 'text': '&Rename Project Title ...', 'has_submenu': False}
  └─ Item 5: &Delete Project ...
     Details: {'id': 7, 'type_flags': [], 'state': [], 'text': '&Delete Project ...', 'has_submenu': False}
  └─ Item 6: 
     Details: {'id': 8, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 7: &Project Summary ...
     Details: {'id': 9, 'type_flags': [], 'state': [], 'text': '&Project Summary ...', 'has_submenu': False}
  └─ Item 8: Compare Model Data ...
     Details: {'id': 10, 'type_flags': [], 'state': [], 'text': 'Compare Model Data ...', 'has_submenu': False}
  └─ Item 9: 
     Details: {'id': 11, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  ... and 15 more items

Top Level Menu 1: &Edit
Details: {'id': 657759, 'type_flags': [], 'state': [], 'text': '&Edit', 'has_submenu': True}
Contains 8 items:
  └─ Item 0: &Geometric Data ...
     Details: {'id': 37, 'type_flags': [], 'state': [], 'text': '&Geometric Data ...', 'has_submenu': False}
  └─ Item 1: 
     Details: {'id': 38, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 2: &Steady Flow Data ...
     Details: {'id': 39, 'type_flags': [], 'state': [], 'text': '&Steady Flow Data ...', 'has_submenu': False}
  └─ Item 3: &Quasi Unsteady Flow (Sediment) ...
     Details: {'id': 40, 'type_flags': [], 'state': [], 'text': '&Quasi Unsteady Flow (Sediment) ...', 'has_submenu': False}
  └─ Item 4: &Unsteady Flow Data ...
     Details: {'id': 41, 'type_flags': [], 'state': [], 'text': '&Unsteady Flow Data ...', 'has_submenu': False}
  └─ Item 5: 
     Details: {'id': 42, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 6: Se&diment Data ...
     Details: {'id': 43, 'type_flags': [], 'state': [], 'text': 'Se&diment Data ...', 'has_submenu': False}
  └─ Item 7: &Water Quality Data ...
     Details: {'id': 44, 'type_flags': [], 'state': [], 'text': '&Water Quality Data ...', 'has_submenu': False}

Top Level Menu 2: &Run
Details: {'id': 526699, 'type_flags': [], 'state': [], 'text': '&Run', 'has_submenu': True}
Contains 9 items:
  └─ Item 0: &Steady Flow Analysis ...
     Details: {'id': 46, 'type_flags': [], 'state': [], 'text': '&Steady Flow Analysis ...', 'has_submenu': False}
  └─ Item 1: &Unsteady Flow Analysis ...
     Details: {'id': 47, 'type_flags': [], 'state': [], 'text': '&Unsteady Flow Analysis ...', 'has_submenu': False}
  └─ Item 2: Quasi-Unsteady Analysis (Sediment)...
     Details: {'id': 48, 'type_flags': [], 'state': [], 'text': 'Quasi-Unsteady Analysis (Sediment)...', 'has_submenu': False}
  └─ Item 3: Water Quality Analysis ...
     Details: {'id': 49, 'type_flags': [], 'state': [], 'text': 'Water Quality Analysis ...', 'has_submenu': False}
  └─ Item 4: &Hydraulic Design Functions ...
     Details: {'id': 50, 'type_flags': [], 'state': [], 'text': '&Hydraulic Design Functions ...', 'has_submenu': False}
  └─ Item 5: 
     Details: {'id': 51, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 6: Run Multiple Plans ...
     Details: {'id': 52, 'type_flags': [], 'state': [], 'text': 'Run Multiple Plans ...', 'has_submenu': False}
  └─ Item 7: 
     Details: {'id': 54, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 8: Uncertainty Analysis
     Details: {'id': 592205, 'type_flags': [], 'state': ['DISABLED', 'GRAYED'], 'text': 'Uncertainty Analysis', 'has_submenu': True}
     Has submenu with 3 items:
       └─ Sub-item 0: Setup Parameters ...
          Details: {'id': 56, 'type_flags': [], 'state': [], 'text': 'Setup Parameters ...', 'has_submenu': False}
       └─ Sub-item 1: Run Monte Carlo Analysis ...
          Details: {'id': 57, 'type_flags': [], 'state': [], 'text': 'Run Monte Carlo Analysis ...', 'has_submenu': False}
       └─ Sub-item 2: Summarize Results
          Details: {'id': 58, 'type_flags': [], 'state': [], 'text': 'Summarize Results', 'has_submenu': False}

Top Level Menu 3: &View
Details: {'id': 723171, 'type_flags': [], 'state': [], 'text': '&View', 'has_submenu': True}
Contains 23 items:
  └─ Item 0: &Cross-Sections ...
     Details: {'id': 61, 'type_flags': [], 'state': [], 'text': '&Cross-Sections ...', 'has_submenu': False}
  └─ Item 1: &Water Surface Profiles ...
     Details: {'id': 62, 'type_flags': [], 'state': [], 'text': '&Water Surface Profiles ...', 'has_submenu': False}
  └─ Item 2: &General Profile Plot ...
     Details: {'id': 63, 'type_flags': [], 'state': [], 'text': '&General Profile Plot ...', 'has_submenu': False}
  └─ Item 3: &Rating Curves ...
     Details: {'id': 64, 'type_flags': [], 'state': [], 'text': '&Rating Curves ...', 'has_submenu': False}
  └─ Item 4: 3D View ...
     Details: {'id': 65, 'type_flags': [], 'state': [], 'text': '3D View ...', 'has_submenu': False}
  └─ Item 5: &X-Y-Z Perspective Plots (Classic) ...
     Details: {'id': 66, 'type_flags': [], 'state': [], 'text': '&X-Y-Z Perspective Plots (Classic) ...', 'has_submenu': False}
  └─ Item 6: &Stage and Flow Hydrographs ...
     Details: {'id': 67, 'type_flags': [], 'state': [], 'text': '&Stage and Flow Hydrographs ...', 'has_submenu': False}
  └─ Item 7: &Hydraulic Property Tables ...
     Details: {'id': 68, 'type_flags': [], 'state': [], 'text': '&Hydraulic Property Tables ...', 'has_submenu': False}
  └─ Item 8: 
     Details: {'id': 69, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 9: &Detailed Output Tables ...
     Details: {'id': 70, 'type_flags': [], 'state': [], 'text': '&Detailed Output Tables ...', 'has_submenu': False}
  ... and 13 more items

Top Level Menu 4: &Options
Details: {'id': 591991, 'type_flags': [], 'state': [], 'text': '&Options', 'has_submenu': True}
Contains 5 items:
  └─ Item 0: &Program Setup
     Details: {'id': 657447, 'type_flags': [], 'state': [], 'text': '&Program Setup', 'has_submenu': True}
     Has submenu with 6 items:
       └─ Sub-item 0: Default File &Viewer ...
          Details: {'id': 91, 'type_flags': [], 'state': [], 'text': 'Default File &Viewer ...', 'has_submenu': False}
       └─ Sub-item 1: Default Project Folder ...
          Details: {'id': 92, 'type_flags': [], 'state': [], 'text': 'Default Project Folder ...', 'has_submenu': False}
       └─ Sub-item 2: &Open last project on startup
          Details: {'id': 93, 'type_flags': [], 'state': [], 'text': '&Open last project on startup', 'has_submenu': False}
       └─ Sub-item 3: Automatically Backup Data
          Details: {'id': 94, 'type_flags': [], 'state': ['CHECKED'], 'text': 'Automatically Backup Data', 'has_submenu': False}
       └─ Sub-item 4: Set Time for Automatic Backup ...
          Details: {'id': 95, 'type_flags': [], 'state': [], 'text': 'Set Time for Automatic Backup ...', 'has_submenu': False}
  └─ Item 1: &Default Parameters
     Details: {'id': 788529, 'type_flags': [], 'state': [], 'text': '&Default Parameters', 'has_submenu': True}
     Has submenu with 1 items:
       └─ Sub-item 0: &Expansion and Contraction Coef ...
          Details: {'id': 98, 'type_flags': [], 'state': [], 'text': '&Expansion and Contraction Coef ...', 'has_submenu': False}
  └─ Item 2: &Unit system (US Customary/SI) ...
     Details: {'id': 99, 'type_flags': [], 'state': [], 'text': '&Unit system (US Customary/SI) ...', 'has_submenu': False}
  └─ Item 3: &Convert Project Units ...
     Details: {'id': 100, 'type_flags': [], 'state': [], 'text': '&Convert Project Units ...', 'has_submenu': False}
  └─ Item 4: Convert &Horizontal Coordinate Systems ...
     Details: {'id': 101, 'type_flags': [], 'state': [], 'text': 'Convert &Horizontal Coordinate Systems ...', 'has_submenu': False}

Top Level Menu 5: &GIS Tools
Details: {'id': 592241, 'type_flags': [], 'state': [], 'text': '&GIS Tools', 'has_submenu': True}
Contains 1 items:
  └─ Item 0: RAS Mapper ...
     Details: {'id': 103, 'type_flags': [], 'state': [], 'text': 'RAS Mapper ...', 'has_submenu': False}

Top Level Menu 6: &Help
Details: {'id': 1376653, 'type_flags': [], 'state': [], 'text': '&Help', 'has_submenu': True}
Contains 21 items:
  └─ Item 0: HEC-RAS Online Documentation ...   F1
     Details: {'id': 105, 'type_flags': [], 'state': [], 'text': 'HEC-RAS Online Documentation ...\tF1', 'has_submenu': False}
  └─ Item 1: Release Notes ...
     Details: {'id': 106, 'type_flags': [], 'state': [], 'text': 'Release Notes ...', 'has_submenu': False}
  └─ Item 2: Known Issues ...
     Details: {'id': 107, 'type_flags': [], 'state': [], 'text': 'Known Issues ...', 'has_submenu': False}
  └─ Item 3: Community Support on Discourse ...
     Details: {'id': 108, 'type_flags': [], 'state': [], 'text': 'Community Support on Discourse ...', 'has_submenu': False}
  └─ Item 4: 
     Details: {'id': 109, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 5: Users Manual ...
     Details: {'id': 110, 'type_flags': [], 'state': [], 'text': 'Users Manual ...', 'has_submenu': False}
  └─ Item 6: 2D Modeling User's Manual …
     Details: {'id': 111, 'type_flags': [], 'state': [], 'text': "2D Modeling User's Manual …", 'has_submenu': False}
  └─ Item 7: RAS Mapper User's Manual …
     Details: {'id': 112, 'type_flags': [], 'state': [], 'text': "RAS Mapper User's Manual …", 'has_submenu': False}
  └─ Item 8: Hydraulic Reference …
     Details: {'id': 113, 'type_flags': [], 'state': [], 'text': 'Hydraulic Reference …', 'has_submenu': False}
  └─ Item 9: Applications Guide …
     Details: {'id': 114, 'type_flags': [], 'state': [], 'text': 'Applications Guide …', 'has_submenu': False}
  ... and 11 more items

================================================================================
Performing detailed window hierarchy enumeration...
Main window: HEC-RAS 7.0
Window Handle: 591894
Class: ThunderRT6FormDC
Text: HEC-RAS 7.0
Position: (68, 101, 918, 317)
Style: 0x16CA0000
Extended Style: 0x00040100
Children: 36 found
---
  Window Handle: 591896
  Class: ThunderRT6Timer
  Text: 
  Position: (199, 391, 199, 391)
  Style: 0x44010000
  Extended Style: 0x00000004
---
  Window Handle: 526340
  Class: ThunderRT6TextBox
  Text: 
  Position: (827, 155, 852, 172)
  Style: 0x440100C0
  Extended Style: 0x00000204
---
  Window Handle: 526344
  Class: ThunderRT6CheckBox
  Text: 
  Position: (623, 151, 649, 177)
  Style: 0x5401000B
  Extended Style: 0x00000004
---
  Window Handle: 461070
  Class: ThunderRT6TextBox
  Text: 
  Position: (795, 155, 824, 172)
  Style: 0x440100C0
  Extended Style: 0x00000204
---
  Window Handle: 526622
  Class: ThunderRT6Timer
  Text: 
  Position: (171, 391, 171, 391)
  Style: 0x44010000
  Extended Style: 0x00000004
... and 31 more children

================================================================================
Detailed enumeration complete.
Python
def enumerate_menu_item_details(menu_handle, item_index):
    """Get detailed information about a menu item"""
    # Get menu item ID
    menu_id = win32gui.GetMenuItemID(menu_handle, item_index)

    # Get menu state
    menu_state = win32gui.GetMenuState(menu_handle, item_index, win32con.MF_BYPOSITION)

    # Parse state flags
    state_flags = []
    if menu_state & win32con.MF_CHECKED:
        state_flags.append("CHECKED")
    if menu_state & win32con.MF_DISABLED:
        state_flags.append("DISABLED")
    if menu_state & win32con.MF_GRAYED:
        state_flags.append("GRAYED")
    if menu_state & win32con.MF_SEPARATOR:
        state_flags.append("SEPARATOR")

    # Get menu text
    text = get_menu_string(menu_handle, item_index)

    return {
        "id": menu_id if menu_id != -1 else None,
        "state": state_flags,
        "text": text,
        "has_submenu": win32gui.GetSubMenu(menu_handle, item_index) is not None
    }
Python
# ==============================================================================
# Part 2: Interacting with 64-bit Processes using pywinauto
# ==============================================================================
print("\n--- pywinauto Example ---")
print("This section shows how to inspect an application like RAS Mapper using pywinauto.")
print("You may need to install it first: !pip install pywinauto")

# Handle optional dependency with specific ImportError
try:
    import pywinauto
    from pywinauto import timings, findwindows
except ImportError:
    print("\npywinauto is not installed. Please install it to run this example:")
    print("In a new cell, run: !pip install pywinauto")
    raise

print("pywinauto is installed.")

print("\nAttempting to find a RAS Mapper window to demonstrate pywinauto...")

# Handle expected case where RAS Mapper may not be running
try:
    # Connect to RAS Mapper window (title might vary slightly by version)
    # Using win32 backend as RAS Mapper is a mix of technologies
    app = pywinauto.Application(backend="win32").connect(title_re=".*RAS Mapper.*", timeout=5)

    ras_mapper_window = app.window(title_re=".*RAS Mapper.*")

    print("\nSuccessfully connected to RAS Mapper!")
    print("Now, listing all its controls using pywinauto's print_control_identifiers():")

    # This will print a tree of all interactable controls.
    # It's a very useful function for exploration.
    ras_mapper_window.print_control_identifiers()

except (findwindows.ElementNotFoundError, timings.TimeoutError):
    # This is expected when RAS Mapper is not running - provide guidance
    print("\nCould not find a running RAS Mapper window.")
    print("To run this for real, open HEC-RAS and then RAS Mapper, then execute this cell again.")
    print("Example code to list controls once connected:")
    print("  from pywinauto import Application")
    print("  app = Application(backend='uia').connect(title_re='.*RAS Mapper.*') # uia backend might also work")
    print("  ras_mapper_window = app.window(title_re='.*RAS Mapper.*')")
    print("  ras_mapper_window.print_control_identifiers()")
Text Only
--- pywinauto Example ---
This section shows how to inspect an application like RAS Mapper using pywinauto.
You may need to install it first: !pip install pywinauto


pywinauto is installed.

Attempting to find a RAS Mapper window to demonstrate pywinauto...



Could not find a running RAS Mapper window.
To run this for real, open HEC-RAS and then RAS Mapper, then execute this cell again.
Example code to list controls once connected:
  from pywinauto import Application
  app = Application(backend='uia').connect(title_re='.*RAS Mapper.*') # uia backend might also work
  ras_mapper_window = app.window(title_re='.*RAS Mapper.*')
  ras_mapper_window.print_control_identifiers()

RasGuiAutomation Library Functions

The exploration above shows the low-level Win32 API techniques used to automate HEC-RAS. The ras-commander library encapsulates these patterns in the RasGuiAutomation class, providing high-level functions for common automation tasks:

Available Functions

Function Purpose
open_rasmapper() Opens RASMapper via GIS Tools menu, waits for user
run_unsteady_gui() Opens Unsteady Flow Analysis dialog and starts computation
handle_already_running_dialog() Dismisses "already running" dialog when launching HEC-RAS

Dialog Handling

When automating HEC-RAS, a common challenge is handling the "already running" dialog that appears when launching HEC-RAS while another instance is open. The library provides handle_already_running_dialog() to automatically dismiss this dialog.

Dialog Detection Approach

The function uses these Win32 techniques (as explored above): 1. Window enumeration - Find dialogs with class #32770 (standard Windows dialog) 2. Text matching - Check for keywords like "already" or "running" in the dialog text 3. Button clicking - Send WM_COMMAND to click "Yes" button

Python
# =============================================================================
# Example: Using RasGuiAutomation Library Functions
# =============================================================================
# Instead of writing all the Win32 code manually (as shown above), use the
# library functions for common automation tasks.

from ras_commander import RasGuiAutomation

# Example 1: Handle "already running" dialog
# ------------------------------------------
# Call this shortly after launching HEC-RAS to dismiss the dialog if it appears.
# Returns True if dialog was found and dismissed, False otherwise.
#
# dialog_found = RasGuiAutomation.handle_already_running_dialog(timeout=5)
# if dialog_found:
#     print("Dismissed 'already running' dialog")
# else:
#     print("No dialog appeared (HEC-RAS started normally)")

# Example 2: Open RASMapper
# -------------------------
# Opens HEC-RAS with the current project, navigates to GIS Tools > RAS Mapper,
# and waits for user to close RASMapper. Useful for .rasmap upgrades.
#
# success = RasGuiAutomation.open_rasmapper(wait_for_user=True, timeout=300)
# if success:
#     print("RASMapper closed successfully")

# Example 3: Run Unsteady Flow Analysis via GUI
# ---------------------------------------------
# Opens the Unsteady Flow Analysis dialog and starts computation.
# Used internally by RasMap.postprocess_stored_maps() for floodplain mapping.
#
# success = RasGuiAutomation.run_unsteady_gui(plan_number="06", wait_for_user=True)
# if success:
#     print("Computation completed")

print("RasGuiAutomation functions available:")
print("  - handle_already_running_dialog(timeout=5)")
print("  - open_rasmapper(wait_for_user=True, timeout=300)")
print("  - run_unsteady_gui(plan_number, wait_for_user=True)")
print("\nSee examples/15_stored_map_generation.ipynb for real usage.")
Text Only
RasGuiAutomation functions available:
  - handle_already_running_dialog(timeout=5)
  - open_rasmapper(wait_for_user=True, timeout=300)
  - run_unsteady_gui(plan_number, wait_for_user=True)

See examples/15_stored_map_generation.ipynb for real usage.
Python
# ==============================================================================
# Part 1b: Deeper Menu and Window Object Enumeration using ctypes
# ==============================================================================

import win32gui
import win32con
import win32api
import win32process
import ctypes
from ctypes import wintypes

# Define MENUITEMINFO structure using ctypes
class MENUITEMINFO(ctypes.Structure):
    _fields_ = [
        ("cbSize", wintypes.UINT),
        ("fMask", wintypes.UINT),
        ("fType", wintypes.UINT),
        ("fState", wintypes.UINT),
        ("wID", wintypes.UINT),
        ("hSubMenu", wintypes.HMENU),
        ("hbmpChecked", wintypes.HBITMAP),
        ("hbmpUnchecked", wintypes.HBITMAP),
        ("dwItemData", ctypes.POINTER(ctypes.c_ulong)),
        ("dwTypeData", wintypes.LPWSTR),
        ("cch", wintypes.UINT),
        ("hbmpItem", wintypes.HBITMAP)
    ]

def get_menu_string(menu_handle, pos):
    """Get menu item string at position"""
    buf_size = 256
    buf = ctypes.create_unicode_buffer(buf_size)
    user32 = ctypes.windll.user32
    result = user32.GetMenuStringW(menu_handle, pos, buf, buf_size, MF_BYPOSITION)
    if result:
        return buf.value
    return ""

def enumerate_menu_item_details(menu_handle, item_index):
    """Get detailed information about a menu item using ctypes"""
    # Create and initialize MENUITEMINFO structure
    mii = MENUITEMINFO()
    mii.cbSize = ctypes.sizeof(MENUITEMINFO)
    mii.fMask = win32con.MIIM_STATE | win32con.MIIM_ID | win32con.MIIM_TYPE | win32con.MIIM_SUBMENU

    # Call GetMenuItemInfo using ctypes
    user32 = ctypes.windll.user32
    result = user32.GetMenuItemInfoW(
        menu_handle, 
        item_index, 
        True,  # fByPosition
        ctypes.byref(mii)
    )

    if result:
        # Parse state flags
        state_flags = []
        if mii.fState & win32con.MFS_CHECKED:
            state_flags.append("CHECKED")
        if mii.fState & win32con.MFS_DISABLED:
            state_flags.append("DISABLED")
        if mii.fState & win32con.MFS_GRAYED:
            state_flags.append("GRAYED")
        if mii.fState & win32con.MFS_HILITE:
            state_flags.append("HIGHLIGHTED")
        if mii.fState & win32con.MFS_DEFAULT:
            state_flags.append("DEFAULT")

        # Parse type flags
        type_flags = []
        if mii.fType & win32con.MFT_STRING:
            type_flags.append("STRING")
        if mii.fType & win32con.MFT_SEPARATOR:
            type_flags.append("SEPARATOR")
        if mii.fType & win32con.MFT_BITMAP:
            type_flags.append("BITMAP")
        if mii.fType & win32con.MFT_OWNERDRAW:
            type_flags.append("OWNERDRAW")

        return {
            "id": mii.wID,
            "type_flags": type_flags,
            "state": state_flags,
            "text": get_menu_string(menu_handle, item_index),
            "has_submenu": bool(mii.hSubMenu)
        }
    else:
        # Fallback to simpler approach if GetMenuItemInfo fails
        menu_id = win32gui.GetMenuItemID(menu_handle, item_index)
        menu_state = win32gui.GetMenuState(menu_handle, item_index, win32con.MF_BYPOSITION)

        state_flags = []
        if menu_state & win32con.MF_CHECKED:
            state_flags.append("CHECKED")
        if menu_state & win32con.MF_DISABLED:
            state_flags.append("DISABLED")
        if menu_state & win32con.MF_GRAYED:
            state_flags.append("GRAYED")
        if menu_state & win32con.MF_SEPARATOR:
            state_flags.append("SEPARATOR")

        return {
            "id": menu_id if menu_id != -1 else None,
            "state": state_flags,
            "text": get_menu_string(menu_handle, item_index),
            "has_submenu": win32gui.GetSubMenu(menu_handle, item_index) is not None,
            "fallback": True
        }

def enumerate_window_details(hwnd, indent=0):
    """Recursively enumerate window details including styles and extended styles"""
    if not hwnd:
        return

    # Get basic window info
    class_name = win32gui.GetClassName(hwnd)
    window_text = win32gui.GetWindowText(hwnd)

    # Get window styles
    style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
    ex_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)

    # Get window metrics
    rect = win32gui.GetWindowRect(hwnd)

    # Print window details
    indent_str = "  " * indent
    print(f"{indent_str}Window Handle: {hwnd}")
    print(f"{indent_str}Class: {class_name}")
    print(f"{indent_str}Text: {window_text}")
    print(f"{indent_str}Position: {rect}")
    print(f"{indent_str}Style: 0x{style:08X}")
    print(f"{indent_str}Extended Style: 0x{ex_style:08X}")

    # Enumerate child windows recursively (limit depth to avoid too much output)
    if indent < 3:  # Limit recursion depth
        child_windows = []
        win32gui.EnumChildWindows(
            hwnd,
            lambda child_hwnd, windows: windows.append(child_hwnd) or True,
            child_windows
        )

        if child_windows:
            print(f"{indent_str}Children: {len(child_windows)} found")
            for child_hwnd in child_windows[:5]:  # Show first 5 children
                print(f"{indent_str}---")
                enumerate_window_details(child_hwnd, indent + 1)
            if len(child_windows) > 5:
                print(f"{indent_str}... and {len(child_windows) - 5} more children")

def enumerate_full_menu_tree(hwnd):
    """Enumerate complete menu tree with all available details"""
    menu_bar = win32gui.GetMenu(hwnd)
    if not menu_bar:
        print("No menu bar found")
        return

    print("\n=== Complete Menu Tree Analysis ===\n")

    menu_count = win32gui.GetMenuItemCount(menu_bar)
    for i in range(menu_count):
        menu_text = get_menu_string(menu_bar, i)
        menu_details = enumerate_menu_item_details(menu_bar, i)
        print(f"\nTop Level Menu {i}: {menu_text}")
        print(f"Details: {menu_details}")

        submenu = win32gui.GetSubMenu(menu_bar, i)
        if submenu:
            submenu_count = win32gui.GetMenuItemCount(submenu)
            print(f"Contains {submenu_count} items:")

            for j in range(min(submenu_count, 10)):  # Show first 10 items
                submenu_text = get_menu_string(submenu, j)
                submenu_details = enumerate_menu_item_details(submenu, j)
                print(f"  └─ Item {j}: {submenu_text}")
                print(f"     Details: {submenu_details}")

                # Check for sub-submenus
                sub_submenu = win32gui.GetSubMenu(submenu, j)
                if sub_submenu:
                    sub_count = win32gui.GetMenuItemCount(sub_submenu)
                    print(f"     Has submenu with {sub_count} items:")
                    for k in range(min(sub_count, 5)):  # Show first 5 sub-items
                        sub_text = get_menu_string(sub_submenu, k)
                        sub_details = enumerate_menu_item_details(sub_submenu, k)
                        print(f"       └─ Sub-item {k}: {sub_text}")
                        print(f"          Details: {sub_details}")

            if submenu_count > 10:
                print(f"  ... and {submenu_count - 10} more items")

# Main execution
if 'hecras_pid' in globals() and hecras_pid is not None:
    # Wait for windows to be ready
    import time
    time.sleep(1)

    # Find HEC-RAS windows
    windows = get_windows_by_pid(hecras_pid)
    hec_ras_hwnd, title = find_main_hecras_window(windows)

    if hec_ras_hwnd:
        print("\n" + "="*80)
        print("Performing detailed menu enumeration...")
        enumerate_full_menu_tree(hec_ras_hwnd)

        print("\n" + "="*80)
        print("Performing detailed window hierarchy enumeration...")
        print(f"Main window: {title}")
        enumerate_window_details(hec_ras_hwnd)

        print("\n" + "="*80)
        print("Detailed enumeration complete.")
    else:
        print("Could not find main HEC-RAS window")
else:
    print("HEC-RAS process ID not found. Please run the cell that launches HEC-RAS first.")
Text Only
================================================================================
Performing detailed menu enumeration...

=== Complete Menu Tree Analysis ===


Top Level Menu 0: &File
Details: {'id': 1509301, 'type_flags': [], 'state': [], 'text': '&File', 'has_submenu': True}
Contains 25 items:
  └─ Item 0: &New Project ...
     Details: {'id': 2, 'type_flags': [], 'state': [], 'text': '&New Project ...', 'has_submenu': False}
  └─ Item 1: &Open Project ...
     Details: {'id': 3, 'type_flags': [], 'state': [], 'text': '&Open Project ...', 'has_submenu': False}
  └─ Item 2: &Save Project
     Details: {'id': 4, 'type_flags': [], 'state': [], 'text': '&Save Project', 'has_submenu': False}
  └─ Item 3: Save Project &As ...
     Details: {'id': 5, 'type_flags': [], 'state': [], 'text': 'Save Project &As ...', 'has_submenu': False}
  └─ Item 4: &Rename Project Title ...
     Details: {'id': 6, 'type_flags': [], 'state': [], 'text': '&Rename Project Title ...', 'has_submenu': False}
  └─ Item 5: &Delete Project ...
     Details: {'id': 7, 'type_flags': [], 'state': [], 'text': '&Delete Project ...', 'has_submenu': False}
  └─ Item 6: 
     Details: {'id': 8, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 7: &Project Summary ...
     Details: {'id': 9, 'type_flags': [], 'state': [], 'text': '&Project Summary ...', 'has_submenu': False}
  └─ Item 8: Compare Model Data ...
     Details: {'id': 10, 'type_flags': [], 'state': [], 'text': 'Compare Model Data ...', 'has_submenu': False}
  └─ Item 9: 
     Details: {'id': 11, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  ... and 15 more items

Top Level Menu 1: &Edit
Details: {'id': 657759, 'type_flags': [], 'state': [], 'text': '&Edit', 'has_submenu': True}
Contains 8 items:
  └─ Item 0: &Geometric Data ...
     Details: {'id': 37, 'type_flags': [], 'state': [], 'text': '&Geometric Data ...', 'has_submenu': False}
  └─ Item 1: 
     Details: {'id': 38, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 2: &Steady Flow Data ...
     Details: {'id': 39, 'type_flags': [], 'state': [], 'text': '&Steady Flow Data ...', 'has_submenu': False}
  └─ Item 3: &Quasi Unsteady Flow (Sediment) ...
     Details: {'id': 40, 'type_flags': [], 'state': [], 'text': '&Quasi Unsteady Flow (Sediment) ...', 'has_submenu': False}
  └─ Item 4: &Unsteady Flow Data ...
     Details: {'id': 41, 'type_flags': [], 'state': [], 'text': '&Unsteady Flow Data ...', 'has_submenu': False}
  └─ Item 5: 
     Details: {'id': 42, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 6: Se&diment Data ...
     Details: {'id': 43, 'type_flags': [], 'state': [], 'text': 'Se&diment Data ...', 'has_submenu': False}
  └─ Item 7: &Water Quality Data ...
     Details: {'id': 44, 'type_flags': [], 'state': [], 'text': '&Water Quality Data ...', 'has_submenu': False}

Top Level Menu 2: &Run
Details: {'id': 526699, 'type_flags': [], 'state': [], 'text': '&Run', 'has_submenu': True}
Contains 9 items:
  └─ Item 0: &Steady Flow Analysis ...
     Details: {'id': 46, 'type_flags': [], 'state': [], 'text': '&Steady Flow Analysis ...', 'has_submenu': False}
  └─ Item 1: &Unsteady Flow Analysis ...
     Details: {'id': 47, 'type_flags': [], 'state': [], 'text': '&Unsteady Flow Analysis ...', 'has_submenu': False}
  └─ Item 2: Quasi-Unsteady Analysis (Sediment)...
     Details: {'id': 48, 'type_flags': [], 'state': [], 'text': 'Quasi-Unsteady Analysis (Sediment)...', 'has_submenu': False}
  └─ Item 3: Water Quality Analysis ...
     Details: {'id': 49, 'type_flags': [], 'state': [], 'text': 'Water Quality Analysis ...', 'has_submenu': False}
  └─ Item 4: &Hydraulic Design Functions ...
     Details: {'id': 50, 'type_flags': [], 'state': [], 'text': '&Hydraulic Design Functions ...', 'has_submenu': False}
  └─ Item 5: 
     Details: {'id': 51, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 6: Run Multiple Plans ...
     Details: {'id': 52, 'type_flags': [], 'state': [], 'text': 'Run Multiple Plans ...', 'has_submenu': False}
  └─ Item 7: 
     Details: {'id': 54, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 8: Uncertainty Analysis
     Details: {'id': 592205, 'type_flags': [], 'state': ['DISABLED', 'GRAYED'], 'text': 'Uncertainty Analysis', 'has_submenu': True}
     Has submenu with 3 items:
       └─ Sub-item 0: Setup Parameters ...
          Details: {'id': 56, 'type_flags': [], 'state': [], 'text': 'Setup Parameters ...', 'has_submenu': False}
       └─ Sub-item 1: Run Monte Carlo Analysis ...
          Details: {'id': 57, 'type_flags': [], 'state': [], 'text': 'Run Monte Carlo Analysis ...', 'has_submenu': False}
       └─ Sub-item 2: Summarize Results
          Details: {'id': 58, 'type_flags': [], 'state': [], 'text': 'Summarize Results', 'has_submenu': False}

Top Level Menu 3: &View
Details: {'id': 723171, 'type_flags': [], 'state': [], 'text': '&View', 'has_submenu': True}
Contains 23 items:
  └─ Item 0: &Cross-Sections ...
     Details: {'id': 61, 'type_flags': [], 'state': [], 'text': '&Cross-Sections ...', 'has_submenu': False}
  └─ Item 1: &Water Surface Profiles ...
     Details: {'id': 62, 'type_flags': [], 'state': [], 'text': '&Water Surface Profiles ...', 'has_submenu': False}
  └─ Item 2: &General Profile Plot ...
     Details: {'id': 63, 'type_flags': [], 'state': [], 'text': '&General Profile Plot ...', 'has_submenu': False}
  └─ Item 3: &Rating Curves ...
     Details: {'id': 64, 'type_flags': [], 'state': [], 'text': '&Rating Curves ...', 'has_submenu': False}
  └─ Item 4: 3D View ...
     Details: {'id': 65, 'type_flags': [], 'state': [], 'text': '3D View ...', 'has_submenu': False}
  └─ Item 5: &X-Y-Z Perspective Plots (Classic) ...
     Details: {'id': 66, 'type_flags': [], 'state': [], 'text': '&X-Y-Z Perspective Plots (Classic) ...', 'has_submenu': False}
  └─ Item 6: &Stage and Flow Hydrographs ...
     Details: {'id': 67, 'type_flags': [], 'state': [], 'text': '&Stage and Flow Hydrographs ...', 'has_submenu': False}
  └─ Item 7: &Hydraulic Property Tables ...
     Details: {'id': 68, 'type_flags': [], 'state': [], 'text': '&Hydraulic Property Tables ...', 'has_submenu': False}
  └─ Item 8: 
     Details: {'id': 69, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 9: &Detailed Output Tables ...
     Details: {'id': 70, 'type_flags': [], 'state': [], 'text': '&Detailed Output Tables ...', 'has_submenu': False}
  ... and 13 more items

Top Level Menu 4: &Options
Details: {'id': 591991, 'type_flags': [], 'state': [], 'text': '&Options', 'has_submenu': True}
Contains 5 items:
  └─ Item 0: &Program Setup
     Details: {'id': 657447, 'type_flags': [], 'state': [], 'text': '&Program Setup', 'has_submenu': True}
     Has submenu with 6 items:
       └─ Sub-item 0: Default File &Viewer ...
          Details: {'id': 91, 'type_flags': [], 'state': [], 'text': 'Default File &Viewer ...', 'has_submenu': False}
       └─ Sub-item 1: Default Project Folder ...
          Details: {'id': 92, 'type_flags': [], 'state': [], 'text': 'Default Project Folder ...', 'has_submenu': False}
       └─ Sub-item 2: &Open last project on startup
          Details: {'id': 93, 'type_flags': [], 'state': [], 'text': '&Open last project on startup', 'has_submenu': False}
       └─ Sub-item 3: Automatically Backup Data
          Details: {'id': 94, 'type_flags': [], 'state': ['CHECKED'], 'text': 'Automatically Backup Data', 'has_submenu': False}
       └─ Sub-item 4: Set Time for Automatic Backup ...
          Details: {'id': 95, 'type_flags': [], 'state': [], 'text': 'Set Time for Automatic Backup ...', 'has_submenu': False}
  └─ Item 1: &Default Parameters
     Details: {'id': 788529, 'type_flags': [], 'state': [], 'text': '&Default Parameters', 'has_submenu': True}
     Has submenu with 1 items:
       └─ Sub-item 0: &Expansion and Contraction Coef ...
          Details: {'id': 98, 'type_flags': [], 'state': [], 'text': '&Expansion and Contraction Coef ...', 'has_submenu': False}
  └─ Item 2: &Unit system (US Customary/SI) ...
     Details: {'id': 99, 'type_flags': [], 'state': [], 'text': '&Unit system (US Customary/SI) ...', 'has_submenu': False}
  └─ Item 3: &Convert Project Units ...
     Details: {'id': 100, 'type_flags': [], 'state': [], 'text': '&Convert Project Units ...', 'has_submenu': False}
  └─ Item 4: Convert &Horizontal Coordinate Systems ...
     Details: {'id': 101, 'type_flags': [], 'state': [], 'text': 'Convert &Horizontal Coordinate Systems ...', 'has_submenu': False}

Top Level Menu 5: &GIS Tools
Details: {'id': 592241, 'type_flags': [], 'state': [], 'text': '&GIS Tools', 'has_submenu': True}
Contains 1 items:
  └─ Item 0: RAS Mapper ...
     Details: {'id': 103, 'type_flags': [], 'state': [], 'text': 'RAS Mapper ...', 'has_submenu': False}

Top Level Menu 6: &Help
Details: {'id': 1376653, 'type_flags': [], 'state': [], 'text': '&Help', 'has_submenu': True}
Contains 21 items:
  └─ Item 0: HEC-RAS Online Documentation ...   F1
     Details: {'id': 105, 'type_flags': [], 'state': [], 'text': 'HEC-RAS Online Documentation ...\tF1', 'has_submenu': False}
  └─ Item 1: Release Notes ...
     Details: {'id': 106, 'type_flags': [], 'state': [], 'text': 'Release Notes ...', 'has_submenu': False}
  └─ Item 2: Known Issues ...
     Details: {'id': 107, 'type_flags': [], 'state': [], 'text': 'Known Issues ...', 'has_submenu': False}
  └─ Item 3: Community Support on Discourse ...
     Details: {'id': 108, 'type_flags': [], 'state': [], 'text': 'Community Support on Discourse ...', 'has_submenu': False}
  └─ Item 4: 
     Details: {'id': 109, 'type_flags': ['SEPARATOR'], 'state': ['DISABLED', 'GRAYED'], 'text': '', 'has_submenu': False}
  └─ Item 5: Users Manual ...
     Details: {'id': 110, 'type_flags': [], 'state': [], 'text': 'Users Manual ...', 'has_submenu': False}
  └─ Item 6: 2D Modeling User's Manual …
     Details: {'id': 111, 'type_flags': [], 'state': [], 'text': "2D Modeling User's Manual …", 'has_submenu': False}
  └─ Item 7: RAS Mapper User's Manual …
     Details: {'id': 112, 'type_flags': [], 'state': [], 'text': "RAS Mapper User's Manual …", 'has_submenu': False}
  └─ Item 8: Hydraulic Reference …
     Details: {'id': 113, 'type_flags': [], 'state': [], 'text': 'Hydraulic Reference …', 'has_submenu': False}
  └─ Item 9: Applications Guide …
     Details: {'id': 114, 'type_flags': [], 'state': [], 'text': 'Applications Guide …', 'has_submenu': False}
  ... and 11 more items

================================================================================
Performing detailed window hierarchy enumeration...
Main window: HEC-RAS 7.0
Window Handle: 591894
Class: ThunderRT6FormDC
Text: HEC-RAS 7.0
Position: (68, 101, 918, 317)
Style: 0x16CA0000
Extended Style: 0x00040100
Children: 36 found
---
  Window Handle: 591896
  Class: ThunderRT6Timer
  Text: 
  Position: (199, 391, 199, 391)
  Style: 0x44010000
  Extended Style: 0x00000004
---
  Window Handle: 526340
  Class: ThunderRT6TextBox
  Text: 
  Position: (827, 155, 852, 172)
  Style: 0x440100C0
  Extended Style: 0x00000204
---
  Window Handle: 526344
  Class: ThunderRT6CheckBox
  Text: 
  Position: (623, 151, 649, 177)
  Style: 0x5401000B
  Extended Style: 0x00000004
---
  Window Handle: 461070
  Class: ThunderRT6TextBox
  Text: 
  Position: (795, 155, 824, 172)
  Style: 0x440100C0
  Extended Style: 0x00000204
---
  Window Handle: 526622
  Class: ThunderRT6Timer
  Text: 
  Position: (171, 391, 171, 391)
  Style: 0x44010000
  Extended Style: 0x00000004
... and 31 more children

================================================================================
Detailed enumeration complete.

GUI Automation Best Practices

Prefer API Over GUI

Decision Tree:

Text Only
Need to automate HEC-RAS task?
  ├─ Is there a ras-commander API method?
  │  ├─ YES → Use API (RasCmdr, RasPlan, etc.)
  │  └─ NO → Continue...
  ├─ Is there a COM interface method? (HEC-RAS 5.x)
  │  ├─ YES → Use RasControl class
  │  └─ NO → Continue...
  └─ Must use GUI automation (last resort)
     ├─ Use RasGuiAutomation helpers
     └─ Document why API not available

Making GUI Automation Robust

Pattern 1: Retry Logic

Python
def click_with_retry(element, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            element.click()
            return True
        except Exception as e:
            print(f"Attempt {attempt + 1} failed: {e}")
            time.sleep(1)

    return False

Pattern 2: Graceful Degradation

Python
try:
    # Attempt GUI automation
    result = automate_via_gui()
except Exception as e:
    # Fall back to manual instructions
    print(f"GUI automation failed: {e}")
    print("Please complete this step manually:")
    print("  1. Open HEC-RAS")
    print("  2. Click File → Export → Google Earth")
    print("  3. Save to output folder")
    input("Press Enter when complete...")

Pattern 3: Error Recovery

Python
def safe_gui_automation(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            # Log error
            print(f"GUI automation error: {e}")

            # Try to recover GUI state
            try:
                app = Application().connect(title_re=".*HEC-RAS.*")
                app.window().type_keys("{ESC}")  # Close any dialogs
                app.window().type_keys("{ESC}")
            except:
                pass

            raise

    return wrapper

LLM Forward: Document Manual Steps

When GUI automation is required, document for manual review:

Python
def document_gui_steps(task_description, steps, output_file):
    import json
    from datetime import datetime

    documentation = {
        'timestamp': datetime.now().isoformat(),
        'task': task_description,
        'automation_type': 'GUI (fragile - consider API alternative)',
        'manual_steps': steps,
        'review_instructions': [
            '1. Verify all steps completed successfully',
            '2. Check output files exist',
            '3. Open HEC-RAS GUI to confirm results',
            '4. Document any errors encountered'
        ]
    }

    with open(output_file, 'w') as f:
        json.dump(documentation, f, indent=2)

    print(f"GUI automation documented: {output_file}")

# Usage - save to project folder
document_gui_steps(
    task_description="Export cross sections to CSV",
    steps=[
        "Open HEC-RAS project",
        "Navigate to Geometry → Cross Sections",
        "Click File → Export → CSV",
        "Select output folder",
        "Click Save"
    ],
    output_file=project_path / 'gui_automation_log.json'
)

Resources

  • pywinauto documentation: https://pywinauto.readthedocs.io/
  • win32gui examples: Search for "win32gui python examples"

Future Direction

ras-commander aims to provide API coverage for all common tasks, eliminating need for GUI automation. If you find yourself using GUI automation frequently, please file a GitHub issue requesting API support for that functionality.

This provides the basic building blocks for GUI-driven automations for HEC-RAS without the HECRASController