Showing posts with label delphi. Show all posts
Showing posts with label delphi. Show all posts

Saturday, July 26, 2025

💾 A 35-Year-Old Turbo Pascal Program Gets a Delphi 11.3 FMX Facelift

 Back in 1989, while stationed at MCAS Cherry Point as a U.S. Marine, I wrote a debt reduction program using Turbo Pascal. I called it Zilch. It was a side project—a DOS program to help people get out of debt faster by applying logic and structure to their monthly payments.

What I didn’t expect was that the program would still be alive 35 years later… and that I’d be rewriting it in Delphi 11.3 FMX to run natively on both Windows and macOS.

Today that same program—has helped over 16,000 people eliminate more than $114 million in debt. It’s been featured on Good Morning America, profiled in Military Lifestyle magazine, and used by people from all walks of life who just wanted a fair shot at financial freedom.

Turbo Pascal 1991 (640 x 480)


Delphi 5 2000 (640 x 480)


Delphi 5 2019 (640 x 480)


Delphi 11.3 FMX 2025 (Light)


Delphi 11.3 FMX 2025 (Dark)

🧰 Rewriting It with Delphi 11.3 FMX

After years of maintaining a Windows-only VCL version, I finally gave the software a full FireMonkey makeover. The new version runs beautifully on 64-bit Windows and macOS, thanks to Delphi’s powerful cross-platform capabilities.

Here’s what I used:

  • Delphi 11.3 Alexandria (FMX)

  • SQLite for local embedded storage

  • FastReport FMX for printable reports

  • Pure native code — no external dependencies, no subscriptions, no nonsense

I designed the UI using nested TLayout structures with TRectangle backgrounds and TLabel overlays for text. Simple. Clean. Predictable. Just the way I like it.

📣 Hoping to Share This Story Wider

I recently sent out a press release titled:

“Veteran’s 34-Year-Old Software Quietly Wipes Out $114M in Debt”


I'm hoping it catches the attention of journalists—not because it's flashy, but because it's quietly helped people get out of debt with logic, structure, and a clear plan.

But if the story does spread, I want the Delphi community to know this moment belongs to all of us.

I didn't build this alone.

🙏 Thank You, Delphi Community

I want to take a moment to thank the developers and authors who helped me along the way—people whose work made this possible, directly or indirectly:

  • Jeff Duntemann, whose Complete Turbo Pascal (1989) was my original gateway into programming. Without that book, none of this would have happened.

  • Ray Konopka, for his generous email responses and beautifully structured coding guidance. His style continues to shape how I write and organize code.

  • Cary Jensen, for his FireDAC and database wisdom. Cary’s knowledge and books helped me modernize the back-end without losing the software’s soul.

  • Ian Barker, for both encouragement and that persistent (okay, desperate) nudge to add a dark theme. You were right, Ian. It looks sharp. 😉

  • Andrea Magni, for Delphi GUI Programming with FireMonkey. His insights helped me tame layouts and think more visually in FMX.

  • David Cornelius, for Fearless Cross-Platform Development with Delphi. That book gave me the confidence to finally step outside of the Windows-only box.

  • Harry Stahl, for Cross-Platform Development with Delphi 10.2 and FireMonkey, which filled in several key gaps during this transition.

  • William Meyer, for Delphi Legacy Projects. His book helped me realize where I’d been going off track from the beginning—and finally set me straight in the right direction.

To all of you—thank you. Whether it was your book, your blog, your talk, or a few kind words on Twitter or in an email… you’ve helped keep ZilchWorks alive.

🧭 Final Thoughts

I’ve always believed that code should serve people—and this project has served thousands. Not because it’s cutting edge, but because it’s clear, honest, and built to last.

Delphi made that possible.

If you’re a Delphi developer with a legacy app—or a dusty old Turbo Pascal project—you don’t have to rewrite it in something new. Sometimes, all it needs is a fresh FMX coat of paint.

Thanks for reading.

Semper Fi,
Gunny Mike
https://www.zilchworks.com




Thursday, March 13, 2025

Delphi FMX Air.Style Missing 'scrollboxstyle' Definition

I've been upgrading my flagship software product Zilch, from Delphi 5 VCL to Delphi 11 FMX. I'm finally in the report development phase. I'm using the FMX version of FastReports.

Things have been going along quite well, other than the typical stumbling around, which happens when learning the ins and outs of a new component suite (FastReports). 

