Sunday, May 11, 2025

Delphi Tip of the Day: A Better Way to Center Modal Forms


Yesterday, I was going through some final aesthetic checks on my FMX desktop software application. I have two modal forms that popup up during click events. One is help about which I set to screen center. And the other contains code that centers the form on it's parent window.

I was testing the behavior of this form toggling between light and dark themes. I noticed that with light theme the window just appears but with the dark theme I could see the window move it's way from the top of the screen to it's centered position and then switch to dark.

In the OnShow event handler I was calculating Left and Top like I have always done to center the window. This was the first time I'd ever seen the window reposition itself. This is the first time I'd ever used a dark theme. And, this is the first major application I'd written with FMX.

Left := ParentForm.Left + (ParentForm.Width - Self.Width) div 2;
Top := ParentForm.Top + (ParentForm.Height - Self.Height) div 2;
To say the least, I was annoyed by this split second flash centering itself on the parent window. I tried all kinds of stuff to minimize this flash. Nothing work.

There's got to be a better way!

I didn't realize I had left the Position property of the form set to Default. In essence that is screen position (0,0), or the top-left corner. 

The first line of my code was moving the form horizontally from the left edge to the center of its parent. (I never saw that move happening). The second line of code was moving the form vertically from the top edge to the center of its parent (I saw this move happening).

I thought there's got to be a better way? I don't want customers seeing this screen moving like this. I tried setting the form's Visible property to False before the centering code and setting the Visible to True after. Nope, that is unauthorized. 

I tried in the OnCreate instead. Nope. I had created public properties for the forms Left and Top along with getters and setters. I created a whole elaborate scheme to support positioning this form where I wanted it.

Then it dawned on me. "Isn't there a screen position property?"

Yes, there is.
MainFormCenter! It's a thing!

FMX Form Position Property

Are you kidding me. MainFormCenter! It's a thing! When did this show up?

I immediately set the form Position property to MainFormCenter and added an Exit statement to the top of the OnShow event handler. 

And voila! 

It works like a champ. No more flashy form realignment stuff happening.

I then removed all the getters, setters, and properties I added for manually doing the form centering activity. Simple, cleaner code. 😎

BTW, the same thing exists for VCL.

VCL Form Position Property

DocWiki Links:

Enjoy
Semper Fi
Gunny Mike

https://zilchworks.com

Saturday, May 3, 2025

Delphi DOH! of the Day: Hardcoded Paths in TFDConnection.Params


I just spent two weeks tracking down an error that very easily, could have been avoided, if I hadn't been in such a rush.

I'm creating an FMX desktop application using Delphi 11.3 professional and a SQLite database which targets Windows and MacOS. I'm using a static class called TAppInfo which holds constants, variables, procedures, and functions for managing all the housekeeping bits for my application.

Inside the class constructor I'm using TPath.GetHomePath for managing paths:
//-----------------------------------------------------------------------  
// Build user paths 
//-----------------------------------------------------------------------  
ProgDataPath := IncludeTrailingPathDelimiter( TPath.GetHomePath) + VendorName + PathDelim + AppName;
Database     := ProgDataPath + PathDelim + DatabaseName;
This works great. It allows me to test both Windows and MacOS. In addition, I can easily test this on non-development machines by making sure I put the SQLite database file in the corresponding Application Path. It lets me simulate how everything would work if the application had been installed using an installer.

Everything had been going great. 
Build a little code. 
Test on Windows (my development machine). 
Test on MacOS using PAServer. 
Test on a non-development Windows machine using a thumb drive. 
All is good.

And then BOOM! 

It stopped working on the non-development test machine.

I kept getting a FireDAC SQLite error.


I couldn't figure out why all of the sudden it just crashed when I tried running it on a test machine. I spent hours creating MAP files, and testing, and asking ChatGPT to help me troubleshoot. 

I wrapped exception logic inside OnCreate event handlers. 
Nothing. 
Same error without displaying the exception logic.

"I may not be the brightest bulb on the tree..."


I may not be the brightest bulb on the tree but I knew something was different. I just couldn't figure it out or remember what I did that was different. By now two weeks has gone by and no resolution.

This morning I stripped out all the exception code and went back to square one. I looked at the project source. The only three units listed are two data modules and the main program. 

begin
  Application.Initialize;
  Application.CreateForm(TdmImages, dmImages);
  Application.CreateForm(TdmSQLite, dmSQLite);
  Application.CreateForm(TfMain, fMain);
  Application.Run;
end.
  • There's nothing SQLite related in the dmImages data module.
  • It's not getting to fMain.
  • It must be dmSQLite.
It's doesn't make sense. I've got exception logic inside the OnCreate event handler of dmSQLite but it's not firing off.

And then I had an A-Ha! moment. Right click on the dmSQLite data module and view as text!

Then I remembered what happened!

And there it was. The TFDConnection had hardcoded Params.Strings with the Database configured to a file located on my development machine and the DriverID = to SQLite.

Then I remembered what happened. I had been creating a new FastReport within the IDE. The report needed data from the SQLite database. So, I configured the TFDConnection to point to the SQLite database so I could drag and drop the database columns onto the report from within the IDE. 

Everything worked great! The report was spot on! I spent three days tweaking the report until it looked just right. I couldn't wait to test it on the non-development machine.

Ooops! I was in such a rush, I forgot to remove the hardcoded Params.Strings

I asked ChatGPT for an explanation as to why the OnCreate exception logic didn't fire. 

Explanation from ChatGPT:
  • Delphi loads the .dfm or .fmx file and instantiates all the components declared within it. 
  • This includes setting all published properties like FDConnection.Params, FDQuery1.Connection, Button1.text, etc.
  • This happens before the OnCreate event fires.
  • After all components are created and their published properties are set, OnCreate is triggered.

🛠 Your Specific Case

If TFDConnection.Params had hardcoded values (e.g. a file path or server name that doesn't exist on the test machine), they were already applied before OnCreate — meaning the connection could already try to auto-connect or be used by another component, causing a crash.

Lessons Learnt:
  1. 80 miles an hour is faster than 100 miles an hour. When you rush you make mistakes.
  2. Published Properties inside .dfm and .fmx files are set before any corresponding OnCreate event handlers.
  3. Make it a priority to View forms and data modules as text when troubleshooting.
  4. Remember Occams Razor: "The simplest explanation is usually the best one."
Enjoy,
Semper Fi
Gunny Mike
https://zilchworks.com