Win32COM Automation¶
# =============================================================================
# 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__}")
📦 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:
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:
-
Window Not Found: HEC-RAS not running or title changed
Pythonimport 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) -
Timing Issues: GUI not ready for next action
-
Element Not Found: Control identifiers changed
Prerequisites¶
Before running this notebook, ensure you have:
- ras-commander installed:
pip install ras-commander - Python 3.10+: Check with
python --version - HEC-RAS installed: GUI automation requires running application
- pywin32:
pip install pywin32 - pywinauto:
pip install pywinauto(for advanced automation) - 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
Related Notebooks¶
- 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:
# ❌ 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.
# =============================================================================
# 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}")
Project: Balde Eagle Creek
HEC-RAS Version: 7.0
# =============================================================================
# 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}")
Project: <workspace>\examples\example_projects\Balde Eagle Creek_16
HEC-RAS: "<HEC-RAS>\7.0\Ras.exe"
# =============================================================================
# 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...")
============================================================
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...
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)
# 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.")
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.
# ==============================================================================
# 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.")
================================================================================
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.
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
}
# ==============================================================================
# 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()")
--- 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
# =============================================================================
# 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.")
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.
# ==============================================================================
# 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.")
================================================================================
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:
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
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
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
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:
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.