Back in May 2024, I decided to implement a dark theme after watching Ian Barker's presentation called "The fundamental secrets of good UI design". https://www.youtube.com/live/TL2YVio_eTo

During this presentation Barker pleads with the audience to include a dark theme option because it makes life much easier for people like him. Those who suffer with severe cataracts. 

I am very meticulous about my work. Borderline obsessive. After spending nearly 4 weeks creating, tweaking, previewing, re-tweaking a report I finally decided to see what it looks like in dark mode. That's when I got hit with an access violation. 

My first reaction was WTF! 😡 


FMX.StdCtrls

I spent 3 months or more back in May 2024 testing and trying several different FMX styles. I finally settled on the Air.Style after making several customizations. And now this happens. Just when I thought I was getting close to wrapping up this project. WTF! 😡

Inside my application I'm using the StyleManager.  I call:

TStyleManager.SetStyleFromFile( <FullFilePath> )

<FullFilePath> is the full file path of the modified version of Air.Style I'm using inside my program. I remember trying Dark.Style early on but I didn't like the brownish-copper glow effect so I opted for the Air.Style.

I quickly tried setting the <FullFilePath> to Dark.Style and tested it again, IT WORKED! 😎

Okay, so there is something different between Air.Style and Dark.Style. I searched both .STYLE files looking for "TScrollBar"

It turns out that Air.Style is missing the definition for StyleName = 'scrollboxstyle'.

I copied the complete 'scrollboxstyle' definition from Dark.Style and pasted it inside Air.Style and IT WORKED!

Payoff Savings Report - Air.Style

I'm glad this was a quick fix. Here is the complete 'scrollboxstyle' definition for anyone that would like to modify the Air.Style so it works with FastReports. 

  object TLayout
    StyleName = 'scrollboxstyle'
    DesignVisible = False
    Height = 131.000000000000000000
    Position.X = 254.000000000000000000
    Position.Y = 435.000000000000000000
    Width = 334.000000000000000000
    object TLayout
      StyleName = 'background'
      Align = Contents
      Locked = True
      Height = 131.000000000000000000
      Width = 334.000000000000000000
      object TLayout
        StyleName = 'content'
        Align = Client
        Height = 115.000000000000000000
        Width = 318.000000000000000000
      end
      object TScrollBar
        StyleName = 'vscrollbar'
        Align = Right
        Height = 115.000000000000000000
        Orientation = Vertical
        Position.X = 318.000000000000000000
        Width = 16.000000000000000000
      end
      object TScrollBar
        StyleName = 'hscrollbar'
        Align = Bottom
        Height = 16.000000000000000000
        Orientation = Horizontal
        Position.Y = 115.000000000000000000
        Width = 334.000000000000000000
      end
      object TSmallScrollBar
        StyleName = 'vsmallscrollbar'
        Align = Right
        Height = 8.000000000000000000
        Orientation = Vertical
        Margins.Left = 2.000000000000000000
        Position.X = 99.000000000000000000
        Position.Y = 2.000000000000000000
        Visible = False
        Width = 8.000000000000000000
      end
      object TSmallScrollBar
        StyleName = 'hsmallscrollbar'
        Align = Bottom
        Height = 8.000000000000000000
        Orientation = Horizontal
        Margins.Top = 2.000000000000000000
        Position.X = 2.000000000000000000
        Position.Y = 113.000000000000000000
        Visible = False
        Width = 150.000000000000000000
      end
      object TLayout
        Align = Contents
        Height = 131.000000000000000000
        Width = 334.000000000000000000
        object TLayout
          Align = Bottom
          Height = 20.000000000000000000
          Position.Y = 111.000000000000000000
          Width = 334.000000000000000000
          object TSizeGrip
            StyleName = 'sizegrip'
            Align = Right
            Locked = True
            Height = 20.000000000000000000
            Position.X = 314.000000000000000000
            Width = 20.000000000000000000
          end
        end
      end
    end
  end


Update: March 16, 2025

I'll have to give the new Delph 12.3 command-line style converter tool (vsf2fm.exe) a try. Hat Tip to Ray Konopka for pointing me in this direction.


I just installed Delphi 12.3 and used the vsf2fm.exe command-line tool and converted the Carbon.vsf to Carbon.style and it works perfectly.


Update: March 18, 2025

Just tried targeting macOS and the Carbon.style looks fantastic.

Enjoy!
Semper Fi
Gunny Mike
https://zilchworks.com



Sunday, February 16, 2025

Delphi Tip of the Day: FMX FastReport Text Object (TfrxMemoView)

