Pacific Blue Software Logo

Migrating VCL Forms to FireMonkey with an AI Assistant

Migrating VCL Forms to FireMonkey with an AI Assistant

Moving a VCL application to FireMonkey (FMX) - to run it on macOS, a phone or just with a modern look - is mostly a long list of small, mechanical changes. Caption becomes Text, Left becomes Position.X, Vcl.StdCtrls becomes FMX.StdCtrls, hundreds of times over. That part suits an AI coding assistant such as Claude Code very well.

The rest is not mechanical: code that draws on a canvas, calls the Windows API, or relies on data-aware controls has to be redesigned, not translated. An assistant will translate it anyway, and the result usually compiles. This article is about splitting the work so the assistant does the first part and you keep control of the second.


What Converts Cleanly

Give the assistant the .dfm and .pas files and it will write the .fmx and the new unit. These changes it gets right nearly every time - all the FMX names below were checked by compiling them with Delphi 13:

VCLFMX
Vcl.StdCtrls, Vcl.Forms ...FMX.StdCtrls, FMX.Forms ...
{$R *.dfm}{$R *.fmx}
Caption on labels, buttons, check boxesText
Checked on check boxes and radio buttonsIsChecked
Left, Top (Integer)Position.X, Position.Y (Single)
Align := alClientAlign := TAlignLayout.Client
TPageControl / TTabSheetTTabControl / TTabItem
TComboBox with typing allowedTComboEdit
ComboBox.Text of a drop-down listComboBox.Selected.Text
Image.PictureImage.Bitmap
Position := poScreenCenterPosition := TFormPosition.ScreenCenter

Put this table, and any others you build up, in the project's CLAUDE.md, so each form is converted the same way.


What It Gets Wrong

Fonts that do not change

In FMX the style decides a control's font unless you say otherwise. Setting TextSettings.Font.Size has no visible effect while Size is still in StyledSettings:

Title_Label.StyledSettings := Title_Label.StyledSettings - [TStyledSetting.Size, TStyledSetting.Style];
Title_Label.TextSettings.Font.Size := 14;
Title_Label.TextSettings.Font.Style := [TFontStyle.fsBold];

A converted form whose bold headings all come out plain has usually missed this. The same applies to font colour and family.

Colours

A VCL TColor is stored as $00BBGGRR; an FMX TAlphaColor is $AARRGGBB. clRed is $000000FF and TAlphaColors.Red is $FFFF0000. Copying a numeric colour across unchanged swaps red and blue, and with an alpha of zero the colour is fully transparent, so it disappears. Ask for every colour constant to be converted explicitly. The article on converting web colours to Delphi shows the byte order.

Canvas drawing

Owner-drawn grids, custom charts and anything in an OnPaint handler need more than renaming. FMX drawing happens between BeginScene and EndScene, uses Fill and Stroke instead of Brush and Pen, takes Single coordinates in a TRectF, and draws text with FillText instead of TextOut:

if Canvas.BeginScene then
try
  Canvas.Fill.Color := TAlphaColors.Black;
  Canvas.FillText(RectF(4, 2, 200, 22), 'Total', False, 1, [], TTextAlign.Leading);
finally
  Canvas.EndScene;
end;

An assistant can make that translation, but whether the result looks right - line widths, half-pixel offsets, text that is clipped - is only visible when you run it. Expect to adjust every owner-drawn control by eye.

Windows-only code

Message handlers (procedure WMSize(var Msg: TWMSize); message WM_SIZE;), PostMessage, Handle, the registry and other Windows API calls either do not exist in FMX or only work on Windows. If the goal is other platforms, each one needs a decision, not a translation: TThread.Queue or TMessageManager in place of posted messages, an ini or JSON file in place of the registry, and so on. Ask the assistant to list all of these before converting anything, then decide each one yourself.

Data-aware controls

FMX has no TDBGrid, TDBEdit or TDBNavigator. Controls are connected to datasets with LiveBindings instead, which is a different design, not a different spelling. An assistant asked to convert a data-aware form will produce LiveBindings, but check each binding in the LiveBindings Designer - a binding that points at the wrong field shows nothing, without an error.

Dialogs on mobile

ShowModal and MessageDlg wait for an answer on desktop platforms, but not on Android, where code after the call keeps running. Code meant to run on a phone needs the versions that take an anonymous method, such as TDialogService.MessageDialog, with the follow-up code moved inside it.

Third-party components

Components without an FMX version cannot be converted at all, and an assistant may substitute something plausible without saying so. Have it list every component class on each form first, and mark which ones have an FMX equivalent.


A Workflow That Catches the Mistakes

  1. Survey first. Ask for a report, not code: every form, the component classes on it, every Windows API call, message handler, canvas drawing routine and data-aware control. This is the list of decisions you need to make.
  2. Move logic out of forms. Code in event handlers that is not about the screen - calculations, validation, database work - goes into plain units first, while the application is still VCL and can be tested as it was. Those units are then shared, not converted.
  3. One form at a time. Start with the simplest. Convert it, compile, open it in the IDE - the form designer reports any property it does not recognise - and run it.
  4. Compare by eye. Run the old and new versions side by side. Spacing, fonts and colours are the most common differences, and none of them shows up in a compile.
  5. Commit each form once it is right, so a later mistake cannot spoil it.

For reading a form file outside the IDE - to check what an assistant changed, or to see a form's structure at a glance - our DFM Viewer shows a .dfm or .fmx file as a component tree with its properties.


Summary


Useful References


Let the AI Do the Typing, Keep the Judgement


Back to Articles for Developers
Back to Articles on AI
Back to Articles on Delphi
Using Claude Code on a Delphi Project