Drive legacy Delphi/VCL and DevExpress Windows desktop apps from Python — by relative control order, with write-and-read-back verification.
Project description
vcldrive
Drive legacy Delphi/VCL and DevExpress Windows desktop apps from Python — by relative control order, with write-and-read-back verification.
Why this exists
Plenty of business runs on old Windows desktop software built in Delphi/VCL with DevExpress controls — ERP, manufacturing, accounting. Automating those apps is unreasonably hard:
- No automation ids. Forms expose several controls of the same class
(for example four
TcxImageComboBoxeditors in a column) with nothing to tell them apart. - Pixel scripts are brittle. Addressing controls by absolute coordinates breaks the moment the window moves or the screen resolution changes.
- Owner-drawn controls are opaque. Many DevExpress editors are not exposed to
UI Automation, so the usual accessibility path returns nothing
(pywinauto #757,
#950). The vendor's
dxUIAutomationEnabledflag would help, but you cannot set it inside a compiled third-party.exe. - Values do not commit. A
TcxCalcEditaccepts a number only after real, per-character key events followed by a focus change. Convenience APIs that set the window text leave the value unposted, which surfaces later as a validation error.
vcldrive packages the techniques that actually work against these forms, so you
do not have to rediscover them. It builds on pywinauto
and adds the missing layer for owner-drawn DevExpress/VCL forms.
What it gives you
| Capability | What it solves |
|---|---|
| Map by relative order | Identify look-alike controls top-to-bottom, left-to-right — resilient to window moves and resolution changes. |
| Row-cluster detection | Skip a band of look-alike fields (a material picker, a grid row) that sits between the inputs you care about. |
| Committing numeric input | Type with real key events and a Tab commit, the only path that posts a TcxCalcEdit. |
| Dropdown by arrows | Open with F4 and step with arrow keys in one session — reliable for long Unicode items. |
| Write-and-read-back | Write, read back, normalise, retry — so a value that did not stick is caught and re-applied. |
| Modal recognition | Read a validation dialog's text and react (re-enter a field, retry). |
| Screenshot as truth | Capture the form, because on-screen state is the only trustworthy confirmation for owner-drawn controls. |
Installation
# Pure logic only (mapping, verification, geometry, dialog matching):
pip install vcldrive
# With the Windows backend, to actually drive an app:
pip install "vcldrive[win32]"
The Windows extra pulls in pywinauto and pywin32. The core package has no
runtime dependencies and imports on any operating system, which keeps the
position logic testable in CI without a desktop.
Quickstart
from vcldrive import Form
# Attach to an already-open window by its class name.
form = Form.connect("TFrmItemEditor")
# Map the column of combo editors to roles by relative order, skipping a
# middle band of 3+ look-alike "material" combos.
combos = form.map_roles(
"TcxImageComboBox",
["anchor", "group", "variant", "color", "side"],
exclude_cluster_min=3,
)
# Pick a group by prefix, then a variant by stepping the open dropdown.
form.combo(combos["group"]).select_by_prefix("Widgets")
form.combo(combos["variant"]).select_by_arrows(7)
# Set the length with committing key events (width follows from the variant).
length = form.map_roles("TcxCalcEdit", ["length", "width"])["length"]
form.numeric(length).set_value(1600)
# Prove it on screen, then commit.
form.screenshot("item_form.png")
form.button("OK").click()
A complete, runnable walkthrough — including the "length is required" retry loop
— is in examples/erp_accessory_form.py.
How it works
Map by relative order, not pixels
map_roles collects every control of a class, sorts them into reading order, and
binds your role names to them one by one. Because it reasons about order rather
than coordinates, the mapping holds when the window moves.
When a form interleaves a band of identical controls between the fields you want,
exclude_cluster_min removes that band first. detect_row_clusters finds any row
where that many same-class controls line up within an 8-pixel tolerance, so roles
on both sides of the band still align.
Commit values the way the editor demands
NumericEdit.set_value focuses the editor, clears it, types each character as a
real key event, and presses Tab. That on-exit commit is what posts the value to a
TcxCalcEdit. ComboBox.select_by_arrows opens the list with F4 and steps with
arrow keys in a single call, because the dropdown closes between separate key
batches.
Trust the screen, then verify
For owner-drawn controls the committed value is not readable as window text, so
Form.screenshot captures ground truth. For controls that are readable,
verify_write writes, reads back, and retries until the value matches — turning a
silent failure into a handled one.
Testing
The pure logic is fully unit-tested with in-memory fakes — no GUI, any OS:
pip install "vcldrive[dev]"
pytest
Compatibility
- Python: 3.10+
- OS: Windows (for driving). Core logic imports anywhere.
- Backend: pywinauto
win32. Bring your own backend by implementing the smallControlHandleprotocol.
Contributing
Issues and pull requests are welcome — especially backend adapters and wrappers for more DevExpress/VCL control classes. Please keep new logic covered by tests.
License
MIT © 2026 Alple
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vcldrive-0.1.0.tar.gz.
File metadata
- Download URL: vcldrive-0.1.0.tar.gz
- Upload date:
- Size: 22.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d35dde78eb7a6a61a67315dae54b4f8fba659870a1fd7e3ac72d5c14c0c1eec
|
|
| MD5 |
459412c81d4a7fffa7b0c27d61975f8b
|
|
| BLAKE2b-256 |
7d27df6bc2b1a1dfa02a74c7d33e09f5583955b3868fe2630b7dedafff2f9b4e
|
File details
Details for the file vcldrive-0.1.0-py3-none-any.whl.
File metadata
- Download URL: vcldrive-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d6fb697f41178d31ae817f1bf3c02e0aafc47e4d4875b6bff2c1a2a5f5ebf8c
|
|
| MD5 |
07b611aa21b262b643554eaea7d9d51a
|
|
| BLAKE2b-256 |
733f8d4cd621fafd144e5ac152931862988f995ab193be1870e1b20845fee62d
|