I've been playing around with FastReport in FMX. To be honest, I've been struggling a bit. So, today I decided to go back to square one and work through the FMX documentation on the FastReport website. https://www.fast-report.com/public_download/docs/FRVCL/online/en/index.html

"The "Text" object... No Problem.
HTML-tags in the "Text" object... Doesn't work as advertised.

The "Text" object (TfrxViewMemo) is extremely powerful. It accepts simple HTML tags that lets you modify a single text field in some really cool ways. It's kind of like a mini "Rich Text" object. 

Here is a what a working example is supposed to look like:



That is one text field with multiple effects applied to separate sections:
  • Bold
  • Italic
  • Bold and Italic
  • Superscript
  • Subscript
  • Color
I ran into an issue when I followed the example to create the "Orange" text. It didn't work. The Original example from the FastReports did not turn the text orange.


Using <font color="#FF8030"> did not display the text in orange. The text between the opening and closing font tags did not display at all. Seeing that the "red" font tag worked, I tried using the color name "orange". And it worked.


I then tried using the Alpha Color of  #FFFF8030 and it also worked.


I'm glad I was able to make the FastReport example work. The text object is very powerful. I experimented with several different Delphi Color name constants and they also worked. I did run into an issue with some of the obsolete color name constants, such as MoneyGreen. The FastReport designer did not recognize "MoneyGreen" as a valid integer. It did however take the MoneyGreen HEX equivalent of #FFC0DCC0.

I decided to create a the following cheat sheet of all the font color names that FastReport text object will recognize.

Name Color HEX Code

Aliceblue

Aliceblue

#FFF0F8FF

Antiquewhite

Antiquewhite

#FFFAEBD7

Aqua

Aqua

#FF00FFFF

Aquamarine

Aquamarine

#FF7FFFD4

Azure

Azure

#FFF0FFFF

Beige

Beige

#FFF5F5DC

Bisque

Bisque

#FFFFE4C4

Black

Black

#FF000000

Blanchedalmond

Blanchedalmond

#FFFFEBCD

Blue

Blue

#FF0000FF

Blueviolet

Blueviolet

#FF8A2BE2

Brown

Brown

#FFA52A2A

Burlywood

Burlywood

#FFDEB887

Cadetblue

Cadetblue

#FF5F9EA0

Chartreuse

Chartreuse

#FF7FFF00

Chocolate

Chocolate

#FFD2691E

Coral

Coral

#FFFF7F50

Cornflowerblue

Cornflowerblue

#FF6495ED

Cornsilk

Cornsilk

#FFFFF8DC

Crimson

Crimson

#FFDC143C

Cyan

Cyan

#FF00FFFF

Darkblue

Darkblue

#FF00008B

Darkcyan

Darkcyan

#FF008B8B

Darkgoldenrod

Darkgoldenrod

#FFB8860B

Darkgray

Darkgray

#FFA9A9A9

Darkgreen

Darkgreen

#FF006400

Darkgrey

Darkgrey

#FFA9A9A9

Darkkhaki

Darkkhaki

#FFBDB76B

Darkmagenta

Darkmagenta

#FF8B008B

Darkolivegreen

Darkolivegreen

#FF556B2F

Darkorange

Darkorange

#FFFF8C00

Darkorchid

Darkorchid

#FF9932CC

Darkred

Darkred

#FF8B0000

Darksalmon

Darksalmon

#FFE9967A

Darkseagreen

Darkseagreen

#FF8FBC8F

Darkslateblue

Darkslateblue

#FF483D8B

Darkslategray

Darkslategray

#FF2F4F4F

Darkslategrey

Darkslategrey

#FF2F4F4F

Darkturquoise

Darkturquoise

#FF00CED1

Darkviolet

Darkviolet

#FF9400D3

Deeppink

Deeppink

#FFFF1493

Deepskyblue

Deepskyblue

#FF00BFFF

Dimgray

Dimgray

#FF696969

Dimgrey

Dimgrey

#FF696969

Dodgerblue

Dodgerblue

#FF1E90FF

Firebrick

Firebrick

#FFB22222

Floralwhite

Floralwhite

#FFFFFAF0

Forestgreen

Forestgreen

#FF228B22

Fuchsia

Fuchsia

#FFFF00FF

Gainsboro

Gainsboro

#FFDCDCDC

Ghostwhite

Ghostwhite

#FFF8F8FF

Gold

Gold

#FFFFD700

Goldenrod

Goldenrod

#FFDAA520

