Delphi RTTI and Component Streaming: Why Properties Disappear from .dfm Files
Delphi’s streaming system persists only the properties it can discover through RTTI. Learn how published declarations, stored directives, and ownership rules control what reaches the .dfm file—and how to verify the round-trip.
06 Aug 2025, 22:14 UTC

You add a property to a custom component, rebuild the project, and the value you set at design time is gone from the saved form. Or a component you create at runtime throws an EReadError when the form reloads. The useful takeaway is that Delphi’s streamer does not save every field you declare; it persists only the properties it can resolve through the class’s Runtime Type Information (RTTI), and only when those properties are declared in a published section with streaming-compatible directives.
How the streamer uses RTTI
RTTI is metadata that the compiler emits about classes, methods, and properties. In Delphi, the VCL and FMX form streaming system relies on this metadata to write component state into .dfm (or .fmx) files and read it back. The class that performs this work is TFiler, which walks the component ownership tree and matches persisted property names in the file against the RTTI of the target class.
Two rules govern what gets written. First, the property must be declared under a published label. Properties in public or protected sections are invisible to the classic streaming system, even if they have getter and setter methods. Second, the property must have a type the streamer knows how to serialize—simple types, strings, enumerations, and nested component references work out of the box.
Ownership matters as well. When a form owns a component, the streamer records that relationship. At runtime, destroying the owner automatically frees the owned components through free-notify semantics. If you create a component at runtime without assigning an owner, it will not appear in the streamed output and may leak memory.
Worked example: a streamable custom panel
The following component definition shows the minimum structure needed for reliable round-trip streaming. Place this in a unit that is used by a VCL form or data module.
unit StreamingDemo;
interface
uses
System.Classes, Vcl.ExtCtrls;
type
TStatusPanel = class(TCustomPanel)
private
FCaption: string;
FAlertLevel: Integer;
procedure SetCaption(const Value: string);
function CaptionStored: Boolean;
published
// Written only when CaptionStored returns True
property Caption: string read FCaption write SetCaption stored CaptionStored;
// Written only when the value differs from the default
property AlertLevel: Integer read FAlertLevel write FAlertLevel default 0;
end;
implementation
procedure TStatusPanel.SetCaption(const Value: string);
begin
FCaption := Value;
end;
function TStatusPanel.CaptionStored: Boolean;
begin
Result := FCaption <> '';
end;
end.
With this definition, the streamer emits a Caption entry only when the caption is non-empty, and an AlertLevel entry only when the integer is not zero. That keeps the .dfm text clean and avoids overwriting design-time defaults with redundant values.
Checking the round-trip without guessing
You can verify that a property is visible to the streaming system before you rebuild the whole project. In any Delphi version, the TypInfo unit exposes GetPropInfo, which returns a pointer to the property’s RTTI record if the property is published. Run this check inside a method on the form that hosts the component, or in a small console project that references the component’s unit.
uses
TypInfo, StreamingDemo;
procedure VerifyStreamable;
var
Info: PPropInfo;
begin
Info := GetPropInfo(TStatusPanel, 'Caption');
if Info <> nil then
// Caption is published and will be considered by TFiler
else
// Caption is missing from RTTI; check the published section
end;
For a full round-trip check, save the form as text (for example, using the form designer’s text-DFM option or the corresponding environment setting), then inspect the file. You should see the property names you declared, not the private field names. If you change a value at runtime and call WriteComponent to a stream, the same names should appear in the output.
Limits and common mistakes
Binary size. RTTI adds metadata to the executable. Classes that are never streamed and never queried at runtime can reduce footprint when RTTI emission is limited, though the classic streaming system requires published properties regardless.
Version mismatches. A .dfm saved in Delphi 7 may fail to load in a modern RAD Studio version if a component’s published interface changed—property names were renamed, types were altered, or the streaming logic was not kept backward-compatible. The streamer matches by name and type, so a renamed property looks like an unknown entry and can raise a read error.
Public properties do not stream. Moving a property from published to public removes it from the streaming path. The property still works at runtime, but its value will not persist to the form file.
Missing stored or default directives. Without a stored directive or a default value, the streamer may write every property on every save, or it may omit values you expected to persist. String properties, for example, are written by default unless you supply a stored method or constant.
Extended RTTI is a separate concern. Delphi 2010 and later offer the System.Rtti unit for runtime type discovery beyond streaming. That extended RTTI is governed by {$RTTI ON} and {$RTTI OFF} directives at the unit or type level. Enabling or disabling that directive affects what TRttiContext can see; it does not change which properties TFiler writes, because classic streaming always uses published declarations.
To confirm a fix, rebuild the host application, reopen the form, and compare the .dfm text against a clean rebuild. The property names and values should match, and the Object Inspector should show the same state after reload.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.