Saturday, September 14, 2024

Delphi Tip of the Day: The Delphi Magazine Total Collection

I'm always looking for ways to improve my Delphi skills. Sometimes, this takes me down a rabbit hole. And each time, these little side trips never answer "Yes" to the question "Will this help me ship?"  

Last week I went down the "global variables are bad" rabbit hole. 🐇🕳

Global Variables Are Bad

I opened my copy of "The Delphi Magazine Total Collection" looking for some inspiration. And, I found a 6 Part series called "Effective Delphi Class Engineering" written by David Baer. I'm slowly working my way through these articles.

Issue: 57 Effective Delphi Class Engineering 1:
Crossing The Chasm


David Baer kicks off a new series of articles aimed at all of us who are reluctant, confused or ill-informed on what object-oriented development is all about. The goal is to demonstrate, clearly and without jargon-loaded obfuscation, how to design, create and use Delphi classes to make your development more productive.
Issue: 59 Effective Delphi Class Engineering 2:
Welcome To The Machine


David Baer continues his series on practical object orientation by putting Delphi’s object machinery under a magnifying glass. He examines topics such as memory management, method calling, protocols and other compiler- related issues.
Issue: 60 Effective Delphi Class Engineering 3:
Skyrocketing Property


David Baer thinks properties are wonderful and this month gives us insights and advice on how they can best be put to use in our classes.
Issue: 62 Effective Delphi Class Engineering Part 4:
The TObject Of My


David Baer focuses on inheritance in this part of his popular series on object oriented development in Delphi.
Issue: 63 Effective Delphi Class Engineering Part 5:
You Are TEgg Man... I AM TWalrus


David Baer has not gone mad (though he may have been listening to too many old Beatles albums!): this instalment of his series continues his practical real-world discussion of polymorphism and inheritance.
Issue: 65 Effective Delphi Class Engineering Part 6:
To Talk Of Many Things


David Baer concludes his series on developing Delphi classes with a miscellany of sound advice, ranging from events to exceptions, RTTI and message handling.



Part 1 of Baer's series of articles, refenced short paper written by Marco Cantu called "When RAD is bad", which I found it on the internet archive. 

https://web.archive.org/web/20001120081900/http://community.borland.com/devnews/article/1,1714,10463,00.html  

But that's not all I found. I found a download link to the entire "The Delphi Magazine Total Collection Issues 1 - 139 (April 1995 to March 2007)"

https://archive.org/details/the-delphi-magazine-total-collection


Enjoy,
Semper Fi
Gunny Mike
https:\\zilchworks.com

Thursday, September 12, 2024

Sell Me This Pen!

OMG! I wish I had known this years ago. The Marine Corps taught me the Xerox Personal Selling Skills 3 (PSS3 = Needs Satisfaction) method. They turned me into a "regurgitator". If I had seen this video in 1994, it would have made a HUGE difference.



Show Me! Don't Tell Me!

HaveDemand your mentor model
the sales behavior for you!


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


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


Saturday, July 13, 2024

Delphi Tip of the Day: Always Use the MenuBar for FMX Desktop Applications

A couple months ago, I watch the video replay "The fundamental secrets of good UI design" done by Ian Barker. Around the 33:15 mark, Barker talks about supporting "dark mode", have a listen:

Transcript:

"One of the things you really should do at a very bare minimum if you do nothing else please support dark mode. And this is kind of a personal beg from me. As I said before I do get these floaters in my eyes, I can see perfectly well you know, I'm not blind or anything like that. I'm shortsighted which is why I have glasses but if you support dark mode you will make my life a lot easier. And other people that have visual  issues as well some people get migraines and things like that dark mode helps."

https://www.youtube.com/live/TL2YVio_eTo?si=GUzsPoZriiIdyfbj&t=1995

I never considered supporting dark mode

Until I watched this video, I never considered supporting dark mode in the current rewrite of my Zilch software. So I began looking into how to incorporate FMX styles. It was confusing at first. Then I got the hang of it. I decided to go with the "Air.Style" that comes with Delphi.

David Cornenelius' book "Fearless Cross-Platform Development with Delphi" and Andrea Magni's book "Delphi GUI Programming with Delphi" were big helps understanding FMX styles.

There were a couple of tweaks I had to make. The Air.Style is a vector style which means it does not use the bitmap designer. It is similar to how Cascading Style Sheets are used in web development. The graphic elements are drawn using SVG paths. 

FMX Main Menu component can't be styled 

The biggest surprise was the TMainMenu component. It turns out the FMX Main Menu component can't be styled. The Embarcadero FMX Style Viewer was showing a Main Menu that was styled how come my Main Menu wasn't styled?

fmx-style-viewer-air
FMX Style Viewer - Air.Style

I didn't want my application to have some stuff styled and some stuff not styled. Then I discovered FMX includes a MenuBar component which does take styling. And that is what is used by the FMX Style Viewer. So, I slowly and methodically swapped out the MainMenu component for the MenuBar component.

Always use the MenuBar for FMX desktop applications


FMX MainMenu vs MenuBar

My recommendation is to skip using the MainMenu component when developing FMX desktop applications and ALWAYS use the MenuBar component.

You will be happy. Your customers will be happy.

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!