Gray

Gray

#FF808080

Green

Green

#FF008000

Greenyellow

Greenyellow

#FFADFF2F

Grey

Grey

#FF808080

Honeydew

Honeydew

#FFF0FFF0

Hotpink

Hotpink

#FFFF69B4

Indianred

Indianred

#FFCD5C5C

Indigo

Indigo

#FF4B0082

Ivory

Ivory

#FFFFFFF0

Khaki

Khaki

#FFF0E68C

Lavender

Lavender

#FFE6E6FA

Lavenderblush

Lavenderblush

#FFFFF0F5

Lawngreen

Lawngreen

#FF7CFC00

Lemonchiffon

Lemonchiffon

#FFFFFACD

Lightblue

Lightblue

#FFADD8E6

Lightcoral

Lightcoral

#FFF08080

Lightcyan

Lightcyan

#FFE0FFFF

Lightgoldenrodyellow

Lightgoldenrodyellow

#FFFAFAD2

Lightgray

Lightgray

#FFD3D3D3

Lightgreen

Lightgreen

#FF90EE90

Lightgrey

Lightgrey

#FFD3D3D3

Lightpink

Lightpink

#FFFFB6C1

Lightsalmon

Lightsalmon

#FFFFA07A

Lightseagreen

Lightseagreen

#FF20B2AA

Lightskyblue

Lightskyblue

#FF87CEFA

Lightslategray

Lightslategray

#FF778899

Lightslategrey

Lightslategrey

#FF778899

Lightsteelblue

Lightsteelblue

#FFB0C4DE

Lightyellow

Lightyellow

#FFFFFFE0

Lime

Lime

#FF00FF00

Limegreen

Limegreen

#FF32CD32

Linen

Linen

#FFFAF0E6

Magenta

Magenta

#FFFF00FF

Maroon

Maroon

#FF800000

Mediumaquamarine

Mediumaquamarine

#FF66CDAA

Mediumblue

Mediumblue

#FF0000CD

Mediumorchid

Mediumorchid

#FFBA55D3

Mediumpurple

Mediumpurple

#FF9370DB

Mediumseagreen

Mediumseagreen

#FF3CB371

Mediumslateblue

Mediumslateblue

#FF7B68EE

Mediumspringgreen

Mediumspringgreen

#FF00FA9A

Mediumturquoise

Mediumturquoise

#FF48D1CC

Mediumvioletred

Mediumvioletred

#FFC71585

Midnightblue

Midnightblue

#FF191970

Mintcream

Mintcream

#FFF5FFFA

Mistyrose

Mistyrose

#FFFFE4E1

Moccasin

Moccasin

#FFFFE4B5

Navajowhite

Navajowhite

#FFFFDEAD

Navy

Navy

#FF000080

Oldlace

Oldlace

#FFFDF5E6

Olive

Olive

#FF808000

Olivedrab

Olivedrab

#FF6B8E23

Orange

Orange

#FFFFA500

Orangered

Orangered

#FFFF4500

Orchid

Orchid

#FFDA70D6

Palegoldenrod

Palegoldenrod

#FFEEE8AA

Palegreen

Palegreen

#FF98FB98

Paleturquoise

Paleturquoise

#FFAFEEEE

Palevioletred

Palevioletred

#FFDB7093

Papayawhip

Papayawhip

#FFFFEFD5

Peachpuff

Peachpuff

#FFFFDAB9

Peru

Peru

#FFCD853F

Pink

Pink

#FFFFC0CB

Plum

Plum

#FFDDA0DD

Powderblue

Powderblue

#FFB0E0E6

Purple

Purple

#FF800080

Red

Red

#FFFF0000

Rosybrown

Rosybrown

#FFBC8F8F

Royalblue

Royalblue

#FF4169E1

Saddlebrown

Saddlebrown

#FF8B4513

Salmon

Salmon

#FFFA8072

Sandybrown

Sandybrown

#FFF4A460

Seagreen

Seagreen

#FF2E8B57

Seashell

Seashell

#FFFFF5EE

Sienna

Sienna

#FFA0522D

Silver

Silver

#FFC0C0C0

Skyblue

Skyblue

#FF87CEEB

Slateblue

Slateblue

#FF6A5ACD

Slategray

Slategray

#FF708090

Slategrey

Slategrey

#FF708090

Snow

Snow

#FFFFFAFA

Springgreen

Springgreen

#FF00FF7F

Steelblue

Steelblue

#FF4682B4

Tan

