Delphi LiveBindings: When Declarative Data Binding Earns Its Place in Your Architecture
An architecture note on Delphi LiveBindings: where declarative binding beats event-handler boilerplate, the smallest maintainable design, and the failure modes that should push you back to explicit code.
17 Sept 2025, 08:40 UTC

If you maintain a Delphi application with dozens of forms, you already know the pattern: every edit control gets an OnChange handler, every dataset gets a DataSource wired to DB-aware controls, and a tangle of Edit1.Text := Customer.Name assignments appears in FormShow. LiveBindings offers an alternative — declarative links between properties — but adopting it wholesale is a mistake. This note covers where it fits, the smallest design that works, and the failure modes that should push you back to explicit code.
The requirement LiveBindings actually solves
LiveBindings, available in both VCL and FireMonkey since around XE2, lets you declare that one property tracks another: Edit1.Text follows Customer.Name, a label shows a formatted currency value, a grid reflects a dataset. The binding engine evaluates expressions and propagates changes automatically when the source notifies — a dataset field change, or an object that fires property-change notifications.
The genuine win is not "no code." It is removing synchronization boilerplate from forms whose only job is to display and edit values, especially in FireMonkey where classic DB-aware controls do not exist and LiveBindings is the standard way to connect a TClientDataSet to a grid.
The smallest suitable design
Resist the LiveBindings Designer for anything beyond trivial forms. The designer stores bindings in the form file as component graphs that are hard to review in diffs and easy to break silently. A more maintainable minimum:
- Keep business objects in a separate unit, free of UI references.
- Expose them to the form through a
TBindSourceAdapter(orTPrototypeBindSourceduring early layout work). - Create bindings in code with a
TBindingsList, so the wiring is searchable, reviewable Pascal.
// Runs in the form unit; no special permissions needed.
// Assumes: FCustomer is a TObject with a Name: string property,
// BindSourceAdapter1 wraps FCustomer, BindingsList1 is on the form.
procedure TCustomerForm.FormCreate(Sender: TObject);
begin
BindSourceAdapter1.DataObject := FCustomer;
BindingsList1.AddExpression(
'EditName.Text', // target property
'BindSourceAdapter1.Name' // source expression
);
end;
For datasets, drop a TBindSourceDB pointing at your TDataSource and link control properties to its fields — this is the normal FireMonkey grid pattern and needs no custom code at all.
Trust and data boundaries
Treat the binding layer as a display concern, not a validation boundary. Binding expressions can convert and format values (and you can register custom converters by descending from the converter classes the bindings unit provides), but they should not be the only place where user input is checked. A binding will happily push an empty string into a field that your database later rejects. Keep validation in the object or dataset events (OnBeforePost, field OnValidate), and let bindings reflect the outcome.
Also be deliberate about direction. Default bindings are often one-way (source to control). Two-way editing of plain objects requires the object to participate in notification; otherwise the UI updates but your model silently does not, or vice versa. Verify direction explicitly for every binding you create in code.
Operational checks
Before shipping a form built on LiveBindings, run three checks:
- Edit-reflects-model: change a value in code (e.g.,
FCustomer.Name := 'Test'followed by the appropriate notification or adapter refresh) and confirm the control updates without any manual repaint call. - Model-reflects-edit: type into the control, move focus, and inspect the underlying object or dataset field in the debugger to confirm the value arrived.
- Converter path: if you registered a custom converter, test it with boundary values — empty strings, zero, dates at month boundaries — because converter bugs surface as wrong display, not exceptions.
Failure modes
Silent breakage on rename. Binding expressions are strings. Renaming a control or property does not update expressions, and failures surface at runtime as evaluation errors or simply nothing happening. Creating bindings in code with compiler-visible identifiers where possible reduces this; a smoke test per form catches the rest.
Update loops. Binding A→B and B→A with converters that are not exact inverses can ping-pong. If a form hangs or flickers after you add a binding, suspect a cycle first.
Bulk-operation slowdown. Every change triggers expression evaluation. When loading or editing many records, wrap the work in DataSet.DisableControls / EnableControls as usual, and for adapter-bound objects, suspend notifications during the batch. If a grid over a large dataset feels sluggish, measure before blaming the bindings — but expect bindings to be part of the cost.
Debugging opacity. A stack trace through the binding engine is far less readable than one through an OnChange handler. This is the real architectural cost: you trade explicit, debuggable flow for declarative wiring.
Conditions that should change the design
Stay with explicit event handlers or DB-aware controls when: the form has heavy conditional logic between controls (cascading visibility rules become unreadable as expressions); the team is small and unfamiliar with the binding engine; or profiling shows binding evaluation in a hot path. Conversely, LiveBindings becomes the obvious choice when you target FireMonkey across desktop and mobile from one codebase — the same binding expressions work on both — or when you have many simple display/edit forms where the boilerplate genuinely dominates.
The pragmatic middle ground most teams land on: LiveBindings for dataset-to-grid and simple property mirroring, explicit code for anything with branching logic. That boundary keeps the declarative benefits where they are real and the debugging pain where you can afford it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.