Tan

#FFD2B48C

Teal

Teal

#FF008080

Thistle

Thistle

#FFD8BFD8

Tomato

Tomato

#FFFF6347

Turquoise

Turquoise

#FF40E0D0

Violet

Violet

#FFEE82EE

Wheat

Wheat

#FFF5DEB3

White

White

#FFFFFFFF

Whitesmoke

Whitesmoke

#FFF5F5F5

Yellow

Yellow

#FFFFFF00

Yellowgreen

Yellowgreen

#FF9ACD32

Osolete Color Names Use HEX Code

LtGray

LtGray

#FFC0C0C0

MedGray

MedGray

#FFA0A0A0

DkGray

DkGray

#FF808080

MoneyGreen

MoneyGreen

#FFC0DCC0

LegacySkyBlue

LegacySkyBlue

#FFF0CAA6

Cream

Cream

#FFF0FBFF



Enjoy!
Semper Fi
Gunny Mike

Sunday, September 1, 2024

Delphi Tip of the Day: How to Keep Your Code Folded in Delphi 12

I like the "Code Folding" feature of  Delphi. However, I noticed that every time I reopened a project in Delphi 12 where I had previously folded the code, the code was no longer folded.

To keep your code folded in Delphi 12 you need to turn on the Save project desktop when closing option inside the IDE.

Tools > Options > IDE > Saving and Recovering

This option is unchecked in the default, out-of-the-box setup.


Delphi 12 also includes a new feature called Save editor state when closing a tab any time
https://docwiki.embarcadero.com/RADStudio/Athens/en/Saving_and_Recovering

Save editor state when closing a tab any time When selected, the current state for the views is saved before closing it. State info includes collapsed regions, cursor/caret position, and bookmarks.

This allows you to close and reopen a tab within the same editing session, and when the tab is reopened it will display exactly as it was when it was closed. This is similar to the Save project desktop when closing setting but functions all the time, not just when closing and reopening a whole project.

This option can only be enabled when Save project desktop when closing is checked.

Let me know in the comments what other out-of-the-box tweaks you like to make to a brand new Delphi install.

Enjoy!
Semper Fi,
Gunny Mike
https://zilchworks.com


Sunday, March 24, 2024

FastReport VCL & FMX Comparison (Embarcadero Edition)

Disclaimer: This post contains affiliate links, which means I may earn a small commission if you click through and make a purchase, at no extra cost to you. Your support helps keep this blog running – thank you!

The upgrade of my flagship product from Delphi 5 VCL to Delphi 11.3 FMX is finally at the presentation stage. I've spent the last six weeks thinking about and learning about data visualization. 


I took a few classes on LinkedIn Learning about data viz, and I re-watched Ray Konopka's excellent video on Data Visualization using Delphi. Seeing is Believing - Data Visualization in Multi-Device Apps. Ray has also made the source code for his demonstration available at https://delphibydesign.com/downloads/ Thank you Ray!

I've never used FastReport before. My current Delphi 5 VCL product uses QuickReports. I did find a great FastReport video available through Embarcadero Academy called Getting Started with FastReport by Cary Jensen. Jensen mainly focuses on the VCL version of FastReport. 

I've never used FastReport before 

I was surprised at the huge difference between the VCL and FMX versions of the FastReport Embarcadero Editions. That's probably why Jensen focused on the VCL version in his video. These are the palette entries for Delphi 11.3 Professional. (VCL on the left, FMX on the right)

I'm interested in the FMX version of FastReport because I'm developing a desktop software product which targets Windows and macOS users. Because I'm new to FastReport, I want to see how far I can take the "Embarcadero" version of FastReport before I commit to purchasing the full version.

Unfortunately, the Embarcadero Edition of FMX FastReport has no export capability. See the Palette image above.

I was surprised at the huge difference between the VCL and FMX versions

Because I'm targeting both Windows and macOS users I need to know if there are any stark differences between those platforms. I created a comparison chart which highlights these differences. This is the same data from the Fast Reports website sorted by features with missing capabilities highlighted.


Here is the same comparison which filters out any features that are unavailable across all platforms.


As you can see, the Embarcadero Edition of FastReports 2.0 FMX is very limited.


The Bottom Line: Because my application is cross-platform for both Windows and macOS, it looks like I will have to purchase the full retail version of FastReport 2.0 FMX in order to give my customers the experience they expect. 

Enjoy
Semper Fi
Gunny Mike
https://zilchworks.com

Note: Some links in this post are affiliate links, meaning I may receive a small commission if you make a purchase. This helps support my work – thank you for your understanding.

Friday, January 5, 2024

Delphi Tip of the Day: Prevent SQLite Date Headaches by using a GetDateAs_YYYYMMDD Function

Working with Date data can be very tricky. I recently encountered an "Invalid argument to date encode", error while trying to update a SQLite database table.


This placed a value of 0000-00-00 into the date field of my SQLite table.

Here is the original code which caused the error.

function TForm1.GetOneOffDateAsDate: TDate;
begin
  Result := DateEdit1.Date;
end;

procedure TForm1.SQLInsertPayment;
begin
  qryO.SQL.Clear;

  qryO.SQL.Add( ' INSERT INTO OneOffPayments    ');
  qryO.SQL.Add( '             (                 ');
  qryO.SQL.Add( '             OneOffDate,       ');
  qryO.SQL.Add( '             OneOffPayment     ');
  qryO.SQL.Add( '             )                 ');
  qryO.SQL.Add( '      VALUES (                 ');
  qryO.SQL.Add( '             :ood,             ');
  qryO.SQL.Add( '             :oop              ');
  qryO.SQL.Add( '             );                ');

  qryO.ParamByName( 'ood' ).Value := GetOneOffDateAsDate;
  qryO.ParamByName( 'oop' ).Value := GetOneOffAmount;

  qryO.ExecSQL;
end;

The getter function GetOneOffDateAsDate passes in a TDate which doesn't play nicely with FireDAC. Fortunately, the fix is quite simple. I found a fantastic explanation for this error on stackoverflow which states FireDAC expects DATE data type values to be a string in the fixed format of YYYY-MM-DD.

FireDAC Expects DATE data types
to be strings formatted as YYYY-MM-DD

So I created another getter function to format the date data as a YYYY-MM-DD string.
Problem solved!

Updated code passing FireDAC a YYYY-MM-DD string

function TForm1.GetOneOffDateAs_YYYYMMDD: String;
begin
  Result := FormatDateTime('YYYY-MM-DD', DateEdit1.Date);
end;

procedure TForm1.SQLInsertPayment;
begin
  qryO.SQL.Clear;

  qryO.SQL.Add( ' INSERT INTO OneOffPayments    ');
  qryO.SQL.Add( '             (                 ');
  qryO.SQL.Add( '             OneOffDate,       ');
  qryO.SQL.Add( '             OneOffPayment     ');
  qryO.SQL.Add( '             )                 ');
  qryO.SQL.Add( '      VALUES (                 ');
  qryO.SQL.Add( '             :ood,             ');
  qryO.SQL.Add( '             :oop              ');
  qryO.SQL.Add( '             );                ');

  qryO.ParamByName( 'ood' ).Value := GetOneOffDateAs_YYYYMMDD;
  qryO.ParamByName( 'oop' ).Value := GetOneOffAmount;

  qryO.ExecSQL;
end;

Happy coding!


Enjoy!
Gunny Mike
https://zilchworks.com

Monday, January 1, 2024

How to open URLs with default applications in macOS and Windows

 I'm currently updating an old Delphi 5 Desktop VCL application to to Delphi 11.3 FMX. And one of the capabilities I want to provide is the ability to launch several webpages from within the application. I want to place a link in the main menu to my YouTube channel so customers can easily get to product videos. And there's also a link to my website in the Help > About box.



It was fairly straightforward the last time I did this using VCL because all I had to worry about was the Windows side of things. However, because I want this application to run on both Windows and macOS it presented a challenge.

The Delphi IDE won't recognize the Macapi namespace unless the target is set to MacOS 64-bit

Harry Stahl covers the COCOA API on pages 98-99 of his book Cross-Platform Development with Delphi. He also gives an example of how to use the NSWorkspace object of  Macapi.Appkit. However, he doesn't show how to setup the uses clause.

I also found a fantastic reference on stackoverflow by David Heffernan that was written in 2015. However, there are two issues with Heffernan's if you are looking for a complete answer:

  1. There is a reference to a blog post by Malcolm Groves called Opening files and URLs in default applications in OS X which is no longer available or accessible.
  2. The example doesn't tell you you need to target the MacOS 64-bit platform before the IDE will recognize Macapi namespace..
The Delphi IDE won't recognize the Macapi namespace unless the target is set to MacOS 64-bit. Shame on me for not reading up on the Embarcadero docs. Wrapping my head around how to use the  {$IFDEF MSWindows} and the {$IFDEF MACOS} was a little tricky But I eventually caught on. 

After a couple hours of going back and forth with code that worked for Windows but didn't work for macOS. And code that worked for macOS but didn't work for Windows, I finally got Heffernan's example to work.

The next step was to extract the code out of the main form and place it into it's own unit. And that is the code I'm sharing with you today. I hope you find this helpful. 



Video demonstration of the below source code

Here is the source code:

Unit1.fmx
object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Goto Website'
  ClientHeight = 238
  ClientWidth = 478
  Position = ScreenCenter
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object Text1: TText
    AutoSize = True
    Cursor = crHandPoint
    Position.X = 116.000000000000000000
    Position.Y = 83.000000000000000000
    Size.Width = 246.078125000000000000
    Size.Height = 31.921875000000000000
    Size.PlatformDefault = False
    Text = 'https://zilchworks.com'
    TextSettings.Font.Size = 24.000000000000000000
    TextSettings.FontColor = claMediumblue
    OnClick = Text1Click
    OnMouseEnter = Text1MouseEnter
    OnMouseLeave = Text1MouseLeave
  end
end

Unit1.pas
unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects;

type
  TForm1 = class(TForm)
    Text1: TText;
    procedure Text1MouseEnter(Sender: TObject);
    procedure Text1MouseLeave(Sender: TObject);
    procedure Text1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  u.OpenURL;

{ TForm1 }

procedure TForm1.Text1Click(Sender: TObject);
begin
  GotoWebsite(Text1.Text);
end;

procedure TForm1.Text1MouseEnter(Sender: TObject);
begin
  Text1.Font.Style :=  Text1.Font.Style + [TFontStyle.fsUnderline];
end;

procedure TForm1.Text1MouseLeave(Sender: TObject);
begin
  Text1.Font.Style :=  Text1.Font.Style - [TFontStyle.fsUnderline];
end;

end.

u.OpenUrl.pas
{
+-------------------------------------------------------------------------------
¦ Filename:    u.OpenURL Unit
¦ Author:      Michael J. Riley
¦ Website:     https://zilchworks.com
¦ YouTube:     https://www.youtube.com/@CapeCodGunny
¦ Copyright:   © 2023-2024 by Michael J. Riley. All Rights Reserved.
+-------------------------------------------------------------------------------
¦ Purpose:     Opens a url using the default browser on the users system.
¦
¦ OS:          Windows, macOS
+-------------------------------------------------------------------------------
¦ Developer References:
¦
¦ Book:        Cross-Platform Development with Delphi 10.2 & FireMonkey
¦ Author:      Harry Stahl
¦ ISBN:        https://isbnsearch.org/isbn/9781549545764
¦ Notes:       See pages 98-99.
¦
¦ Websites:    https://stackoverflow.com/q/28858392/195983
¦              https://stackoverflow.com/a/28859000/195983
+-------------------------------------------------------------------------------
¦ DISCLAIMER:
¦
¦ This source code is provided "as is" and without any warranty. Use it at your
¦ own risk. The author(s) make no guarantees or assurances regarding the
¦ correctness or functionality of the code, and disclaim any liability for
¦ damages resulting from its use.
¦
¦ It is advisable to thoroughly review and test the code before deploying it in
¦ any production environment.
¦
¦ By using this code, you agree to these terms and acknowledge that any issues
¦ arising from its use are solely your responsibility.
+-------------------------------------------------------------------------------
}
unit u.OpenURL;

interface

{$IFDEF MSWindows}
uses
  Winapi.ShellAPI,
  Winapi.Windows;
{$ENDIF}

{$IFDEF MACOS}
uses
  Macapi.AppKit,
  Macapi.Foundation,
  Macapi.Helpers;

  procedure macOSGotoWebsite(URL: string);
{$ENDIF}

  procedure GotoWebsite(URL: string);

implementation


procedure GotoWebsite(URL: string);
begin
  {$IFDEF MSWindows}
  ShellExecute(GetDesktopWindow, 'open', PChar(URL), '', '', SW_SHOWNORMAL)
  {$ENDIF}
  {$IFDEF MACOS}
  macOSGotoWebsite(URL);
  {$ENDIF}
end;

{$IFDEF MACOS}
procedure macOSGotoWebsite(URL: string);
var
  macURL: NSURL;
  macWorkspace: NSWorkspace;
begin
  macURL := TNSURL.Wrap(TNSURL.OCClass.URLWithString(StrToNSStr(URL)));
  macWorkspace := TNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace);
  macWorkspace.openURL(macURL);
end;
{$ENDIF}

end.

Enjoy!




Wednesday, December 20, 2023

Delphi Tip of the Day: Use Your Own Unique Prefix for Types and Constants

I'm in the process of converting an old Delphi 5 VCL application to Delphi 11 FMX. Yeah, I have Delphi 12 but I'm waiting for the first bug release before I start using it. Well, this morning I learned a valuable lesson. A lesson which stopped me dead in my tracks. 

There's nothing stopping you from creating an already existing type!

I was making some refactoring changes to my code. I didn't realize I was about to make a huge mistake. I went plodding ahead pleased with the progress I was making and admiring the beautiful, refactored code I was creating. When boom... I renamed a type that stepped on a type already inside the System.UITypes unit.

There's nothing to stop you from creating an already existing type. Of course I didn't use the preview changes function. I just hit go!

I had defined my own enumerated type called TMyImageIndex. I decided to just simply call it TImageIndex. So, that's what I did using Refactor > Rename type TMyImageIndex CTRL+SHIFT+E

I knew I messed up when I saw way too much code show up in the results panel.

How dare Embarcadero define a type called TImageIndex

WTF.  If it already existed the IDE should have prevented me or at least warned me. How dare Embarcadero define a type called TImageIndex.

type
//TImageIndex = (iiAdd = 2, iiUpdate = 3);)
  TZwImageIndex = (iiAdd = 2, iiUpdate = 3);)

const
  ZW_WIDTH_COLUMN_GLYPH  = 20;
  ZW_WIDTH_COLUMN_DATE   = 90;
  ZW_WIDTH_COLUMN_AMOUNT = 74

It took me about 30 minutes, but I was able to get the original code back in place, And working.

So going forward I have created my own unique prefix of  TZw (Zw = ZilchWorks). Going forward I will preface all Types and Constants.

Enjoy!
Semper Fi
Gunny Mike
https://zilchworks.com

Sunday, July 23, 2023

Here's how I will code better in Delphi. How will you?

Disclaimer: This post contains affiliate links, which means I may earn a small commission if you click through and make a purchase, at no extra cost to you. Your support helps keep this blog running – thank you!

When it comes to Delphi, it's amazing how much I don't know, once knew but forgot, or just plain skipped over because it was too mind-boggling at the time. I took a break from reading Alister Christie's new book "Code Better in Delphi", which I purchased last night, to write this blog post.


https://amzn.to/4dE8oOq (Paperback)

While reading the bit on "refactoring", I realized how little refactoring I actually put into practice. Yes, I have Fowler's book, the second edition. And no, I haven't read it. I've thumbed through it a little in the past. I even tried looking for the "Introduce Explaining Variable" refactor Christie discusses on page 53 of his book. It turns out Fowler now calls it "Extract Variable". Keeping Fowlers "Refactoring" book on my desk, and referring to it often, is one way I am going to code better in Delphi.

It's amazing how much I don't know or once knew but forgot

I just started reading "The Observer Pattern" in Christie's book and had to stop. I bought "Head First Design Patterns" by O'Reilly, quite a few years ago. I have the 10th Anniversary edition with code examples in Java 8. My intent was to install Java on my machine and force myself to learn these patterns by following the code in the book. It didn't go so well. The authors do a great job using story to make learning fun. I just couldn't wrap my head around Java.

Christie has managed to reignite my desire to learn these patterns. I downloaded the Java source code. I plan to work my way through "Head First Design Patterns" pasting the Java source code into ChatGPT, and asking Chat to convert it to Delphi.

I will navigate through each pattern using this method. I will then revisit the same pattern in Primoz Gabrijelcic's book "Hand-On Design Patterns with Delphi". This is another way I'm going to learn to code better in Delphi. 

Another way is to revisit the Model-View-Controller method of code separation. Here is a the MVC song from 2007 I just learned about today. 

And I also want to implement "Interfaces".

Here's my list of how I will code better in Delphi:

  • Finish reading Christie's book 
  • Practice refactoring often
  • Learn and implement design patterns
  • Discover and utilize ways to separate code concerns such as MVC or MVVM
  • Overcome my stumbling block of Interfaces

Use the comments below to share how you will code better in Delphi.

Enjoy
Semper Fi
Gunny Mike

Note: Some links in this post are affiliate links, meaning I may receive a small commission if you make a purchase. This helps support my work – thank you for your understanding.