0% found this document useful (0 votes)
467 views214 pages

Cross Platform

Development with Delphi 10.2 & FireMonkey

Uploaded by

Donovan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
467 views214 pages

Cross Platform

Development with Delphi 10.2 & FireMonkey

Uploaded by

Donovan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 214

Cross-Platform Development

with Delphi 10.2


&
FireMonkey

for
Windows, Mac OS X (macOS)
&
Linux

Harry Stahl
Author: Harry Stahl
Publisher: Harry Stahl
City: Bonn, Germany
Copyright (2017), All rights reserved

Delphi and FireMonkey are registered trademarks of Embarcadero.


Apple, OS X, iOS, iPad, and iPhone are registered trademarks of Apple.
Windows is a registered trademark of Microsoft.

If I have used in this book a trademark - without referring to the owner, please inform me. I
will revise it immediately.

First edition
TABLE OF CONTENTS

Foreword
Introduction
About the book
About the author
Contact information
Chapter 1: What is FireMonkey?
Chapter 2: How to use the FireMonkey components
Section 1: Getting Started
Section 2: New FireMonkey project
FireMonkey desktop application (Multi Device Application)
Using the Multi Device Designer (Fire UI)
Form inheritance with the Multi Device Designer
Reverting to inherited settings
Creating a platform-specific event handler with the Multi-Device Designer
Section 3: A first FMX-program (analog clock)
Section 4: Selected FireMonkey components
TButton (with Trimming)
TEdit (without PasswordChar)
TExpander
TForm (furthermore with caption)
TFrame
TPanel
TRectangle
TCheckbox, TRadioButton (IsChecked)
TGroupBox with TRadioButtons
TSwitch
TImage
TImageControl
TImageViewer
TImageViewer (to use with Livebindings Designer)
TLabel (new property FontColor)
TPathLabel
TPath
TImageList (not available - but compensation possible)
TListBox (no TCheckListbox, but ShowCheckboxes)
All Components (except the form)
Several Components (Properties with additional type-qualifying)
TMenuItem (without ImageIndex)
TMainMenu (Handling MAC Menus)
TMemo (CaretPosition, no Modified, FindNext-replacement)
TDropTarget (how drag & drop works in FireMonkey)
TRichEdit (not available - but possible for replacement via 3rd-party)
TPageControl (Not available - but replacement available)
TStringGrid (works different)
TGrid (Image and other elements in the Grid)
TStringGrid-alternative (TMSFMXGrid)
THeader (not sections, but items)
THeaderControl (is not available under FireMonkey)
TProgressBar (not "position" but value)
TTabControl (no Ownerdraw)
TTrackbar (helpful property "tracking")
TSpeedButton (without Bitmap)
TStatusbar (a way to compensate the missing "Panels")
MessageDlg (e.g. not directly usable with mtWarning)
Section 5: The FireMonkey Style-Designer
a) Using the Styles Editor
b) Styles in FireMonkey - an overview
c) How to convert VCL Styles to FireMonkey Styles
d) Using FireMonkey Styles
e) Understanding FireMonkey-Styles
Chapter 3: Tips and tricks for Cross-Platform Development
Section 1: Starting other programs
Section 2: Get the program directory and program data directory
Section 3: Catch to the program passed start-up parameters
Section 4: "Hello World" - Multilingual programs and new markets
Section 5: Apply sandboxing and Entitlements properly
Section 6: Using MAC APIs (POSIX, CORE and Cocoa) in Delphi
Chapter 4: Requirements for Cross-Platform Development
Section 1: Setting up Windows PC and MAC PC
Section 2: Enabling MAC OSX Platform
Section 3: Provisioning and deployment (MAC)
1. Submission to the APPLE App Store
2. How to create a .dmg file for distribution outside the Apple App Store
3. How to create your own setup package with the Application Developer ID / Installer
a) Hot to request a Developer ID certificate and an Application Developer Installer ID
b) Working with the code-signing tool and Package Maker
Chapter 5: Cross-Platform development with Linux
Section 1: Setting up Windows and Linux-PC
Section 2: Enabling the Linux-Platform
Section 3: Provisioning and deployment (Linux)
Section 4: A first Linux-Console-Application
Section 5: Linux-Server-Console-Application and Client-Application
Section 6: Create an Application as Service (Daemon)
Section 7: Delphi units for Linux
Chapter 6: Working with Graphics in FireMonkey
1. FireMonkey TBitmap versus Windows TBitmap
2. TBitmapData instead of ScanLine for bitmap manipulation
3. How to change the alpha channel of a TBitmap
4. How to draw on the canvas of a bitmap
5. How to turn graphics, flip, invert or color to gray
6. How to draw a bitmap scaled
Chapter 7: 3D-Programming
Section 1: Overview
1. 3D-Objects
2. Cameras
3. Screen Projections
4. Rotations
5. Light
6. Materials
Section 2: The 3D Coordinate System
Section 3: 3D-Application "Atomic Model"
Section 4: 3D-Application "Solar Model"
Chapter 8: Animations, Transitions and Effects
Chapter 9: Sending and receiving messages with the TMessageManager
Section 1: Simple Messaging-Demo
Section 2: Enhanced Messaging-Demo
Chapter 10: Useful third-party components for FireMonkey
1. TMS-Components
2. Report-generator: FastReport FMX
3. RemObjects-Application Framework (Hydra)
4. Other components
Chapter 11 How to - tips & tricks for FMX
R1 ... Get the display resolution?
R2 ... Check whether the Escape, Ctrl or Alt key is pressed
R3 ... Use folder names under Windows and MAC properly
R4 ... Use search-mask for "all files" in Windows and MAC OS X properly
R5 ... Avoid looping symlink folders (Alias)
R6 ... In which situations file symlinks functions play a role otherwise
R7 ... Determine the control under the mouse position
R8 ... Find out on which MAC OS X operating system the program is running
R9 … Get the current user name in Mac OS X / Linux / Windows
R10 … Send files as an attachment of an e-mail with the system mail program
R11 … Provide the user with help-files under Win & MAC
R12 ... Drag and drop text from external source (eg browser) to a TEdit box
R13 ... Store additional information in standard objects
R14 … Using ActiveControl
R15 … Replace OnDrawItem event of the ListBox from VCL with the OnPainting event of
the TListBoxItems
R16 … Load Bitmap from resource file (for retina display)
R17 … Swap items in a listbox
R18 … Swap items in a Listbox via Drag & Drop
R19 … Using FMX functions in a VCL application via DLL
R20 … Draw text in TGrid right or centered
R21 … Draw text in TStringGrid right or centered
R22 … Working with the "visible" property of controls
R23 … Prevent unintended shortening of TLabel text
R24 ... How to show a pop-up menu at a special position
R25 … Determine the document directory
R26 … Improve the font quality (especially on Windows)
R27 … Select a folder with a dialog
R28 ... Let a column in a string grid occupy the remaining space
R29 ... Create missing components with Frames
R30... Moving controls at runtime in the form
Chapter 12: Outlook
Foreword
This book is not for beginners of programming, but for experienced VCL-developers, who want to start
with FireMonkey now or for those who are already working with FireMonkey and searching for some
problem-solutions.

Experienced users who previously developed for Windows have - when migrating to FireMonkey - a
series of questions that sometimes seems to be difficult to find the answers. First of all, it needs to find
out the establishment of the connection between Windows PC and the MAC or Linux and various
setting dialogs.

There are often only small differences between FireMonkey- and VCL components, which causes
failing in development work. It costs a lot of time to find the differences. This is why I have written this
book which explains the small and large differences among the most important of the well-known VCL
components.

The use of FireMonkey components makes especially more sense if they are used for cross-platform
development. But under Windows and e.g. the MAC some functions are completely different, for
example passing parameters at the start of the program.

To save your time to search, this book provides you some of the answers you need.

If the MAC or Linux is new to you or you have no basic information about the handling of files,
available storage locations or required developer tools, you will find all of them (at least the most
important of information) in this book.

A new chapter in this book is about 3D-programming. Here you will find the basic understanding and
some easy sample-applications.

If you have any suggestions on the topic, please write an email to the author.

Just as an information: This book is not about the topic "databases". This is due to the personal
circumstance that the author doesn't use the Delphi database components, but instead of this a own
solution for the work with "databases".

September 2017, Bonn


Introduction
The Internet is a blessing and a curse. You will find everything, but at the same time you are lost. In
addition to searching in the internet, personal studies on the sources of Delphi and the MAC API or
Linux libraries were necessary to introduce the solutions offered in this book.

About the book


This book will be available first only in a printed version.

To read the book: If you have not set up your Mac or your Linux-PC to work with Delphi yet, you may
start with the relevant chapter (Chapter 4 for the MAC and chapter 5 for Linux). If you have already
completed the setup, you can just start reading it from the beginning.

About the author


I have been developing software for a certain long time. Starting with Turbo Pascal version 3.0 in the
eighties, I then bought 5.0 Turbo Pascal for 700 DM which was at that time quite expensive for me.
Apart from a short detour to Visual Basic, I stay faithful to Delphi with all versions from number 1 to
the current version Delphi Tokyo (10.2).

By now I've written dozens of programs with Delphi. One can develop robust and sophisticated
programs with Delphi, which is confirmed by more than 5,000 customers with 10,000 users of mine .

Contact information
You can find my general web page at www.hastasoft.de and a special site for developers at
www.devpage.de where you will also find a brief description of some of my tools that can you assist in
program development.

You can also reach me by e-mail: [email protected].


Chapter 1: What is FireMonkey?
FireMonkey, usually abbreviated as "FMX", is a software component library or vector-based
framework, which allows cross-platform application development for Windows, MAC OS X (or
"macOS"), Linux, iOS and Android, often with the same source code. The first FMX version was
released with XE2, with XE3 followed a extended FMX version, which was often called "FireMonkey
2".

Since then FMX has been heavily reworked with every Delphi version, so the developer often had to
make a number of adjustments when switching to the latest FMX version.

Fortunately the functionality of FMX increased with every new Delphi-version, so that today we have a
very powerful framework, with which you can do not only everything that is possible with the VCL,
but also much more. All components are freely rotatable and individually scalable. There are also a
number of 3D components that can be used to write 3D programs. Finally, the effects and animations
are to be mentioned, which give FMX another unique feature

The representation of the components is supported by the GPU (Graphic Processing Unit), which
makes the output faster and more fluid. Under Windows, the GPU is addressed with DirectX, under
Mac with OpenGL and under iOS / Android with OpenGL / ES.

History
FireMonkey was originally developed by Eugene Kryukov (company KSDEV, Uland-UDE in Russia).
The product was known as VGScene. In 2011 the framework was purchased by Embarcadero and
integrated in Delphi XE2 as a new framework, in addition to VCL. From XE3 it is only from the
enterprise version on an integral part of Delphi, for the professional version you have to purchase it
separately as a so-called mobile pack.

Since Delphi 10 Berlin you can create 64-bit applications for Windows and also for IOS, for Mac and
Android it remains so far with the 32-bit version. Starting with Delphi 10.2 Tokyo, the Linux platform
(64-bit) is also supported, but only for the creation of console applications.

Outlook
In relation to the VCL platform, the main innovations and enhancements are found now at FMX. There
are always new components and features added to the components. In this respect I see here the future
of software development with Delphi.

Since May 2017, with the "FMXLinux" Add-on, we have also a possibility to develop Linux
applications for the desktop with Delphi (more info on fmxlinux: http://www.fmxlinux.com).
So, do not be surprised if you already see some screenshots of Linux desktop programs here in the
book. These were created with Delphi and the FMXLinux Add-on and look just better, as result
displays in console windows.
Chapter 2: How to use the FireMonkey
components
Section 1: Getting Started
My books for XE7 and earlier were more about removing the stumbling blocks if you plan to convert
your previously developed VCL components into a FireMonkey program or start new projects with
FireMonkey.

This is still the subject of the book, but the new components and abilities of the FMX framework are
described more extensively, for example in the area of 3D programming.

To get a first impression, check out the included program examples, which give a good overview. For
example, open the included demo "ControlsDemo". The FireMonkey demos can be found in Delphi
Tokyo via the start page:

The folder "Object Pascal" will take you to the sub-folder "Multi-Device Samples". In the subfolder
"User Interface" you will find 28 sample projects which gives you a good overview to FMX.

If you want to get help in programming to convert existing VCL applications, you can use the Mida
converter.

In the download section of the Embarcadero Developer Network, the MIDA-BASIC version is offered
free of charge for download.

In the paid Pro version (from approx. 90,-- Euro) also third components (for example TMS
components) are converted. In addition, source code is also adapted as far as possible (
http://www.midaconverter.com).

Note: In my book for development with Delphi XE7, there was a detailed section on the use of the
Mida Converter. For reasons of space and to avoid repetitions, I have removed it for this book, but if
necessary, my book for XE7 with the corresponding content for a small amount of Euro is available at
Amazon as an e-book.
Section 2: New FireMonkey project
Of course, it comes to the question of how and when to go into the individual differences between the
VCL and FireMonkey.

The best way is probably to simply describe the usual steps of program development and emphasize the
differences between the common VCL components and the FireMonkey components at the relevant
points.

FireMonkey desktop application (Multi Device Application)


As with any Windows project , you start developing a FireMonkey program to run both on Windows,
Mac or Linux under the "File" menu with the command "New Multi-Device Application" or very
comfortable directly over the start page:

There will be shown a templates dialog, which allows you to generate either an "Blank Application", a
"3D application" or an application that already has certain controls in the form:

Normally you choose the "Blank Application" when no special 3D-functionality is needed. The other
templates would be used rather for mobile applications (i.e. iOS and Android). But we want to develop
a normal desktop application here, so please select the "Blank Application". Then click OK.

Result:

The "Target Platforms" on the right side of the node are by default collapsed. Being unfolded, it looks
like this:
The 32-bit Windows platform is enabled by default. If you want to compile the program now, you will
get a running 32-bit Windows program. Delphi has also automatically added a MAC OS platform as
well as the mobile platforms iOS and Android (but you can ignore the mobile platforms if you don't
need it).

You can simply select one of the listed target platforms by double clicking and then compile it. And
you will have then a corresponding target platform - application. The program will be able actually to
run on a MAC or a mobile device, but you still have to set up the environment according to Delphi. It
will be described later in this book how to do this for the MAC and also for Linux.

Without the additional "FmxLinux" framework, you can not add a Linux platform for a desktop
application. If you had created a console application, you could also create an application for Linux, as
described in Chapter 5 below.

Using the Multi Device Designer (Fire UI)


With the Multi-Device-Designer you can get started to add components, set their properties and write
event handlers to interact with the user. If you want to develop a cross-platform project, it is generally
recommended to add at the very beginning a view for the MAC OS Platform so that you can also
compile the MAC-version from time to time and see how it works.
However, you should work as much as possible with the view "Master" and only use the other views to
make specific settings for the relevant operating system.

With the drop-down list on the left side, you can also use the Master View with different styles.

In the various created views (OS X, Windows, etc.) you can add components and change their
properties, but not delete them. If you want to remove a component, you have to activate the master
view and then remove it. Consequently, it is then removed from the other views. The same procedure
can be applied to changing the name of a component: Here you can - because the name must be unique
- change it only in the Master View.

The use of the Multi Device Designer make it much easier to develop cross-compiling applications. For
example, the buttons in dialog boxes are located on the left side of the bottom on Windows, and on
right side of the bottom under the MAC OS (and Linux). Also, buttons under the MAC OS normally do
not have graphics.

Here is the view of a dialog from my accounting program that is available for Windows , the MAC OS
X and now also for Linux (with the help of the FMXLinux):
At the bottom left corner are the buttons with the images. Now have a look at the MAC OS X view:

Here, the buttons are moved to the right side and the images in the Object-Inspector are set to visible =
false. The checkbox has been moved to the left side.
The advantage is that you can use the designer here and do not have to make any programmatic
changes for the dialog at runtime. In the past, that was usual and a rather time-consuming working-
method.

Form inheritance with the Multi Device Designer


Also, it is a space-saving method because Delphi creates for each view a separate form and only this is
included for the correspondent platform. When the files are created, in a file manager it looks like the
following:

FMandant.fmx is the master form file. This file serves as a master for the respective generated platform.
If we have only this master form and want to create a program for the MAC OS X, we can use this
master form only.

Here we want to create a Windows program and also a version for the MAC OS X. Therefore we have
chosen here "OS X Desktop" in the right drop-down list so that Delphi will create a form for this
platform. This is the file "FMandant.Macintosh.fmx".

The specific platform forms work on the principle of form inheritance (similar to the "TFrame"). When
we open the MAC form in a text editor, it looks like this:

While the form master-file consists of 250 lines, the derived MAC form-file has only 42 lines. The
reason is that only the changes in relation to the recognized master form will be saved here. The button
is now on the right side instead of the left. Therefore it has a different horizontal position and this value
(in green here) will be detected. The image symbol in the button should be visible under Windows, but
not under the MAC OS X, which is also noted accordingly (blue background).

So you can simply modify for a particular platform the position, the color, the size or other contents
(e.g. text of labels, buttons).

Reverting to inherited settings


If you have made changes of a control on a different platform view, you can individually or completely
reverse the settings to the values in the master view. Example:

The button on the MAC view has been moved to the right side and the text was changed to "MAC".

If you want to restore all settings of the buttons back to the initial state, click on the button with the
right mouse and select the command "Revert to Inherited":

The button is then in the MAC view again on the left side and has again the caption "Button1".

If you want, for example, only to restore the original text of the button, click once on the button with
the left mouse button to select it. Then, in the Object Inspector, right-click on the word "Text" (on the
left side and not in the edit box) and select the command "Revert to inherited".

The button stays on the right, but his label was changed from "MAC" to "Button1".
So it is possible to bring back only individual properties to the initial state, if necessary.

Creating a Platform-specific event handler with the Multi-Device Designer


However, the Multi-Device Designer offers a further simplification: Until now, you have the source
code in an OnClick event and usually edit it in a event procedure to serve different platforms. Normally
you use the IFDEF switch to make the source code compilable for either Windows or the Mac. Even it
might actually be the right way, if you have only a few different platform-specific statements in the
code. But if the code is very different for each platform, then you have a lot of text in the event handler,
which can become unmanageable. It is then possible here to create directly a separate event handler for
the MAC platform. So if you have created an OnClick event already in the master, go to the View
"MAC OS Desktop".

Then select the button and add in the OnClick event simply the words "Mac" as viewable in the image
above, then press the key "Enter". Delphi will create a new empty event handler immediately, so that
you can fill it with specific source code for the MAC.

Small drawback: If the source code contains platform-specific calls, you still have to work with IFDEF,
however. But that is much easier now, because you can surround the whole inner frame of the
procedure with {$ IFDEF MACOS} ... {$ ENDIF}.

In general, it is always a matter of the case which approach is the better.

There is another innovation (since XE7): Among a number of components, you can select initial
settings for certain properties - e.g. the property "PlatformDefault" instead of "Top" or "Bottom" for the
TTabPosition in a TabControl.

Even if you have only one Master View and do not generate platform-specific views, everything would
be on its proper place on all platforms because of the "Platform Default". The functionality mentioned
in this example is yet more relevant for iOS and Android, because the tabs there should be at the
bottom or at the top.
Section 3: A first FMX-program (analog clock)
Here we want to start with a simple program, which demonstrates at the same time that you can write a
full-fledged program with a very few lines of code.

This is an analog clock, which will look like this:

One could program the app even with only 3 lines of code (therefore the title of the program window).
On YouTube you can see this (in German language):

https://youtu.be/QL5SdJAiWGE

We will need here some more lines, but with that we can reduce design work, and we can learn about
using style names and simply copying existing FireMonkey objects.

Here now step by step:

1. Create a new FireMonkey Multi-Device-Application.

2. Drag and Drop a TCircle-component into the form and set the values for width and height each
time to 400 Pixel. Set the property "Fill", "Color" to "White".

3. Insert a TLayout-component into the TCircle-component and set the height to 400 and the width to
50 Pixels. For "Align" you select "center". Name the property "StyleName" to "clocknumber".

4. Insert a TText-component into the TLayout-component and set the value for "Text" to "12",
under "Textsettings" you select "Bold" and for the size of the text you set 24 points.
The preliminary result looks like this: The structure list looks like this:
If it looks different on your PC, you can move the elements by drag and drop to the right position.

We could go on and copy this TLayout ("LayoutZiffer") 11 times and increase the value for
RotationAngle each time by 30 degrees and reduce the value for "RotationAngle" for the TText-
Element by 30 degrees (like I have demonstrated it in the mentioned video above).

But instead of this we reach this with a few lines of code:

5. Place the following text into the OnCreate-event of the form:

procedure TForm24.FormCreate(Sender: TObject);


var
L: Integer; LA : TLayout; T: TText;
begin
for L := 1 to 11 do begin

// create a copy of the layouts, with child-objects included


LA := TLayout (circle1.FindStyleResource
('clocknumber', true));
if LA <> NIL then begin
LA.Parent := circle1;
LA.RotationAngle := L * 30;

// We have only one child, the TText-component


T := TText (LA.Children[0]);
if T <> NIL then begin
T.Text := L.ToString;
T.RotationAngle := (L*30) * -1;
end;
end;
end;

end;
This line here is very important:

LA := TLayout (circle1.FindStyleResource('clocknumber', true));

Here we get as function result the layout which has the stylename "clocknumber". This is the name that
we have set before under "StyleName" of the TLayout-component.

The second parameter of the function call determines whether we will get back a link to the original
("False") or if there will be created a copy of the object and we receive a link to that copy (=True).

We make the call with "True", so we will get as result a link to the new created copy of the object. In
comparison to the original the copy does not have a parent yet, so we have to assign one:

LA.Parent := circle1.

After that, we assign the needed value for "RotationAngle" to the new created TLayout. But how access
the TText-element? We don't have to create it manually, because it was a child-element of the original
TLayout. And by creating a copy of the TLayout it will create also all the included child-objects.
Because the layout had only one element, we can access it with

T := TText (LA.Children[0]);

and can make the needed assignments.

When we start the program, it looks like this at runtime:

So you can see immediately how useful the property "RotationAngle" is.

6. Now we insert 3 TRoundRects so that they are standing vertical and the end is exactly centered.

a. Name the first RoundRect "rrHour" and enter the following values:

Position.x: 191; Position .y: 94; Height: 110; Width: 18;

b. Name the second RoundRect "rrMin" and enter the following values:

Position.x: 193; Position .y: 55; Height: 151; Width: 14;


c. At the end you name the third RoundRect "rrSec" and enter the following:

Position.x: 196; Position .y: 40; Height: 164; Width: 8;

For the second hand you choose for "Fill", "Color" the value "red", for the other RoundRects
"Black".

Also, make sure that the RoundRects are within the circle and that the minute hand is above the
hour hand, and the second hand over the minute hand (if necessary, go to the Delphi edit menu
and choose "Bring to Front" to change the z-order).

7. For each of the RoundRects you enter the values:

RotationCenter.x: 0,5; RotationCenter.y: 1;

8. Finally, add another circle within the circle (color "black") and set the following values:

Position.x: 183; Position .y: 183; Height: 34; Width: 34;

In the structure list it looks like this:

And this is the form-view:


9. Now add a TTimer component (interval = 1000 and Enabled = True) and enter the following code
in the OnTimer event:

procedure TForm24.Timer1Timer(Sender: TObject);


begin
rrHour.RotationAngle :=
HourOf(Now) + MinuteOf(Now)/ 2;
rrMin.RotationAngle := 6 * MinuteOf(Now);
rrSec.RotationAngle := 6 * SecondOf(Now);
end;

Depending on the value for hour, minute or second, the degree value for the component is calculated
here.

Now the program is finished, you can run it and look at the result.

When you run the application on the Mac, the result looks like this:

Even when this book is not about mobile programming: if you select "Android" as Target Platform in
the Project-Manager and run the app on a via USB connected Android-device, it runs perfect there:
Note: To have the clock always to be displayed centered in the form, it is necessary to insert another
TLayout into the form. Then you have to align the TCircle to this layout centered (without using the
"Align" property) and then set the layout via "Align" to "Center".

Here you can download the demo:

http://www.devpage.de/download/fmbook4/AnalogClock.zip
Section 4: Selected FireMonkey components
Based on the individual components and features that you normally use here, let’s look into the
individual components one by one.

TButton (with Trimming)


Unfortunately, the FireMonkey TButton has not so many skills, such as this one from the VCL who has
gained enormous possibilities in the last few years.

But there is something that the VCL button does not have: the property "Trimming", with which you
can specify how to handle the displaying of the text if it is too long to display.

The default setting is now "ttCharacter". When the text is too long it will be displayed with a letter and
3 points between the outer button border. For example in the text "Repetition" like "Repet ...".

If you select "ttNone", it behaves as described in the VCL, which means the text is displayed until the
border of the button, then cut off.

With "ttWord", if words are available, the border is set as full word-border.
So "Print and Close" would be shortened to "Print and ..." when the word "Close" does not fit in the
display area of the button.

Finally, a TButton can have also a constantly pressed-down status. You can do this by setting the
property "IsPressed". At the same time the property "StayPressed" must be enabled.

Since Delphi 10.1 Berlin there is also the property "Hint", which is activated by default via
"ShowHint".

TEdit (without PasswordChar)


If an edit-field does not display its contents, you will not reach this as in the VCL with

Edit.passwordchar: = '#';

but with
Edit.password: = True;

New since XE7: There is a new feature "ControlType", which is set by default to "Styled". If you set
this to "Platform", the native element is used on the platform in your form - currently only on iOS and
Windows. This can be an advantage e.g. when using a screen reader. Or just when you want to get a
100% platform appearance. If you choose the option, it shows this control changes already at the design
time:
In addition to the TEdit, the "ControlType" property is also available for the TMemo or the TCalendar
control (the latter only for iOS). I assume that further controls will be provided with this property in
future. Therefore, if you use iOS and Android next to the MAC OS platform, this maybe an important
information.
TExpander
You can compare the TExpander component with the TCategoryPanel from the VCL. Here too, you
can insert different components into the expander and open or close the component by setting
"IsExpanded".

TForm (furthermore with caption)


While actually all FireMonkey components are not using the "caption" but the "Text" property, the
TForm goes on having the property "caption".

For FireMonkey forms there is (currently) no "KeyPreview" property as in the VCL form. However,
the TForm can still get the FormKeyDown-info even if another control has the focus. If you write a
program for which it is important to have a central location where the keyboard input will be checked,
you could use the KeyEvent routine of the focused control and then forward it to the form.

With the property "FullScreen" you can let the form start directly in full screen mode. This property
should not be confused with "WindowState" where you can, as usual, bring the window to the
maximum size of the free desktop area (wsMaximize) like under Windows. With the
"ShowFullScreenIcon" option, you can activate the full screen icon on the MAC (it has no effect under
Windows because there is no full screen icon for the window area). At runtime you can switch between
the two modes - both under Windows and the MAC OS X - with "FullScreen: = True" or "FullScreen:
= false" .

TFrame
Since XE 4 frames are available, it basically works like in Windows. Under "File", "New", "Other"
menu, you can first create a TFrame. Then you can click on the frame icon in the component palette
and attach to the form a region in which the frame is to be inserted. This will open a dialog where you
can select one of the available frames (if you have not done so before, you have to add an existing
frame to the project).

TPanel (no caption or text-property, align with qualifier)


A speciality has the component "TPanel" which has neither the property "Caption" nor the property
"Text". To achieve the results known under Windows, you have to paste a TLabel component into it.

If you want to align a TPanel object at runtime, it is not enough (as in the VCL form), to set the align-
property for example to "alClient". Rather, you have to use a qualifier, the term "TAlignLayout", before
that.

Example: Panel.Align: = TAlignLayout.alClient;

TRectangle
Works similar to a TPanel, but you have to use the Fill property to apply a background color and use
the Stroke property to specify a particular type of border.

TCheckbox, TRadioButton (IsChecked)


When using the TCheckbox- component or the TRadioButton, you do not even need to search for the
property "Checked", but just for "IsChecked" instead.

Like the (newer) VCL components, also the FireMonkey components with text elements have the
property "WordWrap", which is actually quite useful. Like all FireMonkey components, the discussed
controls here, has several features to represent its appearance ("Styles", e.g. "StyledSettings",
"StyleLookup" and "StyleName", but more later).

TGroupBox with TRadioButtons


In the VCL, you know the TRadioGroup component, where you can use the "RadioGroup.Itemindex"
property to determine the activated radio button. This component does not exist under FMX, but you
can use the TGroupBox and place the desired TRadioButtons there. In order for the RadioButtons to be
grouped, you must assign a uniform name for each RadioButton within the GroupBox to the
"GroupName" property:

TSwitch (alternative to the TCheckbox)


This component has no equivalent in the VCL. Under FireMonkey, you can use the TSwitch as an
alternative to TCheckBox to represent an ON or OFF state. These switches can be found very often in
mobile applications, but also in desktop applications now.

The component looks like this:

TImage (WrapMode for display-modi)


With the VCL TImage component, you can use the property "Picture" to load an image. You can
influence the way to displaying the picture with the properties "Stretch" and "Proportional". Under
FireMonkey, there is also a TImage component. Here, you can use the property "MultiResBitmap" to
load the image into the component:

This means, you can save as many bitmaps as you need for several resolutions to use. If you call the
wizard for editing here, you will get the following dialog:

Here you can add individual bitmaps by clicking on the first icon on left side at top. Then you can click
on the open icon in the text input line to select an image-file. By default, the preview will not be
displayed on the right side of the dialog. This can be enabled by clicking on the magnifying icon in the
toolbar.

Note: If you click on the green check arrow, a query will arise, asking whether the information
available at design time should be deleted. If you confirm this with "Yes", the name of the source
directory of the bitmaps will no longer be displayed here. As long as you still need this information,
you should close the dialog with the red close button.

At runtime, you will access the individual bitmaps via the items property, e.g. as follows:

MyBitmap.assign (Image1.MultiResBitmap.Items [0] .bitmap);

It is not mandatory to save Bitmaps with various resolutions in this component. You can also save
different pictures with same resolution, and in fact, achieve a replacement for the VCL ImageList.
However, you cannot enter the same scale value two times.

It is convenient that you can drag from Windows Explorer or any other file manager files directly into
this image editor.
The property "WrapMode" can be used to influence the type of presentation.

The following modes are available here:

WrapMode:
iwFit (Default) - adjusts the image to the square of the component where the image proportions (the ratio between the width
and height) are maintained.
iwOriginal Displays the image with the original dimensions.
iwStretch Enlarges the image so that it fills the entire rectangle of the component
iwTile Tiles the image so that it fills the whole rectangle of the component.
iwCenter Center the image horizontally and vertically in the middle of the component

iwPlace Fit the image to the TImage rectangle. If the width or height of the image is larger than the corresponding dimension of
the TImage rectangle, the image will be scaled to fit the TImage rectangle while maintaining the image proportions (the
width to height ratio). The resulting image is set to the center of the TImage rectangle. Place only scales images down
and never enlarges them.

TImageControl (automatic scaling)


A graphic loaded by using the property "bitmap" is scaled automatically. When you click at runtime on
the displayed image, a file-open dialog will be shown There you can load an image into the component.
If this default behavior is not wanted the TImageControl, you can avoid this with the following
assignment:

Imagecontrol1.EnableOpenDialog: = false;

TImageViewer (pictures scaled by user)


The TImageViewer is a very interesting component. Again, you load the graphics with the property
"bitmap" into the component. By default, the properties "MouseScaling" and "MouseTracking" are
activated here. As a result, the user can scale the size of the graphic with the mouse wheel, when the
mouse is over the displayed element. If you want to disable this behavior, you must disable the last-
mentioned properties.

TImageViewer (to use with the LiveBindings-Designer)


The TImageViewer is ideal for producing a dynamic link to another component, by using the so-called
"live bindings". Let's see how it works with the track bar component here:
In the Object Inspector, we have clicked on "Bind visually" in the property "LiveBindings". This will
automatically show the "LiveBindings-Designer". You can then use the property "BitmapScale" from
the "ImageViewer1" to connect it with the "Value" property from the "Trackbar1" component.

Just click on the 3 dots on the ImageViewer1 element, it will show the dialog "Bindable Members".
Then select the property "BitmapScale" and confirm with "OK":

Then click on the "Value" at the "Trackbar1" with the mouse and drag it onto the "BitmapScale" of
"ImageViewer1" and then release the mouse button.
Result:

If you do this for the first time, you can also use the "LiveBindings-Expert". To do this, click on the
item "ImageViewer1" in the LiveBindings designer. Then click on the icon with the magic wand. This
opens a dialog where you can specify the settings:

Select "Link a component property with a control," and then click on "Next".

In the drop-down list select the component "ImageViewer1" and then "BitmapScale" as property:

Then click on "Next" and select on the next page the control "Trackbar1". Then click on "Finish".
In the designer you can see now, the property "BitmapScale" from the "ImageViewer1" is linked to the
"Value" property of "Trackbar1".

The property "BitmapScale" with value of 1 stands for 100% image size. So set the property "Max" of
the "Trackbar1" component also to "1". If you want allow magnifications, set it to e.g. 2 or 3. At
runtime, you can change the display size of the graphic by using the slider.

It's an easy job which doesn't need to create a single line of source code for it. Below is the runtime
result:

TLabel (New property FontColor)


If you wanted to change the color of a TLabel, for example, in the first version of FireMonkey, you had
to create a custom style and then set the property "Fill" of the text element with the desired color. From
FireMonkey 2 on, this is much easier. Just use the property in the Object Inspector "TextSettings" and
then "FontColor" and select the desired color.

Note: The selection in FontColor only works when FontColor is disabled in the "StyledSettings"
options:

I am not going to discuss whether this mixing of the various settings is useful or not. Anyway, you
should know this, otherwise you may search long for the answer why the color you have chosen in
"FontColor" is not displayed. The property "StyledSettings" refers to the current style. This can be the
default style and, however, can also be a user-defined style. The settings of the style override any other
settings when it is enabled here in StyledSettings.

TPathLabel
This component (there is nothing comparable under the VCL) does not display text, but "path data", i.e.
vector data, similar to the TPath component.

TPath
This component also displays vector data, but it has the additional "Fill" property, which means that the
filled surfaces can have a (uniform) color background.

TImageList
The extremely useful TImageList, which serves as a container for graphics and can be edited via a
dialog at design time, has been available also for FireMonkey since XE8. Compared to the VCL-
TImageList, the FMX version is significantly more powerful. Here is a screenshot of the dialog for
adding and editing the TImageList:
The "Sources of Images" are displayed on the left. These can be accessed at runtime via
"Imagelist1.source".

Example:
procedure TForm27.Grid1GetValue(Sender: TObject; const ACol, ARow: Integer;
var Value: TValue);
var
SizeF: TSizeF;
begin
SizeF.cx := 16;
SizeF.cy := 16;

case ACol of
0: if ImageList1.BitmapExists (ARow) then begin
Value := ImageList1.Bitmap (SizeF, ARow);
end;
1: Value := ARow.ToString;
end;

end;

The result looks like this:

Design-Time:

Run-Time:
You can also change the ImageList at runtime, e.g. adding a bitmap. Here is a demo. Note that you
have to specify the properties for the "source" and for the "destination" as well:

procedure AddImageToImgList (ImageList1: TImageList; AFile: string);


var
bm : TBitmap;
Asource: TCustomSourceItem;
cd, dItem: TCustomDestinationItem;
item: TCustombitmapItem;
Layer: TLayer;
sizew, sizeh: Integer;
begin
if FileExists (AFile) then begin
ImageList1.BeginUpdate;

bm := Tbitmap.Create;
bm.LoadFromFile(AFile);

sizew := bm.Width;
sizeh := bm.Height;

ASource := ImageList1.Source.Add;
Asource.Name := ChangeFileExt (ExtractFileName
(AFile), '');

Item := ASource.MultiResBitmap.Add;

if sizew <> 16 then begin


item.MultiResBitmap.Width := sizew;
item.MultiResBitmap.height := sizeh;
end;

item.Bitmap.Assign(bm);
bm.free;

cd:=imageList1.Destination.Add;

Layer := cd.Layers.Add;
Layer.Name := ASource.Name;

if sizew <> 16 then begin


Layer.SourceRect.Right := Sizew;
Layer.SourceRect.Bottom := sizeh;
end;

ImageList1.EndUpdate;
end;
end;
The TImageList is also important if you want to ensure that your program displays an image with a
higher resolution at higher screen resolutions.

You can store multiple resolutions for each (MultiRes-) bitmap, which are automatically used by FMX
for the correct resolution.

A simple double-click on the icon in the middle:

opens the MultiResBitmap dialog directly, or you can open the MultiResBitmap dialog by double-
clicking on list on the left-hand side:

Here are the values for "scaling" important, for the selection of the right icons at runtime.

TListBox (not TCheckListbox, but ShowCheckboxes)


Don't waste your time to search a TCheckListBox as there is not such a component for FireMonkey. If
you want to display a TListBox with checkboxes, select instead the "ShowCheckBoxes" option. Quite
interesting is the property "ListStyle". With that you can display the entries in the list box next to each
other (lshorizontal).

Here's an example:

Although the listbox works in general like the VCL component (e.g. Items.LoadfromFile, etc.), it is to
distinguish whether you are working at design time or run time with "Items" or "ListItems":

In the listbox above on the left side, the entries were added by using the "Strings" property of the
listbox. In the listbox on the right I have right-clicked with the mouse and then selected the command
"Add ListBoxItem".

As you can see in the structure-overview, only the second listbox has individual entries for each
"ListBoxItems". So only in the second listbox you can select the individual ListBoxItems by mouse-
click and make the appropriate changes to the settings in the Objectinspector.

Warning: if you now edit the entries in the second listbox by the property "Strings" and confirm the
changes, all ListBoxItems would be converted to normal strings and all previous settings to the
individual ListBoxItems would be lost. Therefore you have to be careful of how to process it here.
Here is an example of how to access the items. At design time:

At runtime, you can address the individual items of a TListBox both on the property "ltems" (= list of
strings) and on the properties of the "ListItems" (list of TListboxItems).
See the example source code with this result:

procedure TForm19.FormCreate(Sender: TObject);


begin
Listbox1.Items[0] := 'Text set via "Items"-property';
Listbox1.ListItems[1].Text := 'Text set via "ListItems"';
Listbox2.Items[1] := 'Text set with "Items"-property';
end;

To test, whether in a ListBoxItem the property "IsChecked" is set, do it like this:


if Listbox1.items[0].isChecked then ...
Listbox1.items[0].isChecked := True;

Enhancement of the Listbox-Items


Beginning with Delphi XE4: If you right-click with the mouse on the listbox, you can not add only
simple text entries. As you can see here in the pop-up menu, you have the choice between several
entries:

With a TListboxGroupHeader, you can use the subheadings in the listbox. The TSearchBox is very
useful. When you enter text there, the ListboxItems will be filtered and shows only the matching
entries. For example, if the list box offer quite a lot of options, the user can enter a keyword and
immediately receives the appropriate setting option. That is something comfortable, that the VCL
listbox does not offer!

Here is a list box with a group header and a SearchBox:

And this is the listbox after the user has input some text into the SearchBox:
If you click in the SearchBox on the ClearEdit button, it clears the contents of the text SearchBox. As
the filtering happens all automatically, you need not write a single line of source code!

No OwnerDraw for listboxes


Since there is no "OnDrawItem" event for the listbox, you have to work with the appropriate settings
for the ListBoxItem so as to get the different representation (but note: There are existing events like
"OnPaint" and "OnPainting" for the ListBox and for every ListBoxItem, which you can use in special
cases).

Most of the time, it is sufficient to get along with the pre-installed properties of the TListBoxItem, since
it offers, in addition to the text property, a number of other uses:

Under Bitmap, you can assign a bitmap at design time or at runtime.

Starting with Delphi XE8, the ListBox has an entry "Images", where you can create a link to a
TImageList (which only exists since XE8). Accordingly, each LitboxItem has an entry "ImageIndex",
where you can also create a reference to an image from the ImageList at design time or at runtime.

The "Detail" property stands for another text entry, which can be displayed within the TListBox item at
different locations, depending on whether you choose "leftdetail" or "rightdetail" for the "itemstyle".

Under Accessory you can choose the following:


If the text from the "Detail" property is to be displayed, select "aDetail" for "Accessory".

And as "ItemStyle", select a style that contains the term "detail":

In this way you can solve most standard tasks with the integrated skills. Under the VCL you have to
draw the desired output yourself by "Ownerdraw".

Here is a step by step example of how to output text in a listbox with different colors, with an icon and
detail information:
This is the finished program. When you enter text in the search box, the contents of the listbox are
filtered out accordingly. While this functionality is set up very quickly under FMX, you would have a
lot of effort under the VCL to reach the same result.

1. Create a new multi-device project.

2. Add a TListox, name it "lbEvents", and select "listboxitemrightdetail" for "Itemstyle" (under
DefaultItemStyles).

3. Add a TImageList and add three icons:


4. Set the "Images" value of the listbox to "ImageList1".

5. Add 3 buttons, and assign the text property "Add Info", "Add Warning" and "Add Error".

6. Add a search box to the listbox by right-clicking:

Your form looks now like this:


7. Create the following procedure, and for the OnClick events of the buttons, the following events:

procedure AddInfoItem(lb: TListBox; imgidx: Integer; txt: string; AColor: TAlphaColor; ADetail: string = '-');
var
lbi: TListBoxItem;
begin
lbi := TListBoxItem.Create(lb);
//lbi.StyleLookup := 'ListBoxItem1Style1';
lbi.ItemData.Text := txt;
lbi.ItemData.Detail := Adetail;
lbi.ImageIndex := imgidx;
lbi.StyledSettings := lbi.StyledSettings -
[TStyledSetting.Fontcolor];
lbi.TextSettings.FontColor := AColor;
lb.AddObject(lbi);
end;

procedure TForm24.Button1Click(Sender: TObject);


begin
AddInfoItem (lbEvents, 0, 'Information here...',
TAlphaColorrec.Black);
end;

procedure TForm24.Button2Click(Sender: TObject);


begin
AddInfoItem (lbEvents, 1, 'Warning here...',
TAlphaColorrec.blue, 'Battery is at 5%!');
end;

procedure TForm24.Button3Click(Sender: TObject);


begin
AddInfoItem (lbEvents, 2, 'Error here...',
TAlphaColorrec.red, 'Disk is damaged!!');
end;
This completes the program and you can run it.

Two notes about the "AddInfoItem" procedure:

1. To make it possible that the text can be displayed in color, the "FontColor" property must be
removed from the StyledSettings.

2. To the commented out line: You could also arrange the existing elements of the ListboxItem
differently for the display. For example, let the detail text under the normal text and the text a little
closer to the icon. To do so:

a) Change the value for "ItemHeight" to 40.

b) Temporarily add a TListboxItem to the listbox, right-click and select "Add custom style".

c) In the structure list under "ListBoxItemStyle1", set the value for Height to 40 and move the
"detail" element to the first layout, set Align to "Bottom" and choose for Height 16:

If you enable the commented out line above, and run the program again, the result looks like this:
You can download the demo here:

http://www.devpage.de/download/fmbook4/Eventlist.zip

But you are not at the end of the possibilities. You can also add additional text, images, or other
elements to the "Style" for the ListboxItem.

To do this, you can look at an example provided by Delphi: "CustomListbox", which you usually find
here:

C:\Users\Ihr AnwenderName\Documents\Embarcadero\Studio\19.0\Samples\Object Pascal\Multi-Device Samples\User


Interface\CustomListBox

The compiled example looks like this:

All Components (except the form)


While the TForm contains properties such as "Left" and "Top", all other components does not have
that. Here you can use the property "Position" with the X and Y values for the left and top position. It
should also be noted that none of them are integer values but of the type "extended".
Several Components (Properties with additional type-qualifying)
In many FMX components, you can use the properties like in the VCL components . In a TMemo you
can use the property "Align" to assign the value "alClient". However, at runtime you must provide
additional type qualifiers.

Not: memo1.Align := alClient;


But: memo1.Align := TAlignLayout.alClient;

Even not: memo1.FontColor := clRed;


But: memo1.FontColor := TAlphaColors.Red;

The well-known "clColorName" of the VLC components works here under FireMonkey always
without "cl", but with "TAlphaColors" before it.

So if you're going to make common assignments and the program does not accept that, try to figure out
whether an additional type qualifier is required.

TAlphaColors itself is declared in the unit "System.UITypes":

Type
TAlphaColors = TAlphaColorRec;

But once you know this, it is OK.

However, there's also a trick to bypass the use of this qualifier. Simply link the unit "System.UIConsts",
then you can use color values such as "claRed," "claBlack".

Uses
System.UIConsts;
...
memo1.FontColor := claRed; // Alternative:

TMenuItem)
Since Delphi-XE8, in the TMenuItem the "Bitmap" property has the "ImageIndex" property, so you can
assign a symbol to a linked TImageList. If you create a Cross-Platform application here, you should
consider, that other platforms then windows normally doesn't use bitmaps in menus (MAC and Linux).
So you can add an "OSX Desktop" view with the Multi-Device Designer and remove the "Items" item
for the menu in this view. Or you remove the icons from the menu at runtime.

It should also be noted, that in the FireMonkey component the setting "IsChecked" is used instead of
"Checked".

TMainMenu (Handling MAC Menus)


If you compile your program, you have usually the "File" menu in the Windows version. In the
compiled version for the MAC the name of the program (that is the ".exe file" - without extension) is
set from the first menu-name. In this menu we have usually only commands to exit the program or a
command to call a settings dialog or a command "Information about ...". So it is recommended to create
another menu with that name as the first entry:

In the Windows version you have to let the "TEditor" invisible. In the MAC version, you make it
visible and hide the "Exit TEditor" from the "File" menu. Here is the implementation in the source
code:

procedure TF_Main.FormCreate(Sender: TObject);


begin
{$IFDEF MACOS}
mTEditor.Visible := True;
mFileExit.Visible := false; // from Menu File
{$ENDIF}
//...
end;

And so it looks like the following in the Mac:

You can use this solution if you have only one master view and no specific view for the MAC platform.
However, if you have a specific form for the MAC, you can edit the properties of the menu over the
Object Inspector. In the "OS X Desktop" view you set the text for the "TEditor"-menu to visible and
hide the "Exit"-entry under file-menu.

The name of the first visible menu entry is always replaced by the program name (executable) under
Mac OS. Therefore, you should choose the project name of the application carefully.

TMemo (CaretPosition, no Modified, FindNext-replacement)


Under Windows, you can determine the current row and column in the memo as follows:

Line: = SendMessage (memo.handle, EM_LINEFROMCHAR, Memo.SelStart, 0);


column: = Memo.SelStart - SendMessage (memo.handle, EM_LINEINDEX, line, 0);

Under FireMonkey, it works like that (use property "caret position"):


line := Memo.CaretPosition.Line +1;
column: = Memo.CaretPosition.Pos+1;

The property of the VCL component "Modified" does not exist under FireMonkey. You must, instead,
manage it by yourself under the event "OnChange" or "OnChangeTracking". Here you can set a
variable (e.g. the "Tag" variable of the TMemo), which records whether the memo has been altered or
not. You can reset it later when you have saved the contents of the memo into a file.

Clipboard.HasFormat(CF_Text) -replacement
Under the VCL there is the "Clipboard", but not under FireMonkey. For the FireMonkey memo
component we have functions such as "Memo.CopyToClipboard" or "Memo.CutToClipBoard" and
"Memo.PasteFromClipboard". But how to determine if the clipboard contains text (for example, to
make a button enabled)? Here you have to use a PlatformService.

Example (unit "FMX.Platform" is required):


procedure TF_Main.Timer1Timer(Sender: TObject);
var
s: String;
ClipService: IFMXClipboardService;
begin
try
if TPlatformServices.Current.
SupportsPlatformService(IFMXClipboardService,
IInterface(ClipService))
then begin
try
s := ClipService.GetClipboard.ToString;
bnPaste.Enabled := (s <> '') and (s <> '(empty)');
finally
end;
end;
finally
end;
end;

Spellchecking
A whole new possibility arises from XE5 on, with the ability to make a spell-check for the text entered.

And it works with a service that is automatically available under the MAC .

You enable or disable the spell-checker with the Boolean variable "CheckSpelling" (either in the
Objectinspector or at runtime). For the spell checking, a platform service will be used. It could be a
good idea to check first whether the service for the Platform currently used is available. To query this,
you need to include the units "FMX.Platform" and "FMX.SpellChecker". The query looks like this:

IF TPlatformServices.Current.SupportsPlatformService(IFMXSpellCheckerService) then begin


memo1.CheckSpelling := True;
end;

For Windows, no spell checker service is available. But in principle, it should not be difficult to
implement an own one. As a template, you can take the implementation for the MAC platform and
rewrite it accordingly for Windows.

FindNext-Replacement
Unfortunately there is not the most useful function

"Memo.FindNext (Memo.text, StartPos, EndPos, SearchOptions)"

for the FireMonkey Memo component available as in the VCL. Even among the standard actions, there
is no corresponding action. So you have to use your own solution. Subsequently, I demonstrate a
simple approach which I have used in my program "TEditor" for the MAC and Windows. Here is a
simplified version of the TEditor program for the FindReplaceDemo:

When you click on the search icon (magnifying glass), the following routine will be executed:
procedure TF_MainFindReplace.SearchEditButton1Click(Sender: TObject);
var
P,L: Integer;
cp: TCaretPosition;
begin
for L := 0 to Memo1.lines.count -1 do begin
if cbGreat.IsChecked then begin
P := Pos (edfind.Text, Memo1.lines[L]);
end else begin
P := Pos (ANSIUPPERCASE (edfind.Text), ANSIUPPERCASE (Memo1.lines[L]));
end;

if P <> 0 then begin


cp.Line := L;
cp.Pos := P-1;
break;
end;
end;

if P <> 0 then begin


Memo1.SetFocus;
Memo1.CaretPosition := cp;
Memo1.SelStart := Memo1.PosToTextPos(cp); // - (cp.Line)-1;
Memo1.SelLength := Length (edfind.Text);
if cbReplace.IsChecked then begin
bnReplace.Enabled := True;
bnReplaceAll.Enabled := True;
end;
end else begin
if cbReplace.IsChecked then begin
bnReplace.Enabled := false;
bnReplaceAll.Enabled := false;
end;
end;
end;
This was the function for the first search. If you want to search the next item, you have to start from the
current position, behind the selection.

The source code for this is as follows:

procedure TF_MainFindReplace.bnFindNextClick(Sender: TObject);


var
P, L: Integer;
cp: TCaretPosition;
part: String;
begin
for L := Memo1.CaretPosition.line to Memo1.lines.count -1 do begin
if cbGreat.IsChecked then begin
if L = Memo1.CaretPosition.line then begin
P := Instr (Memo1.CaretPosition.pos+Memo1.selLength,
Memo1.lines[L], edFind.Text);
end else begin
P := Pos (edfind.Text, Memo1.lines[L]);
end;
end else begin
if L = Memo1.CaretPosition.line then begin
P := Instr (Memo1.CaretPosition.pos+Memo1.selLength,
ANSIUPPERCASE (Memo1.lines[L]), ANSIUpperCase
(edFind.Text));
end else begin
P := Pos (ANSIUPPERCASE (edfind.Text), ANSIUPPERCASE
(Memo1.lines[L]));
end;
end;
if P <> 0 then begin
cp.Line := L;
cp.Pos := P-1;
break;
end;
end;

if P <> 0 then begin


Memo1.CaretPosition := cp;
Memo1.SetFocus;
Memo1.SelStart := Memo1.PosToTextPos(cp);;
Memo1.SelLength := Length (edfind.Text);
if cbReplace.IsChecked then begin
bnReplace.Enabled := True;
bnReplaceAll.Enabled := True;
end;
end else begin
if cbReplace.IsChecked then begin
bnReplace.Enabled := false;
bnReplaceAll.Enabled := false;
end;
end;
end;

// Note: The example used in the Instr function is a own help function that looks like this:

function Instr (AtPos: Integer; S:String; toFind: String): Integer;


var
R : Integer;
begin
S := Copy (S, AtPos, (Length(S)-AtPos)+1);
R := Pos (toFind, S);
if R <> 0 then Result := R + AtPos -1 else Result := 0;
end;

If you want to replace the selected text, click on "OK", then the following routine will be executed:

procedure TF_MainFindReplace.bnReplaceClick(Sender: TObject);


begin
if Memo1 <> NIL then begin
if Ansiuppercase (Memo1.SelText) = Ansiuppercase (edFind.text) then begin
Memo1.DeleteSelection;
Memo1.InsertAfter (Memo1.CaretPosition, edReplace.Text,
[TInsertOption.ioSelected]);
end;
end;
end;

Here is once again a typical cliff. We have to use "TInsertOption" as a qualifier here.

You can download the simplified FindReplaceDemo from my homepage. It contains also the source
code for the click on the button "Replace All". In this procedure you will find three positions, where
"Application.ProcessMessages" is executed. One should always consider this function when any
interactions have to take place with the host system, e.g. to set the position of the cursor.

Here's the link to download the demos:


http://www.devpage.de/download/fmbook3/FindReplaceDemo.zip

Of course, it also works on the MAC.

By the way, a REDO function (like on VCL) is not available.

Printing a TMemo
Printing a memo under FireMonkey is unfortunately a bit more complicated. Under the VCL
component, functions such as "Canvas.textout (x, y, 'Text')", etc. were available and we have functions
that cause an automatic line break for longer texts. Under FireMonkey, you must calculate this by your
own.

The function to use under FireMonkey is Canvas.Filltext().

It should be noted that the data for the page width and height are different on the MAC by the factor of
10, which is taken into account in the calculation with corresponding IFDEF's. Here is a simple
example that works on both on Windows and the MAC (the unit FMX.Printer is required):
procedure TF_Main.mPrintClick(Sender: TObject);
var
r, rPage: TRectF;
i: Integer;
tf: tfilltextflags;
a: TTextalign;
T, th: Extended;
sl: TStringList;
z: Integer;

function GetFormattedLines (s: string): TStringList;


var
x, L, p: Integer;
pw: Extended;
label
StartAgain;
begin
sl.Clear;

if S = '' then begin


sl.Text := #13#10;
Result := sl;
exit;
end;

{$IFDEF Win32 OR Win 64}


pw := (rPage.Right - rPage.Left) - 100;
{$ENDIF}

{$IFDEF MACOS}
pw := (rPage.Right - rPage.Left) - 1;
{$ENDIF}

StartAgain:

for L := 1 to Length (s) do begin


if Printer.Canvas.TextWidth (Copy (S, 1, L)) > pw then begin
for P := L Downto 1 do begin
if S[P] = ' ' then begin
sl.Add(Copy (S, 1, P-1));
S := Copy (S, P+1, Length (s));
Goto StartAgain;
end;
end;

sl.Add(Copy (S, 1, L-1));


S := Copy (S, L, Length (s));
Goto StartAgain;
end;
end;

if S <> '' then begin


sl.Add(s);
end;

Result := sl;
end;

begin
if ActiveMemo <> NIL then begin
if PrintDialog1.Execute then begin
tf:= [];
a:=TTextAlign.taLeading;
sl:= TStringList.Create;

with Printer do begin


{$IFDEF Win32 OR Win 64}
r := RectF(200,200,(Pagewidth - 200),(PageHeight - 200));
rPage := RectF(200,200,(Pagewidth - 200),(PageHeight - 200));
{$ENDIF}

{$IFDEF MACOS}
r := RectF(20,20,(Pagewidth - 20),(PageHeight - 20));
rPage := RectF(20,20,(Pagewidth - 20),(PageHeight - 20));
{$ENDIF}

BeginDoc;

th := canvas.TextHeight('Test');
T := 0;

for i := 0 to ActiveMemo.Lines.Count-1 do begin

sl := GetFormattedLines (ActiveMemo.Lines.Strings[i]);

for z := 0 to sl.Count-1 do begin

T := T + Th;

if T+th+th >= rpage.Bottom-rpage.Top then begin


T := 0;
r.top := rPage.top;
Printer.NewPage;
end;

r.top := r.top+ th;


Canvas.FillText(r,sl[z],True,1,tf,a,a);
end;
end;

Canvas.Font.Family := ActiveMemo.Font.Family;
Printer.ActivePrinter;
EndDoc;
end;
end;
end;
end;

TDropTarget (how drag & drop works in FireMonkey)


Those who have been programming with Windows and the VCL components may know that one had to
call the function "DragAcceptFiles" so as to activate “drag & drop” to take over the file from the
Windows Explorer . Then we had to ensure that the message WM_DROPFILES was processed in the
form and write an event handler for it. A little bit complicated, I mean.

Here we take a look at the provided "ControlsDemo" I mentioned before. Generally, it is thought that
one uses the TDropTarget component in its application to where the user can "drag & drop" one or
more files (but you can also use normal components on which you can "drag and drop" anything). At
runtime, move the rectangle or the circle over the drop target icon and release the mouse button. In the
edit box the class name of the dragged element is then displayed as a result.

Let's have a look into the DragOver event of the DropTarget:

procedure TfrmCtrlsDemo.DropTarget1DragOver(Sender: TObject;


const Data: TDragObject; const Point: TPointF; var Operation: TDragOperation);
begin
Operation := TDragOperation.Link;
end;

With that, we tell the program that the drop operation is "allowed". By default, the default value of
"Operation" is "None", which means, it is not allowed. In addition "Move" and "Copy" also exists. A
special visual feedback you have (under Windows) only when you drag a file from the Explorer over
the target:
On the MAC, an corresponding icon appears, even when you drag components only inside the form.

By the way, the demo works only from XE7 on. In all previous versions of the demo, the property
"HitTest" of a parent component was disabled, so that no evaluation of the corresponding mouse
actions was processed.

When the property "Filter" is empty, all dragged files will be accepted. If you want that only certain file
types can be dragged on the drop target, enter an appropriate filter. You then change the settings of the
filter to "* .txt" under Windows or the MAC, when only text files shall be allowed for drag & drop
actions.

To make sure, that the filter works properly, you have to format the string of the filter-object like this:

'<first filter name>|<first filter value>|<second filter name>|<second filter value>|...|<n-th filter name>|
<n-th filter value>'

Example: only ".exe" and "*.txt" are allowed:

'Applications (*.exe)|*.EXE|Text files (*.txt)|*.TXT'

The arrow is animated and the symbol "Link" (or "Copy" or "Move") appears, which means the
DropTarget will accept the files. Now the question is how to evaluate it when the user released the
mouse button. Here an example:
procedure TfrmCtrlsDemo.DropTarget1DragDrop(Sender:
TObject; const Data: TDragObject; const Point:
TPointF);
const Data: TDragObject; const Point: TPointF);
var
I: Integer;
begin
if Data.Source <> nil then
Edit1.Text := Data.Source.ClassName
else
Edit1.Text := Data.Files[0];
end;

The files, dropped on the target are included in "Data.Files", where "Files" is an array of strings. In the
procedure above, I showed you how to get access to it. Of course, it will work also on the MAC:

If "Data.Source" is not NIL, it is a component that has been dragged to the target in the program itself.
Of course , you can also drag and drop some files on a normal edit - box. “Tip12” in chapter 11 tells
how to do it .

TRichEdit (not available - but possible for replacement via 3rd-party)


A TRichEdit component is for FireMonkey not yet available. Thus you have to use a component from
third parties. Since the beginning of 2015 there is a very good RichEdit component available from TMS
software:

TTMSFMXRichEditor: http://www.tmssoftware.com/site/tmsfmxpack.asp?s=fmxricheditor#features

For this component is even a spell-checker offered, which is really helpful.

TPageControl (not available - but replacement available)


Such an extraordinarily useful component under Windows cannot be found in FireMonkey. However,
the TTabControl component provides a reasonable compensation (see below).

TStringGrid (works different)


This component is very often used in VCL programs. Like the FireMonkey listbox also the FireMonkey
TStringGrid does not have an OwnerDraw event (but an OnPainting event, which will be introduced in
detail later).

When you insert a new TStringGrid into the form, you will get this situation:

As you can see, the grid element contains no columns. You also will not find a property "Columns" in
the Object Inspector.
You can add columns, by clicking on the StringGrid-component with the right mouse button. From the
context menu, you can select either the entry "Items Editor" or the command "Add TStringColumn".

As you can see below, we have added two columns with the entry-editor. You can now click on a
"StringColumn" and set different properties, for example, the property "header". The header is actually
not the row-number zero of the grid, but a separate element. Keep this in mind, when you convert
source-code from VCL to FMX. You may show or hide the header with the property "ShowHeader".

The property "RowCount" exists also in the FireMonkey StringGrid. By default, a grid has initially 100
lines. Therefore, the assignments shown here in the "FormCreate"event are working correctly:
Assignments made without adding Columns before would not work. The columns can, of course, also
be generated at runtime, if necessary.

But how can we change the font-settings (e.g. bold or font color)?

The StringColumn component has not a font-property, but the StringGrid component has one under the
"TextSettings". But all the settings you make here are valid for the whole grid. If you want it to be
different for one or several columns or cells, you can use the "OnDrawColumnCell" event.

Example:

procedure TForm9.StringGrid1DrawColumnCell(Sender: TObject;


const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF;
const Row: Integer; const Value: TValue; const State: TGridDrawStates);
var
Flags: TFillTextFlags;
ar: TRectF;
begin
Flags := [TFillTextFlag.ftRightToLeft];

canvas.BeginScene;
ar := bounds;

ar.Inflate(-1,-1);

canvas.ClearRect(ar);
canvas.Font.Style := [ TFontStyle.fsBold ];
canvas.Fill.Color := TAlphaColors.Red;
canvas.FillText(bounds, Value.ToString, True, 1, flags, TTextAlign.taTrailing,
TTextAlign.taCenter);

canvas.EndScene;
end;
The result looks like this:

The text output is in bold and in red. One note: there is a separate event for drawing of the column
header: "OnDrawColumnHeader". We have now also "OnDrawColumnCell and"
OnDrawColumnBackground " as new events available.
You can then draw the text of the column left, right or centered, depending on the program logic. And
you can set this by using the different "TTextAlign" parameters, e.g. "TTextAlign.taCenter" for
centered text.

To distinguish: "OnDrawColumnBackground" is only called if the grid is in edit mode, otherwise the
"OnDrawColumnCell" is used for output.
If the "DefaultDrawing" setting is activated (default), the grid first draws its content itself. If you
disable this option, no output is made (the grid lines are always drawn as long as the "ColLines" or
"RowLines" is activated in the options settings).

Here, depending on the program logic, you can output the text of the respective column to the right or
centered by specifying the TTextAlign parameters (for example, TTextAlign.tacenter).

Note: Under Delphi Seattle about 36 events were published for the StringGrid (made visible in the
object inspector). Under Delphi Berlin it was only 13, under Delphi Tokyo it is now again 22. Probably
it was a bug, which was now repaired. It is important for you to know that, in addition to today's 22
events, other events are available for the grid, but you have to allocate them yourself at runtime.

For example, Events such as "OnEnter", "OnExit", or "OnViewPortPositionChanged". There are about
44 "On-Events" for the StringGrid. Simply enter "StringGrid1.On" once in the editor and press the Strg
+ spacebar to display the available events (of course you must have a StringGrid on the form or
declared a corresponding variable).

The "OnCreateCustomEditor" event is also worth mentioning (also in relation to the VCL StringGrid).
Here you can create your own cell editor or specify it for editing. For example, you could use a
TComboEdit, which then offers comfortable options for text selection.

Here is a short source code example and the runtime result:


procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.Cells[0,0] := 'Toyota';
end;

procedure TForm1.StringGrid1CreateCustomEditor
(Sender: TObject;
const Column: TColumn; var Control:
TStyledControl);
begin
Control := TComboEdit.create (self);
TComboEdit (Control).Items.Add('Mercedes');
TComboEdit (Control).Items.Add('BMW');
end;

TGrid (Image and other elements in the Grid)


What you have done with "OwnerDraw" events in the VCL (String-) Grid component, for example,
displaying images or checkboxes, can be de done here easily with the TGrid. Beside TStringColumns,
you can also use "TImageColums" or "TCheckColums".

Click on the TGrid with the right mouse button and select the required entry:
The number of Column types are constantly increasing. While under XE7 it were 8, now under Delphi
Tokyo we have 12 types. The special thing with some column types, e.g. "TDateColumn" and
"TTimeColumn" is, that special editors are offered (here a TDateEdit and a TTimeEdit).

To display text you use the the "GridGetvalue" event.

The first picture shows the form at design time and the second at run time:

Here you can see the source code being used:


procedure TForm2.Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: TValue);
var
aCol: TColumn;
begin
aCol := Grid1.ColumnByIndex(Col);

if aCol = StringColumnText then


Value := intToStr (Row+1)
else if aCol = ImageColumn1 then begin
if odd (Row) then
Value := Image1.Bitmap
else Value := Image2.Bitmap;
end;
end;

As far as images are concerned, it is much easier with the "TGlyphColumn". You must assign a
TImageList to the StringGrid ("Images" property). And then you can simply assign the desired entry
from the imagelist in the "OnGetImageIndex" event.

Depending on the program-logic, you can show images, texts or checkboxes. Thus, with the TGrid a
very flexible component is provided, which is ultimately much more powerful than the VCL
TStringGrid.

TStringGrid-alternative (TMSFMXGrid)
As a long-time user of the TMS components I am glad to give you an alternative that could make the
transition from the VCL StringGrid to a FireMonkey grid more easily: The TMSFMXGrid which, in
addition to the usual VCL properties (ColCount, DefaultRowHeight, etc.) has many other skills (such
as export the contents to an Excel file). For more info:
http://www.tmssoftware.com/site/tmsfmxpack.asp.

THeader (not sections, but items)


This component also exists for FMX. In the VCL we have sections, here in FireMonkey we have
"Items", which are "THeaderItems".

THeaderControl (is not available under FireMonkey)


This component does not exists under FireMonkey. Use the THeader instead. Since the individual
THeader items work as containers, you can easily insert here required elements, such as checkboxes or
bitmaps.

TProgressBar (not "position" but value)


The TProgressBar works in essential things like the VCL component. But in FireMonkey components,
you don't use the property "Position", but "Value". The Position property is used with Position.x and
Position.y to set the horizontal and vertical offset of the component.

TTabControl (no OwnerDraw)


This component works similar like the VCL version. You can generate individual TTabItem elements
and then insert here your components. Of course you can create TTabItems also at runtime and then
paste them into the TTabControl.

An "OwnerDraw" property and an "OnDrawTab" event do not exist, but you have an "OnPaint" and an
"OnPainting" event, where you can use the canvas to draw text. Beside this, there are the Styles
"TabControlStyle" and "TabItemStyle" through which you can make the corresponding optical
adjustments via the "OnApplyStyleLookup" event.

TTrackbar (helpful property "tracking")


The FireMonkey TTrackbar component has, in contrast to the VCL component, the property
"Tracking". If you set this to "False", the OnChange event is only triggered when the move of the
trackbutton has been completed (the user has released the mouse button).

TSpeedButton (without Bitmap)


In the VCL-TSpeedButton an image can be assigned with the property "Glyph". This is also possible
under FMX, but it is different and more comfortable at the same time, since you can simply select an
image via a linked TImageList via "ImageIdex".

TStatusbar (a way to compensate the missing "Panels")


In the VCL TStatusbar you can display a text either on the property "Panels" or via the "SimpleText"
property. In the FireMonkey status bar, there is neither something of this kind nor text property. But
you could use the status bar as container and insert Labels.

MessageDlg (e.g. not directly usable with mtWarning)


With this dialog, there is a problem, if you try to use it like in the VCL.

What works here under Windows:


if MessageDlg('Info', mtWarning, [mbYes, mbNo], 0) = mrYes then begin
// do something or not
end;

does not work with FireMonkey.

Here it should look as the following:


if MessageDlg('Info', TMsgDlgType.mtWarning, [TMsgDlgBtn.mbYes,
TMsgDlgBtn.mbNo], 0) = mrYes then begin
// do something or not
end;

Thus, the qualifiers are required...

It has cost me some time to figure out how it works.


Section 5: The FireMonkey Style-Designer
a) Using the Style Editor

Double-click on a possibly existing StyleBook or right-click on a control and select the command

· "Edit Custom Style" if you only want to change the style for one component or select
· "Edit Default Style", if you want to change the default style for all components of this type in
the current project.

You will then see the following screen:

In the tree view on the left, you can select the individual styles and assign appropriate values in the
Object Inspector. Over the tree view on top, there is a button which allows you to delete layout
elements from the tree view.
And deleting elements works only with this button. The delete key does not work here.
The StyleBook and the styles have a very great importance for the work with FireMonkey. Styles are
often used to achieve the same optical results like in the VCL by using the OwnerDraw events.

When you have called the style editor under Delphi XE2, the control was then selected in the tree-view
and in the middle of the IDE-window the control itself was drawn. Unfortunately that is from XE3 on
not the case any more. Now, on the left side, only the tree view is shown and no item is selected. If the
structure list is slightly longer, it's not so easy to find the desired item quickly.

Many users do not understand at all what they can do at this place.

Therefore: first click on the element displayed in the center of the screen, then the element is selected
also in the structure view. From Delphi 10 Berlin on, the search is also supported by direct text input.
Select the structure list and type the name of the object you are looking for (but there is no edit field,
you must type "blind").

Some explanations about the symbols in the style-editor toolbar:

Symbol Description
Creates a new, empty FireMonkey style.

Opens a dialog box where you can select a


FireMonkey style to load.

RAD Studio provides some FireMonkey styles.


You can find them in C:\Program Files
(x86)\Embarcadero\Studio\19.0\Redist\styles\Fmx .

Opens a dialog box where you can select a


FireMonkey style to add all of its style objects to
your current style.

Saves the current FireMonkey style into a .style


file.

You can use the - and + buttons to adjust the


zoom of the object design area. Supported zoom
levels are multiples of 100% between 100% and
1000%.
Selects a target platform to edit.

Opens a dialog box to select a platform to add to


the Platform combo box:

Removes the currently selected platform from the


Platform combo box.

Use a transparency grid as background for the


object design area.

Use a white background for the object design


area.

Use a black background for the object design area.

The arrow head of this button lists style items of


the style that do not exist for the selected
Platform but are available for other platforms of
the current style.
Click a style item on the list to add a copy to the
current platform of the style.

b) Styles in FireMonkey - an overview

A "Style" describes a particular type of representation of a control, a form or an entire application. Such
styles are also known in the VCL. From XE2 on, in a VCL application, it is able to select an application
style for a Windows application (under "Project, Options, Application appearance"). With the following
XE versions, the thing has been so far refined on the VCL side, because each control has received the
property "StyleElements". This allows you to specify how elements are displayed (client area, borders,
fonts) and if the style should be applied or not. A similar property of comparable meaning is also
available for a number of components in the "StyledSettings" under FireMonkey.

FireMonkey and VCL have so far moved closer by the time. From XE3 on, the Bitmap-Style Designer
is available to edit the VCL styles. And you can save these VCL-styles as FireMonkey Styles, too.

Let's look, first of all, at the basics of the FireMonkey styles. FireMonkey styles are included with
Delphi and shipped as style files. You can find these in Delphi Tokyo e.g. under

C:\Program Files (x86)\Embarcadero\Studio\19.0\Redist\styles\Fmx

These are text-files which you can load into a text-editor. Load it once so that you can get an idea of
such a file. In your program, you can load this style files either at design time in a StyleBook
component or at run-time.

In addition, the standard styles are already integrated in Delphi. Even if you put no StyleBook on your
form, there is already a standard style which is always used when you don't load another style. This
standard style then ensures that your program appears as a typical Windows program under Windows
and as a typical MAC OS X program under the MAC.

How does it work now so that a TButton will be drawn with the help of styles and reflect a normal, a
focused or pressed state?

Well, before I give the answer, let me remark that the high dynamic in the development of the
FireMonkey framework is shown here. From XE2 to XE4, there was always another technical solution
for that. I'll spare the details and show here only how it proceeds under Delphi 10.2:
The background of TButton is defined in "background", where the background is a
"TButtonStyleObject". This object contains a reference to a section of a bitmap. If you click on
"background", you can find in the Object Inspector the items "NormalLink", "FocusedLink", "HotLink"
and "PressedLink" that are all defined as "TBitmapLinks":

If you click on such a "TBitmapLink", you will get the following view:
Here you can see the settings for "FocusedLink", for the Windows 10 Style selected (box "Links").
Instead of having to enter any values manually, you can simply move the selection frame as required.

The Bitmap has been available in Delphi 10 in the resolutions 1, 1.5 and 2 fold (in the physical
resolutions 400x600, 600x900 and 800x1200 pixels). Depending on the resolution (DPI) the program
runs later, the most suitable resolution available is chosen here. If necessary, however, you can add
additional resolutions, simply click on the button with the plus symbol.

If you would load the Windows 10 ModernStyle, you would find also a 3-fold resolution.
You can also add your own bitmaps to a style and use your own bitmap style.

By the way, you can also find a "FocusedLink" for the TSpeedButton, but it has the same source-
position as the "NormalLink" because the state "Focused" does not exist for the TSpeedButton.

As mentioned earlier, there is no bitmap-link in the styles for XE2. It starts with XE3. This makes it
possible to draw the representation of the components more quickly. Because it is easier to use an
existing bitmap for the drawing instead of drawing a complicated appearance at run time.

The knowledge of the standard styles is important if you want to create a custom style for a component,
e.g., for a TButton.

The structure of a button style is still different if you are using the supplied FireMonkey styles. If you
load here the "Dark Style" into a StyleBook, you will get for the TButton

the following structure presented in the tree view:

The background consists of a TRectangle that contains two TColorAnimations, a TGlowEffect and a
TGlyphStyle. The file contains no style bitmap. As you can see, there are ultimately several ways to
define the appearance of a component. In general, it is worth looking once at the basic styles of the
individual components in the style-editor, which will increases our understanding of the use of the
FireMonkey components.

c) How to convert VCL Styles to FireMonkey Styles

It's very useful that you can convert VCL Styles into FireMonkey styles, and then use it in FireMonkey
applications.
Proceed as follows: In Delphi call the command "Bitmap Style Designer" in the "Tools" menu. Then
open it, e.g., under

C:\Program Files (x86)\Embarcadero\Studio\19.0\Redist\styles\vcl\Luna.vsf

In the "File" menu call the command "Save As". In the dialog that appears, select the file type
"FireMonkey Style":

Save the file to a folder of your choice (preferably in the one that will be not deleted when you uninstall
Delphi).

In a FireMonkey project, you can then load and use this style file in a style-book. It will look like this:

d) Using FireMonkey Styles

How can your application get the style you want? There are several possibilities. Either you load one of
the supplied style-files at design time in a StyleBook component that you put on your form (do not
forget to assign the form property "StyleBook" with the name of your StyleBook component).

Or you provide one or more style-files with your application and then load those you want into the
StyleBook at runtime. Thus, e.g., the user can select a style that he likes.

You can do this, for example, as the following (the path must certainly be adapted to your program
environment):

StyleBook1.Resource.LoadFromFile('D:\LunaStyle.style');

If you want to assign a style to the entire application, you can use the TStyleManager, e.g., at the
create-event of the main form:

TStyleManager.SetStyleFromFile ('D:\LunaStyle.style');
Even forms that are generated later at run time, will then have this style.

To get it work, you must include the unit "FMX.Styles" in your units-list.

e) Understanding FireMonkey-Styles

The visible components in FireMonkey basically do not know how they should represent themself. For
this, they need the style information. The so-called FireMonkey primitives are derived from TControl
and can only represent their basic form as such. Only the TStyledControl, which is also derived from
TControl, can draw itself with a style. Here is the overview:

TControl3D and subsequent classes have also been derived from TControl. These 3D components have
nothing to do with the styles. Here, the representation is made according to the selected "material
source", but for this we will come back later in the chapter on 3D programming.
How does a component find its style? This depends on whether the component has assigned a value in
the property "StyleLookup" or not. If there is a value, the component searches for a style element of the
same name if a StyleBook exists. If so, this style description is used to draw the component.

If there is no StyleBook, or if the value is not found in the StyleLookup property, a StyleName is
constructed to search for. For this purpose the name of the class is used without the leading "T" (i.e.
from "TEdit" the "edit") and supplemented by the word "style". Without a special StyleLookup
assignment, a style named "editstyle" is searched for. First it searches in a possibly existing StyleBook.
(editstyle in RubyGraphite.style)

If the component does not find it there, it searches in the "Defaultstyles". These are the styles built into
Delphi that are always there, even if you do not have a StyleBook in the program. Depending on the
operating system (Windows 7, Windows 8, Windows 10, macOS, etc.) different "default styles" are
searched there.

If it can not find anything here, the component tries to find a style name using the same search method
on the basis of the parent class. If this is not found, the component remains ultimately invisible.
However, this will apply only in cases where quasi-proprietary components have been created, and the
component with no named style also has a parent that also has no assignable style. So if you are not a
component developer, you can forget this problem directly, it will never concern to you.

By the way, you can change the styles as desired at the runtime of the program, and the redrawing is
then performed on the basis of the previously described method.

Since there are always problems with the differentiation of "StyleName", "StyleLookUp" I will try to
make it clear:

The StyleName describes the style itself, it almost represents the style description.

StyleLookup tells the component what style to draw.

You usually use the StyleName in the Style Designer and the StyleLookUp in the Object Inspector
(where you normally do not enter a StyleName).

Typically, the base for a style element is the TLayout, which can contain more primitives or other style
elements. However, there are also special style objects that can be used to form the container for other
style elements, such as the TButtonStyleObject.

These special style objects can be selected directly from the tool palette starting with Delphi 10 Berlin
(previously you had to export the whole style as a file and edit it manually and then re-import it into the
StyleBook):
However, you can also group multiple components within a form, outgoing from a TLayout, and then
assign a style name to the TLayout. If you then assign the StyleName to another component in the
StyleLookUp property, the component is created based on the "Style" that you created with the Layout
element within your form.
Chapter 3: Tips and tricks for Cross-Platform
Development
Section 1: Starting other programs
Depending on whether you want to start another program on Windows, MAC or LINUX (or want to
open a file with the default viewer) other routines are needed for each systems. This is achieved with
the use of different units and the use of IFDEF directives.

In the uses-clause, use the following:


{$IF DEFINED (LINUX) or DEFINED (MACOS)}
POSIX.Stdlib,
$ENDIF}

{$IFDEF MSWINDOWS}
ShellApi,
{$ENDIF}

In the implementation section it looks as follows:


procedure RunProg (prog: string);
begin
{$IFDEF MSWINDOWS}
ShellExecute(0,'open',Pchar (prog),nil,nil,0);
{$NIDIF}

{$IFDEF MACOS}
_system(PAnsiChar ('open ' + AnsiString (prog)));
{$ENDIF}

{$IFDEF LINUX}
_System (MarshaledAString (UTF8String (prog)));
{$ENDIF}
end;

If you need to find out, where functions and procedures for the MAC or Linux are located, look into the
corresponding files in the following directories:
C:\Program Files (x86)\Embarcadero\Studio\19.0\source\rtl\osx
C:\Program Files (x86)\Embarcadero\Studio\19.0\source\rtl\linux
C:\Program Files (x86)\Embarcadero\Studio\19.0\source\rtl\posix
Section 2: Get the program directory and program data
directory
You can find the program directory of the executable file both under Windows and the MAC by using
the command "ParamStr (0)". In fact, the whole name of the executable file is stored here, but it's easy
to identify only the program directory.

The best is to use a unit, in which you set up the corresponding variables in the initialization section.
Here's an example:

unit MyData;

interface

uses
System.SysUtils;

var
AppPath: String;
AppName: String;
AppExeName: string;
AppIniName: String;
ProgDataPath: String;

Implementation

begin
AppExeName := paramstr (0);

AppPath := ExtractFileDir (paramstr (0));


AppName := ChangeFileExt (ExtractFileName
(paramstr (0)), ''); // Ohne Endung

{$IF DEFINED (Linux) or DEFINED (MACOS)}


ProgDataPath := IncludeTrailingPathDelimiter(GetHomePath) +
'.config/' + AppName;
AppIniName := ProgDataPath + PathDelim + AppName +'.cfg';
{$ENDIF}

{$IFDEF MSWINDOWS}
ProgDataPath := IncludeTrailingPathDelimiter(GetHomePath) +
'hastasoft' +PathDelim + AppName;
AppIniName := IncludeTrailingPathDelimiter(GetHomePath) +
'hastasoft' + PathDelim + AppName + PathDelim +
AppName + '.ini';
{$ENDIF}

if Not DirectoryExists (ProgDataPath) then begin


ForceDirectories(ProgDataPath);
end;
end;

Please replace kindly here "Hastasoft" with your own company name or leave this part completely free.
This supplement is especially recommended if you have developed many programs and the customers
use several of your programs. The "Config" folder is a folder on the MAC where programs can store
their configuration files. This folder is normally invisible in the Finder. You can of course use an
alternative file manager that can show these folders (e.g. File IO for the MAC).

You can turn the visibility of the normally hidden folders in the Finder on and off with:

defaults write com.apple.finder AppleShowAllFiles YES // invisible with NO

If you run the following source code, you will see the results at runtime on the different operating
systems (of course, you would install your finished program in the usual places):

ShowMessage
('AppName: ' + #13 + AppName + #13#13 +
'AppPath: ' + #13 + AppPath + #13#13 +
'AppExeName: ' + #13 + AppExeName + #13#13 +
'ProgDataPath: ' + #13 +ProgDataPath + #13#13+
'AppIniName: ' + #13 + AppIniName);

Windows:

MAC OS:
Linux:
Section 3: Catch the program passed start-up parameters
What under Windows can be done very easy with "StartParam := ParamStr (1)", is under the MAC
OSX more difficult, because there it works quite differently. If you right-click on a file in the MAC-
Finder and choose from the menu the command "Open with", the system starts the program and then
sends at the same time a message to the program that it should open the selected file.

So you have to set up a mechanism to ensure that messages can be received from your program.

In MACAPI.AppKit is a NSApplicationDelegate available, which basically handled under MAC OS X


the message "Open File". Unfortunately, this feature has not yet been integrated into the Delphi
FireMonkey implementation.

So there is currently no choice, as to make it by yourself. The following is the source code of the unit
that does the implementation. In the main program then an "Open event" procedure must be
implemented and be passed on generation of own delegates. It looks like so:
-------------------------------------
unit hs_NSApplicationDelegate;

// Will implement an own NSApplication delegate; by this


// application:openFile: messages from MAC-OS-System can be handled.

interface

uses
Macapi.ObjectiveC, Macapi.CoreFoundation, Macapi.AppKit, Macapi.Foundation,
Macapi.CocoaTypes;

type

// Although we only need the OpenFile function here, we must also


// still implement DidFinischLaunching and WilltTerminate, without
// i cannot compile

// from Macapi.AppKit
NSApplicationDelegateExtended = interface(NSApplicationDelegate)
// create with Strg + Shift + G a own GUID
// DON'T use the GUID below in your own program
['{2F5BD639-88DE-46F7-950A-D4469A125229}']
function application(theApplication: Pointer; openFile: CFStringRef):
Boolean; cdecl;
end;

TOpenFileEvent = reference to procedure (const AFileName: string);

TNSApplicationDelegateExtended = class(TOCLocal, NSApplicationDelegateExtended)


private
FOnOpenFile: TOpenFileEvent;
public
constructor Create(const AOnOpenFile: TOpenFileEvent);
function application(theApplication: Pointer; openFile: CFStringRef): Boolean; cdecl;
function applicationShouldTerminate(Notification: NSNotification): NSInteger; cdecl;
procedure applicationWillTerminate(Notification: NSNotification); cdecl;
procedure applicationDidFinishLaunching(Notification: NSNotification); cdecl;
function applicationDockMenu(sender: NSApplication): NSMenu; cdecl;
end;

procedure StartExtendedApplicationDelegate(const AOnOpenFile: TOpenFileEvent);

var
ExtendedDelegate: NSApplicationDelegateExtended;

implementation

uses
FMX.Forms;

constructor TNSApplicationDelegateExtended.Create(const AOnOpenFile: TOpenFileEvent);


begin
inherited Create;
FOnOpenFile := AOnOpenFile;
end;

procedure TNSApplicationDelegateExtended.applicationDidFinishLaunching(
Notification: NSNotification);
begin
// Dummy
end;

function TNSApplicationDelegateExtended.applicationDockMenu(
sender: NSApplication): NSMenu;
begin
// dummy
end;

function TNSApplicationDelegateExtended.applicationShouldTerminate(
Notification: NSNotification): NSInteger;
begin
inherited;
// dummy
end;

procedure TNSApplicationDelegateExtended.applicationWillTerminate(Notification: NSNotification);


begin
// Nicht FreeAndNil, verwenden, sonst gibt es Fehler!!
FMX.Forms.Application.Free;
FMX.Forms.Application := NIL;
end;

function TNSApplicationDelegateExtended.application(theApplication: Pointer; openFile: CFStringRef): Boolean;


var
Range: CFRange; S: string;
begin
Result := True;
Range.location := 0;
Range.length := CFStringGetLength(openFile);
SetLength(S, Range.length);
CFStringGetCharacters(openFile, Range, PChar(S));
TRY
FOnOpenFile(S);
EXCEPT
FMX.Forms.Application.HandleException(ExceptObject);
Result := False;
END;
end;

procedure StartExtendedApplicationDelegate(const AOnOpenFile: TOpenFileEvent);


var
NSApp: NSApplication;
begin
Assert(ExtendedDelegate = nil);
NSApp := TNSApplication.Wrap(TNSApplication.OCClass.sharedApplication);
if Assigned(NSApp) then begin
ExtendedDelegate := TNSApplicationDelegateExtended.Create(AOnOpenFile);
NSApp.setDelegate(ExtendedDelegate);
end;
end;

end.

In the main form, the following must then happen:


procedure TF_Main.FormCreate(Sender: TObject);
begin
{$IFDEF MACOS}
StartExtendedApplicationDelegate (OnOpenParaFile);
{$ENDIF}
end;

In the form-declaration, in the private section:


private
{ Private-Deklarationen }
procedure OnOpenParaFile(const S: String);

In the implementation-section:
procedure TF_Main.OnOpenParaFile(const S: String);
begin
if (s <> '') and fileExists (s) then begin
memo.lines.LoadFromFile(fn);
end;
end;

Unfortunately, it's a bit complicated, but it works.

It should also be noted that it is necessary to enter in the info.plist the information, which types of files
your program will be able to open.

Here is an example of a supplement to the info.plist file which gives the MAC-OS the information that
your program can open text files and .xml as well as source code files:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.plain-text</string>
<string>public.source-code</string>
<string>public.xml</string>
</array>
</dict>
</array>

Without this, the Finder does not provide an entry in the "Open With menu" for your program name.
But that does not prevent other programs instead of the Finder to transmit parameters to the program for
opening files. For example, my file-manager "File IO for MAC" starts every program and gives at the
same time a startup parameter to the program (this is managed by the MAC system in the background).
Section 4: "Hello World" - Multilingual programs and
new markets
The Apple App Store is ideal for selling your programs internationally and it can thus open up entirely
new markets. For this purpose you should not only offer your program in your own language (in my
case German), but also offering an English language version.

For this intention, FireMonkey helps us in a very simple way with the TLang component, of course, in
a cross-platform way. If you have developed your application, add a TLang-component to the main
form.

Example:

Double click on this TLang component, it will open the following dialog:
You might be surprised that even the entry "Öffne mich Mac" is offered. This text is from the view "OS
X Desktop". But it will show up only, when I have clicked on the button"Scan for strings" here again.
You must always do so when you add new controls to the form and want to edit these texts in the
language-designer.

In the field "Two-letter language code" enter "en" and click the "Add" button.

The dialog will then change its appearance and beside the recognized text an input field is now
available for each entry. There you can enter the English text equivalents:

Now simply close the dialog. The changes are applied directly.

To switch now from the standard language to English, you have to do this with a software command.

A user would naturally choose his language on a menu entry. You can save his choice in the program
by an entry in the ini-file and restore it at the next launch.

In order to demonstrate it, we do this directly at the program start in the FormCreate event:

procedure TForm13.FormCreate(Sender: TObject);


begin
Lang1.Lang := 'EN';
end;

WARNING: If you write "en" in lowercase, it does not work. You must enter the language code here in
the uppercase as shown above.

The form looks like this after program start:


What you can see here is that the text of the form ("caption") is not covered by the TLang component,
not even when you use the "Add" button on the right side of the Language Designer dialog to add an
entry with a translation for "Form13" manually.

You must, therefore, make the changes in the source code by yourself, such as:

procedure TForm13.FormCreate(Sender: TObject);


begin
// Set default language
Lang1.Lang := 'EN';

// Depends on used language:


if Lang1.Lang = 'EN' then begin
Caption := 'Title';
end;

if Lang1.Lang = 'DE' then begin


Caption := 'Überschrift';
end;
end;

Even menu texts are not translated. The main menu entries are shown in the Language Designer and
they are editable, but there are not translated at run time.

You must - as long as this bug is not resolved in Delphi - make a separate procedure, where you can
assign each menu item with the needed text (i.e. "mnu_Optionen.text = 'Options', etc). Let's hope that
the menu items are covered by this component in a subsequent version.

If a component should not be translated, you can disable the setting "AutoTranslate" in the Object
Inspector.

If you have multiple forms in the project, they will also be searched for strings, but possibly only after
you have clicked on the button "Scan for Strings". This button must also be pressed again, when you
add additional forms to your project. You need only one TLang component for your whole project,
because every second TLang component would again search the entire project for strings and would
add them to your project (doubling it).

In any newly created forms at run time, you must use the form-create event to tell the TLang
component which language it should use.

It offers itself here to create a separate procedure so that you can switch between different languages at
run time. But you can only change from the original language to other languages. You can only come
back to the original language when you restart the program and use the original language as default. OR
you specify a new language in addition to the original language, i.e., "Add Language" here with the
switch "de" (when German is the default language). Since it obviously makes no sense to provide a
translation then, it simply restored the original entries. In this case, you can also switch back to the
original language at runtime.

The data of the TLang-component will be stored in the the form in an unreadable encoded format by
default. Alternatively, you can save the text outside of the file.

WARNING: If you have already translated texts and then uncheck "StoreInForm" in the Object
Inspector:

you will lose your previous work; there is no confirmation prompt. So, before you uncheck it, use
"Save File" in the Language Designer dialog box to save your language file. This file contains all
previously applied languages and automatically receives the file extension ".lng". At runtime, you can
then load the file with "Lang.LoadfromFile (file name)" into the component.

At the demo here, everything works as it should. In a larger application that I have provided with the
TLang component, the component does not keep track of all translations. Therefore, I have saved
translations as a text file (with the button "Create txt-file template") and then reloaded again for
revisions. You can load this file back to the Designer, when you add a language by using the button
"From txt file". Anyway, everything has worked satisfactorily at the end.
Section 5: Apply sandboxing and Entitlements properly
If you want to bring your application to the App Store, it must support the sandboxing model. From
July 1st, 2012 on, this is the duty. Therefore, we should talk a bit about the sandboxing model first. It's
shown here how your application works with and without sandboxing:
Without the sandbox, your application has full access to all the files and folders of the operating system
(it might be that your program must be run with administrator rights for some actions). You may then
obtain access to documents folder, images or music folders at any time. Also, your application could
write to the Application Bundles of other applications which so far is a problem for the security of the
operating system.

If your application only runs "in the sandbox", it does not realize what else is running around it and
does not have access to the data outside the sandbox. There may be applications for which it's enough
to play in the sandbox. But it is often that you want give the working results of your application to other
applications or want have access to data-output from other applications.

Here, the "Entitlements" come into play. If your application would also like to play outside of the
sandbox, you need to request the appropriate rights.

The fundamental rights can be set with Delphi under the "Project, Options", "Entitlement List" (here,
again, note that this information is only displayed if you have set the MAC OS X as the target
platform):

What most applications will probably need is the right to read and save files, over which one can get
access through the dialog "Open" and "Save". This is also a setting where Apple lets you go through it
if you not describe why it is needed when registering the application in the App Store.

In contrast, if you need direct "read and write" access to the other mentioned folders (pictures, music,
etc.), you must explain it exactly why you need these rights in iTunes Connect. If you don't do this, it's
quite probable that your application will be rejected (already happened to me).

When you click on the "View Details" button in iTunes Connect on the page of your application, you
can edit the so-called meta-data of the application. This is the description of what your application does
and you can add a few screenshots that shows your application.

Under the section "App Review Information", there is a button "Add Entitlement":

Here just click on it, select the required right and then briefly describe (in English, of course) why your
application needs the right. It should also be really necessary or, at least, be comprehensible.
It is important to know that you can request more rights than you can select in the Delphi permission
list.

Indeed, it is possible to request a right that your application can read or write to a specific folder, for
example, the desktop-folder.

However, Apple will normally not approve it, and so, according to my experience so far, these
extensions have only theoretical relevance.

In this respect, I have refrained from further explanations here.

In the previous version of the book (for XE7), there were illustrations of how to add these extended
privileges to the Delphi Entitlements list, and how to manipulate the provision manager to not override
the manually provided information.

The book also had a chapter on working with App-Scoped-Bookmarks, which allows a persistent
access to already opened folders or files outside the sandbox and in the appendix a sandbox unit.

If you need this information for some reason, the previous version of the book with the corresponding
information is still available for purchase at Amazon for a small amount:
https://www.amazon.de/dp/B00VPC7V86
Section 6: Using MAC APIs (POSIX, CORE and Cocoa)
in Delphi
The MAC OS X operating system essentially works with 3-layer systems:

POSIX
CORE API
COCOA Framework

While the first two layers are addressed over a conventional C-interface, the COCOA layer is
accessible via a special Objective-C interface. A set of functions is ultimately in all or several layers.
For example, you can query the computer name with a POSIX function (gethostname) or the COCOA
interface NSHost (Host.Name).

In the following, we will focus on the individual layers:

POSIX

The POSIX interface offers typical low-level operating system functions, which can also be found in
other Unix or Linux systems.

So you can, for example, ask for the name of the computer on which your program is running, with the
POSIX function "gethostname". This function is defined in the "Posix.UniStd.pas". To be more exact,
the function is to be found in the "UniStdAPI.inc".

It's worth it saying here some words on the structure, with which Embarcadero has implemented these
API functions.

Under

C:\Program Files (x86)\Embarcadero\Studio\15.0\source\rtl\posix

you can find the POSIX folder. It has a subdirectory called "OSX". The concrete implementations are
found in the ".INC"-files (.INC stands for "include"). If you look at the individual units of the POSIX
folder, for example, you will find IFDEF declarations in the "Posix.Stdlib.pas" in the following way:

{$IFDEF MACOS}
{$I osx/StdlibTypes.inc}
{$ENDIF MACOS}
{$IFDEF LINUX}
{$I linux/StdlibTypes.inc}
{$ENDIF LINUX}
The Linux folder does not exist yet, but we may be sure that Embarcadero is working on a Linux
implementation.

The function in the UniStdApi.inc is implemented:

function gethostname(name: MarshaledAString; namelen: size_t): Integer; cdecl;


external libc name _PU + 'gethostname';
{$EXTERNALSYM gethostname}

To make them suitable for Delphi, we can do this as follows:


Uses
Posix.unistd.pas,

function mac_GetComputerName: string;

Implemtation

function mac_GetComputerName: string;


var
buf: Array [0..255] of AnsiChar;
begin
if gethostname(buf, sizeOf (Buf)) <> -1 then begin
Result := UTF8ToUnicodeString(buf);
if pos ('.local', Result) <> 0 then begin
Result := copy (Result, 1, pos ('.local', Result)-1);
end;
end;
end;

Probably the computer name on Unix is significantly shorter than the 256 characters that I have
reserved here, but I do it in a safe way since I don't know how it turns out to be.

For MAC OS X, the return name is normally supplemented with a ".local", under which the MAC can
be addressed overall in the network. For our purposes, however, we only need the name as it will
usually appear in the system.

A similarly useful feature that you could still implement from the Posix.unistd would be the "getLogin"
function, with which you can query the user name. Since we don't need here any buffer, in which the
name must be stored, we can use this function directly as:

strUsername := UTF8ToUnicodeString (GetLogin).

COREAPI

You will find most of the core API's in the unit "MacApi.CoreFoundation.pas". Again, take a look at
the Delphi folder structure. The MacApi.CoreFoundation.pas can be found at:

C:\Program Files (x86)\Embarcadero\Studio\15.0\source\rtl\osx


Also look once into the unit, which is a short unit that integrates a number of include-files, such as the
CFString.inc file that implements some CFString functions in Delphi, which we need to work with
CFString objects.

Also the Core API is accessed via a C call systematic. The main difference with the POSIX functions is
that the "Reference counted objects" are behind the so-called Core API. It means that objects
sometimes even COCOA objects are behind functions or data structures.

If you want to use strings as passing parameters here, they cannot be Delphi strings, but CFStrings,
reference counted string objects.

Here is an example that demonstrates the use of the CFStrings:

procedure mac_ShowMessageNative (aHead, AMsg: string);


var
CFHead, CFMsg: CFStringRef;
AResult: CFOptionFlags;
begin
CFHead := CFStringCreateWithCharactersNoCopy
(NIL, PChar (AHead), Length (aHead), kcfAllocatorNull);

CFMsg:= CFStringCreateWithCharactersNoCopy
(NIL, PChar (AMsg), Length (aMsg), kcfAllocatorNull);

try
CFUserNotificationDisplayAlert
(0, 1, NIL, NIL, NIL, CFHead, CFMsg, NIL, NIL, NIL, AResult);
finally
CFRelease (CFHead);
CFRelease (CFMsg);
end;
end;

Here the Core-Foundation function "CFUserNotifiationDisplayAlert" is implemented. We use here


only a very simple implementation. If required, the alert-function could even been shown with a time-
out value, with a further button and user-defined text for the button. The exact skills of the function can
be seen in the MAC Developer Library on the following page:

http://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFUserNotificationRef/Refe

The "CFStringCreateWithCharactersNoCopy" function comes from the MacApi.CoreFoundation.pas


(or CFString.inc). It creates the string object.

All functions whose names contains a "Create" or "Copy" include, when they are called, that the
reference-counter to the object is increased by the value of "1". After using the objects, you must then
release them with "CFRelease" so that the reference-counter can be reduced by the value of "1" again.
When the reference-counter is reset to zero, the object is total released. If you not do it, you will retain
residual objects in the memory that will stay after your program ends. I do not know how for the
programs for the MAC App Store are checked for such shortcomings. I would advise to work carefully
with such objects.

One important information for functions that have a "Get" in their name: Their objects are not copied or
new created. You just use the original actually. Directly after using the "Getxxx" function, you have to
call the function "CFRetain" before you continue with the string objects. CFRetain, then, also performs
an increase of the reference-counter and assures you the further use of the object. After completion of
the work with the object, you give it free with "CFRelease", which let the reference-counter decrease
by "1".

COCOA API

The API's from the COCOA Framework are specifically tailored for the use with Objective-C. You will
find many objects and functions in the "MacApi.Foundation.pas" implemented, for example, the entire
URL functions I use in the HSW.FMXSandbox.pas unit. Look once in this MacApi file, you find it in
the following folder:

C:\Program Files (x86)\Embarcadero\Studio\15.0\source\rtl\osx

The COCOA "objects" (Objetive-C classes, meta-classes and protocols) are usually implemented as
interfaces. Therefore, you will find two interface implementations for the NSString object / class, one
as

NSString = interface(NSObject)

and as

NSStringClass = interface(NSObjectClass).

The same applies, for example, for NSURL and NSURLCLASS. It is important to know it because you
will sometimes need functions from one and the other interface. By the way, a lot is implemented here,
but not all. Sometimes you need to upgrade individual functions through a re-implementation by
yourself, as I have made it, for example, in the HSW.FMXSandbox.pas unit.

But again, let's look at an example of how to use COCOA objects. Here we use a NSWorkspace object
(the unit MacApi.Appkit.pas must be included). However, I will show you first how it does not work,
because that is what you would normally try (and is often seen in forums where people asks why this
does not work):

var
URL : NSURL;
Workspace : NSWorkspace;
begin
URL := TNSURL.Create;
URL.initWithString(NSSTR('http://www.hastasoft.de'));

Workspace := TNSWorkspace.Create;
Workspace.openURL(URL);

URL.release;
Workspace.release;

It will not work because as a result of the Delphi Objective-C bridge, just a "Raw" object is supplied
back, which is not usable in this case. And for the Workspace object, it is important, that only one
shared workspace object is available for each program , to which must be accessed via the
"sharedWorkspace".

Details to this extremely useful object can be found here:

https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspac

The proper use of it looks like this:

var
URL : NSURL;
Workspace : NSWorkspace;
begin
url := TNSURL.Wrap(TNSURL.OCClass.URLWithString
(NSSTR('http://www.hastasoft.de')));
WorkSpace := TNSWorkspace.Wrap
(TNSWorkspace.OCClass.sharedWorkspace);
Workspace.openURL(URL);

There is no need to release anything because nothing was created. Only variables (objects) with content
were set .

Although the most COCOA objects are reference-counted, this works automatically usually. In the
example case, therefore, neither a "retain" nor a "release" after the use of the object is required.

In COCOA objects, it is required to work with "Release" functions only in exceptional cases, which
simplifies the working with these objects very much.
Chapter 4: Requirements for Cross-Platform
Development
Section 1: Setting up Windows PC and MAC PC
You must have a Windows PC and a MAC, which are connected via a network (wireless or wired
network). Alternatively, you can also set Windows in a virtual machine on the MAC. I like the latter
solution not so much because I've run on my MAC different partitions with different MAC OS X
versions according to my needs. And then it's an advantage to have your own Windows PC with Delphi
installed there.
Preparation
You will find on your Windows PC, under the following directory,
C:\Program Files (x86)\Embarcadero\Studio\19.0\PAServer

the file "PAServer19.0.pkg". Copy this file to the MAC (e.g. on the desktop there). The file
"Setup_paserver.exe" - is not needed (it is used, if you want to perform on another Windows machine a
remote debugging).

Small digression: How to set up a drive connecting from your Mac to your PC: Open the Finder and
choose the "Connect to Server" command from the menu "Go to":

Click on "Connect". If you do the connection for the first time, this dialog will appear:
Enter the user name of the Windows PC and below also the server password (not the password of the
MAC-PC).

If the connection works, you have a new volume (drive) available on the MAC. You can share your
files with this drive.

Then, double click on the .PKG file on the MAC and it will be automatically extracted. Double-click
then on the unpacked file "Setup_paserver". The following setup program starts, which installs the
PAServer on the MAC.

The PAServer program is an interface program that Delphi needs to transmit files over a TCP-IP
connection from your Windows PC to the MAC and then to start them there.

The PAServer is found, after installation, on the MAC in the program folder:

/Applications/PAServer19.0.app
On the Internet, it is also described in a docwiki how the server should be installed and run:
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Installing_the_Platform_Assistant_on_a_M
To start the server, go to the Applications folder and launch the application "PAServer 19.0"

A console window will be open then. ,There you just confirm the start with return if you don't want to
assign a password (in my opinion that is never needed).

Then the system will ask you to enter your admin password. If you have done this, the server program
is ready and waiting for a connection.

Delphi transmit then the MAC application produced on the Windows PC, by using the PAServer, to the
MAC into the so-called "ScratchDir". This can usually be found here:

/Users/YourUserName/PAServer/Scratch-Dir.
Section 2: Enabling MAC OSX Platform
By default, the MAC Platform is automatically available as a target-platform in a "Multi Device
Application" in the Project Manager. If you have once deleted it or don't find it in a project that you
have taken over from XE6 or earlier: you should add the platform to your project with a right-click on
the "Target Platforms" in the Project Manager and select the command "Add Platform" from the pop up
menu.

If you do this for the first time, the platform must first be established.

You may also need to make settings for the SDK. For this purpose, the PAServer on the MAC must
already be running. Click, with the right mouse button, on the platform name and select "Edit SDK"
command:

Note: It could be, that before the dialog below appears, you have to input a profile-name. And now to
the dialog:
For "Host-Name", I recommend, instead of the name of the MAC PC, to use the IP-address from the
MAC in the network (you will find the IP address of the MAC under "Control Panel, Network"). The
connection works faster whereas sometimes it works only with the IP address in networks mixed with
wired and wireless connections.

Note: If the IP address is dynamically assigned by your router, the IP address will change at the next
day, when you work again with your connection. If the submission of the files to the MAC does not
work (it then only gives the message "Error" without further explanation), this should be the first thing
to check.

TIP: You can also adjust the system settings of the MAC, so that a specific IP address always will be
used here. Then you will never need to change anything here again (I have done it like that and all
works excellent!).

Use the port number here as specified. Leave it blank under password or enter a password for the
PAServer if you have setup one (not necessary usually and it makes things only complicated).

But now, let's go back to the "Edit SDK" dialog. Here, you can select a suitable SDK., from which
there is normally one or several available to choose. Normally, select the newest. Or select an older one
if there are problems with the latest SDK. SDK versions are directly related to the respective OS
versions.

If you have added a platform, it could look like this in the project window:
Important note: Depending on whether you select the target platform "Windows" or "MAC OSX", the
presentation of the project options dialog also changes. Take a special attention here to the "Version
Info", because the info.plist file will be created with that later, which must accompany each MAC
program.

In the project options the "Entitlement List", where you can specify which rights are available to your
program, is also important. In particular, if you want to distribute your programs on the APPLE App
Store, you should choose carefully the settings here.

From the Entitlement-List, Delphi generates the ".Entitlements" file that will be distributed with the
program (which is especially important for the sandboxing model).

The Entitlement-List:

The Version-Info:
If needed, you can add a new key here. Right-click on the table header "Key" (shown here in yellow):

After choosing "Add Key", the below dialog will then appear where you can enter the new key-name:

Unfortunately, you can only enter single values in the grid-list. If you add an array structure, this looks
good at first sight:

However, the editor inserts an additional <String> entry here so that the key group ultimately can't be
utilized. Therefore (and as long as it is not fixed in Delphi), you must edit the info.plist file by yourself
in a text-editor so that the entries can be valid.

Section 3: Provisioning and deployment (MAC)


Under menu "Project", call the command "Deployment" and the register "Deployment" will be shown.
Here you can add more files to your project. They can, for example, be HTML help files, plain text files
or graphics that are required by your program. This is a really practical thing:

The .rsm-file is a file required only for the debugger. If you create manually a .dmg-file (Disk Image)
later, i.e., for distribution outside the app store, you must delete the file in the scratch-directory from the
MacOS folder before. It shows below how to create the .dmg file. The .rsm-file is also not required for
the distribution in the MAC App Store. Therefore, for all completed projects, delete this file from the
application bundle as far as Delphi has not yet done this for you.

If Delphi has created the program, Delphi will transmits it to the MAC into the Scratch-Dir directory
that you can find normally under

"/Users/YourUserName/PAServer/Scratch-Dir".

The executable file is usually located in the "MacOS" folder. Under "Contents", you will find the
"info.plist" and the "Entitlements.plist". The lybcgunwind.1.0.dlyb file, will needed in the delivery
version of the program, otherwise it won't run.
1. Submission to the APPLE App Store
For this, you have to switch from "Normal" to "Application Store" under the target platform MAC OS
X entry:

Under the menu "Project, Options, Provisioning", check whether "OS X Application Store" is selected
as the build-type. And of course, you should use the "Release configuration". In the two edit fields,
make entries as listed below:

The installation profile "3rd party Mac Developer Installer" is a certificate that is used to sign the
installer-file.

The "3rd party MAC Developer Application" is a certificate, that is used to codesign your application.

First check in the "Keychain Access" on your Mac if you have already installed the required certificates
on your MAC:
If you don't have such a certificate yet, you can get one from Apple, but you definitely need an Apple
Developer ID for it (see below, under 3a).

If you have called the command "Deploy <Projectname>" in the Delphi Project menu, Delphi will
transfer the required files to your MAC and call the code-signing tool that initially signed the
executable file (you will be prompted to allow this). Right after that, Delphi calls the "Product Build
Tool" that produces a so-called "Package" and a file with the extension ".pkg" will be created. This
"package" is then the installation-file. And that is the reason why it is also signed with the "Installer
Certificate".

You can then upload this completed package to Apple with the Apple program "Application Loader".
Firstly, however, you must create a data-entry for your program through iTunes Connect and complete
your input there with "Ready to upload binary". After two more confirmations for data security, you
can then upload your application (the package) with the Application Loader:

If your program is to be reviewed by the Apple employees, it usually takes 3-7 days until an assessment
is carried out.

To stay informed of the progress, you can use a free app from Apple (ITC-Mobile). Below is a
screenshot of my iPhone.

This allows you to manage the apps to a certain extent and examine your sales.
The programs with the green dots are already in the store. Those with orange dots are either waiting for
a review or are just in review. In the grey box, you can see the "OS X" or "iOS" label, which will
indicate if it is a program for the MAC or iPhone / IPad. Under the "Sales / Trends", you can check
how many products have been sold for a day or for a week. Also, you can see the countries in which
you have sold your programs. Surprisingly, I was able to find buyers in the US, Russia, Thailand,
Japan, Italy, Mexico and Slovakia even with my only German-language programs.

After a month, you will get one or more "Financial Reports" via e-mail from Apple, which you can
view over the web in iTunes Connect. There you can see how many units you have sold in the countries
and how much money is owed to you. And you will see when the next payment date is and when Apple
will transfer your share. But the money will only be paid when it is counted that you should get more
than $ 150. The first regular payment date is between 1-2 months.

A tip: Before you load a program into the Apple store, look at Apple's "App Store Review Guidelines".

Here is the link to:

https://developer.apple.com/app-store/review/guidelines
2. How to create a .dmg file for distribution outside the Apple App Store
I sell my programs even without the App Store. You can do this also as follows: After you have created
with Delphi a MAC OSX release version (with the project setting "Normal" and not "App Store"), run
the "Disk Utility" (Applications folder, "Utilities) on the Mac. Select the "File" menu and select "New",
"Disk Image from Folder":

The Finder dialog is displayed, so then can navigate to the directory where your program is:

Select the program file and click on the button "Image". The following dialog will be displayed:
I recommend removing the extension ".app" and choosing the location of the "desk". The TEditor.dmg
file created in this case, can be now distributed to your customers over the Internet.

After downloading by the user, the customer double-clicks on the .dmg-file and drags the file into the
program folder. The program is then installed.
3. Ho to create your own setup package with the Application Developer ID / Installer
It could be another possibility to sign the program with the "Developer Application ID" certificate, and
then create your own setup package and sign this with the "Developer ID Installer" certificate.

What are the advantages?

Well, from MAC OS 10.7.5 on, Apple uses the so-called "Gatekeeper" function that can be enabled in
the system settings under the "Security", "General" tab:

Even if it is written "Allow Download" there - it probably means "Run". Because you can download a
program which is located in a .dmg file from the Internet and even drag the application into the
Applications folder then.

If the user has activated the settings shown as above and is now trying to run your program that you
have sold as described above under number "2.", a dialog will be shown as follows:

So if you want to play it safely, you need to perform your program with the certificates described
above. In your Keychain Access, you must have the following (colored) certificates:
If you already have these certificates, you can skip the next subsection and read more in "Working with
CodeSign tool and Package Maker" under "3 b)".

a) How to request a Developer ID certificate and an Application Developer Installer


ID
If you do not already have such certificates, log in under https://developer.apple.com and click on
"Member Center" in the upper right corner.

Then click on the link shown below and you will be forwarded to the area where you can manage your
certificates:

Then click on the table: "MAC App's, Certificates". This brings you to the page to manage MAC
certificates. Click on the plus button on the right side at the top, then you lead you to the page where
you can choose the certificates according to your needs.

Ultimately, you need all certificates that are offered here (if you also want to distribute your app also
with the App Store). For our purposes, it is enough when you ask for the lower certificates ("Developer
ID", not visible in the screenshot above). Download it to your MAC and double click on the file so that
it will be installed into your Keychain Access (or drag it from the download-folder to the Keychain
Access-window).

I don't want to describe all the steps here, but maybe two tips on this:

Tip 1: Before you can install your certificates, you need the "WWDR Intermediate Certificate" and the
"Developer ID Intermediate Certificate". This notice is also displayed on the page shown above if you
scroll down.

Tip 2: In order to be able to create your certificates, you need a "CertifacteSigningRequest" file in
general.

Where do you get them from?

Use the program "Keychain Access". Go to the menu "Keychain Access", "Certificate Wizard" then
"Request a certificate from a certificate authority":

The following dialog will be displayed:

Once you have finished this dialog, you will receive the file requested. In my case, I had to create this
file several times because different request files were needed for the different certificates.
TIP: after I got my Developer ID's and this was transferred to the Keychain Access, it was first
displayed that they were invalid because it were signed by an "Unknown Certificate Authority". It
helped when to wait: 24 hours later, I downloaded the certificates again from the certificates area and
re-installed it. It was then all perfect.

b) Working with the code-signing tool and Package Maker


If you have Delphi run with your MAC OS X profile and created your MAC program, you must sign it
then.

Since the program is not for the App Store, you must select in the project options "OS X - Normal" as
'Build-type".

After running the program and transferring it to the MAC, you must use the code-signing tool manually
to sign your package. You can use it by invoking it from the command line. To do this, open another
terminal window and change to the directory where your application resides.

Then type in the terminal window:

codesign -s "Developer ID Application" TEditor.App


Here, of course, replace "TEditor.App" with the name of your application.

You'll again be prompted to allow the signing:


Now, you have signed your program, but how can you create the setup package?

You can do this with the "Package Maker" which you will no longer find by default on newer MAC OS
versions. So you must download it from Apple. This is done in the download area
(https://developer.apple.com/downloads/index.action?name=PackageMaker). Search here for
"Auxiliary Tools for X-Code".

Then, install the downloaded program package, open it and start the Package Maker program.

For MAC OS X Maverick or later, the program cannot be used (apparently) because it keeps crashing.
I've simply installed it on an older MAC OS X version (Mountain Lion) and used it there. Here you can
see that it could sometimes be useful to have also older MAC OS Systems in use (I have this on several
of partitions on my Mac). If that's too much trouble, you must use one of the commercial programs,
with which you can create installation packages for the MAC. If you can use the Package Maker,
proceed as follows:

At "Certificate", you click on the arrow, and enter your "Developer ID Installer" certificate. Also, you
can make basic settings, e.g., the title of the setup, where the program may be installed, and so on.

Next screen shot: just drag your program from the Finder into the left side of the window or click on
the button beside the line "Install" and go to your folder where the compiled and already signed
program is:
So you can easily use this dialog to make the necessary settings for your setup package.

If you click on "Edit Interface" in the upper right corner, a dialog will appear, where you can select the
individual installation steps in detail:

Here, you can directly insert your contract text or refer to a file. The license information must be
accepted by the user, otherwise the program cannot be installed.

Then click on the button "Build" or "Build and Run", then your setup package is created. You will be
prompted again to allow the signing and then your setup package is ready.

And it works well with the gatekeeper if the user should have set the installation restrictions described
at the beginning of this section. For me, this is certainly now the preferred distribution method (I used it
also for my MAC programs created previously with Lazarus).

Note: When running the setup-program on my MAC, the PackageMaker-Setup installed again and
again the program into the directory where the program was developed. I could not stop that, but
ultimately it has no negative impact, because on other MAC PC's it works as expected.
Chapter 5: Cross-Platform development with
Linux
The setup and development of programs for Linux is much easier than for Mac OS X. It should be
noted that the Enterprise version is required to develop Linux programs. Another limitation is that you
can only develop console programs, so no applications with a user interface for the desktop.

It is truly welcome that Delphi is now able to develop Linux programs. And it is said that one can
achieve a lot with it. But it is a pity, however, that Delphi does not have the possibility to develop
desktop programs for Linux.

However, here is already mentioned that you can use the FMXLinux-Add On from KSDEV-Software,
with that you can also develop desktop programs for Linux.

Embarcadero tell us, that Ubuntu and Red Hat are supported as Linux-OS, but it is assumed that the
PAServer program also runs under Suse Linux or other Linux derivatives. However, you need a 64-bit
Linux operating system, but this should not be a problem.

What should generally be considered in the Linux64 platform is the fact that no AnsiStrings are
supported.

In addition, Automatic Reference Counting (ARC) is also used for Linux (as for the mobile platforms)
for memory management.

All pointers are in Linux 64 bit, but an integer is still 32 bit.


Section 1: Setting up Windows and Linux-PC
You need a Windows and a Linux PC that are connected over a network (WLAN or cable network).
Alternatively, you can set up Windows in a virtual machine on the Linux machine or you can set up a
virtual machine with Linux on the Windows PC. I personally work now with Linux (Ubuntu 16.04
LTS) as main computer, so that I have here in a VirtualBox a Windows 10 running.

Preparation
On your Windows PC, you will find the following directory

C:\Program Files (x86)\Embarcadero\Studio\19.0\PAServer

Copy the file "LinuxPAServer19.0.tar.gz" file to the Linux PC (for example, to the desktop).

A small excerpt: How to set up a drive connection of your Linux PC to your Windows PC: On Linux,
start the file manager and select "Connect to server" on the left. The following dialog will then appear,
which allows you to connect to your Windows drive:

Click "Connect".

Note: The name of the computer name does not work sometimes. Then you have to enter the IP address
of the computer instead of the computer name (here ew1), so I still had success.

If you set up the connection for the first time, this dialog appears:
You can first try to set up the" anonymous connection". Which access options you have on the
Windows PC depends on how you set up the network share.

If this does not work, try "registered user".

Under "User name" you have to enter the current user name of the Windows PC and under "Password"
also the password there (not the password of the Linux PC).

If the connection has worked, a new medium (drive) is available on the Linux:

You can use this drive to exchange your files.

Then, on the Linux PC, doubleclick the .tar.gz file, then the Linux Archive Manager is started:
Here you can unpack the contained files by clicking on the "unpack" button. As Target you choose your
home directory (or any other one where you have unlimited write permissions).

Then open a console on the Linux PC, change to the directory where the paserver program is located
and start it with the command:

./paserver

A console window ("Terminal") is opened, where you simply confirm the execution with Return, if you
do not want to assign a password (I do not think it is necessary).
Here the result that is displayed on my Linux-PC (I don't have installed it in the home directory, but on
an external hard drive, which is attached to Media).

When you have done this, the server program is ready and waiting for a connection.

Also under Linux the PAServer-program represents an interface program, which Delphi needs to
transfer the generated files from the Windows PC to the Linux computer into the so-called "Scratch-
Dir" directory, where it than will be started.

This is usually found here, where "UserName" would be replaced by your current user name:

/home/UserName/PAServer/Scratch-Dir.

If necessary, you can also switch to this directory in a new console and start your program manually
(do not forget to enter "./" before the program name).

Also useful are the query options of the PAServer program, which can be used for example to show the
available IP addresses of the Linux machine or the directory of the Scratch-Dir location:
Section 2: Enabling the Linux-Platform
As I said before, by default, you can only create console applications for Linux:

Likewise, after selecting this application, the Linux platform is not activated automatically. Add the
Linux platform to your project by right-clicking on the "Target Platforms" entry in the project
management and choosing "Add Platform" from the pop-up menu.

If you do this for the first time, the platform must first be set up.

You may still need to make settings for the SDK. To do this, the PAServer must already be started on
the Linux machine. Right-click on the platform name and select "Edit SDK":
Before I go into this dialog: Possibly you will be prompted to assign a profile name. This then looks
like this:

In the case of "hostname", instead of specifying the name of the Linux computer, I advice to enter its IP
address (the IP address of the Linux PC can be found under Control Panel, Network).

For the rest, I would like to refer to the explanations already made during the establishment of the MAC
SDK.

But now back to the "Edit SDK" dialog. Here you can choose between one or more SDK's. Normally,
select the latest SDK. Select an other SDK, if there are problems with the latest SDK. SDK versions are
directly related to the respective Linux versions.

This will meet now all requirements and you can develop your first Linux console application.
Section 3: Provisioning and deployment (Linux)
In the Project submenu, use the deployment command to access the deployment tab where you can add
additional files to your project. Simple text files or graphics that are required by your program. This is a
really practical thing, so you have everything in the overview:

Here is just the application itself for transfer to the Linux computer provided.
Section 4: A first Linux-Console-Application
If you selected "console application" in Delphi under "File", "New", "More", you will get the following
program (which I have already supplemented with the entry "Writeln ('Hello World!')":

I have already activated the Linux platform here, as already described above.

When you start the program, you will see the following output in the Delphi event log window:

Note: The output is only displayed in the Event Log window when the application has been run in
debug mode. You activate the event log window via the menu "View", "Debug window", "Event log".

If you do not start the program in debug mode, the output is displayed in the terminal window of the
PA server.
Sometimes there are situations where you have to start the Linux program directly on the Linux
machine, for example when the program will wait for user input, e.g. via ReadLn. The PAServer
terminal window is not able to do this. We can look at this directly at the next project.
Section 5: Linux-Server-Console-Application and Client-
Application
Next, we will create a very simple server server-client application using the Indy components. The
client program will connect to the server program and may submit text to the server, which is then
displayed in the terminal window. It can also query the server program on which operating system it is
running and display the result in a memo window.

To do this, we create a console application and add the text as follows:

program ServerApp;

{$APPTYPE CONSOLE}

{$R *.res}

uses
System.SysUtils,
DMUnit in 'DMUnit.pas' {DataModule1: TDataModule};

var
s: string;
begin
try
Writeln ('Serverprogramm V .091 gestartet.');
Writeln ('Exit Prog by "stopserver" command.');

DataModule1.idserv.Active := True;

if DataModule1.idserv.Active then begin


writeln ('Server active on Port:' +
DataModule1.idserv.DefaultPort.ToString);

repeat
Readln (s);
until s = 'stopserver';

writeln ('Programm beendet');


end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

We then add a DataModule to the program. Forms are not allowed, but DataModules are. Here we can
add an Indy-TIdTCPServer component:
For "DefaultPort", set the value "24600" in the object inspector.
Then, click the "OnConnect", "OnDisconnect" and "OnExecute" events one by one in the Object
Inspector:

and add the source-code like follows:

unit DMUnit;
interface
uses
System.SysUtils, System.Classes,

// Indy
IdBaseComponent, IdComponent, IDGlobal,
IdCustomTCPServer, IdTCPServer, IdContext;
type
TDataModule1 = class(TDataModule)
idserv: TIdTCPServer;
procedure idservConnect(AContext: TIdContext);
procedure idservDisconnect(AContext: TIdContext);
procedure idservExecute(AContext: TIdContext);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;

var
DataModule1: TDataModule1;

implementation

{%CLASSGROUP 'System.Classes.TPersistent'}

{$R *.dfm}

procedure TDataModule1.idservConnect(AContext: TIdContext);


begin
Writeln ('Server connected');
end;

procedure TDataModule1.idservDisconnect(AContext: TIdContext);


begin
Writeln ('Server disconnected');
end;

procedure TDataModule1.idservExecute(AContext: TIdContext);


var
cmd, txt: string;
begin
with AContext.Connection do begin
IOHandler.DefStringEncoding :=
IndyTextEncoding_UTF8;
cmd := IOHandler.ReadLn;

if cmd = 'cmd:displaytext' then begin


txt := IOHandler.ReadLn;
Writeln (txt);
end;

if cmd = 'cmd:getos' then begin


IOHandler.WriteLn(TOSVersion.ToString);
end;
end;
end;

Initialization
DataModule1 := TDataModule1.Create(NIL);
end.

If you have created the server program, start it, and in the terminal window you will get the following
output:
If you want to quit the program, press the Enter key once in the terminal window, enter "stopserver"
and confirm with the Enter key. The message "Program terminated" appears.
But let it run quietly, in the meantime, we create a client application that can communicate with the
server program.
At the design time the program looks like this:

The corresponding program code looks like this:


unit Main;
interface
uses
System.SysUtils, System.Types, System.UITypes,
System.Classes, System.Variants,FMX.Types,
FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.StdCtrls, FMX.ScrollBox, FMX.Memo,
FMX.Edit, FMX.Controls.Presentation,

// Indy
IdBaseComponent, IdComponent, IdGlobal,
IdTCPConnection, IdTCPClient;

type
TFMain = class(TForm)
bnConnect: TButton;
bnDisconnect: TButton;
cl1: TIdTCPClient;
edSend: TEdit;
bnSendText: TButton;
bnRequestOS: TButton;
Label1: TLabel;
Panel1: TPanel;
mOSInfo: TMemo;
Label2: TLabel;
procedure bnConnectClick(Sender: TObject);
procedure bnDisconnectClick(Sender: TObject);
procedure bnSendTextClick(Sender: TObject);
procedure bnRequestOSClick(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;

var
FMain: TFMain;

implementation

{$R *.fmx}

procedure TFMain.bnConnectClick(Sender: TObject);


begin
cl1.Connect;
end;

procedure TFMain.bnRequestOSClick(Sender: TObject);


begin
if cl1.Connected then begin
cl1.IOHandler.DefStringEncoding :=
IndyTextEncoding_UTF8;
cl1.IOHandler.WriteLn('cmd:getos');

mOSInfo.Lines.Text := cl1.IOHandler.readLn;
end else begin
ShowMessage ('Connect first');
end;
end;

procedure TFMain.bnDisconnectClick(Sender: TObject);


begin
cl1.disconnect;
end;

procedure TFMain.bnSendTextClick(Sender: TObject);


begin
if cl1.Connected then begin
cl1.IOHandler.DefStringEncoding :=
IndyTextEncoding_UTF8;
cl1.IOHandler.WriteLn('cmd:displaytext');
cl1.IOHandler.WriteLn (edSend.Text);
end else begin
ShowMessage ('Connect first');
end;
end;

end.

At runtime you will have the following output:


In the terminal window::

And below the Windows-FMX-program (which could alternatively also be created as a MAC-OS X
application):
You can download the demo programs here:
http://www.devpage.de/download/fmbook4/ServerApp.zip
http://www.devpage.de/download/fmbook4/ClientApp.zip
Section 6: Create an Application as Service (Daemon)
From Windows, you may already know the "service", an invisible program that runs in the background
and does not accept any input and does not generate any output on the screen.
There is something like this under Linux, which is called "daemon". So if you want to run your server
application in the background and do not have to input and output via the terminal (because, for
example, the communication is only controlled via third-party programs), create a "daemon".
This requires only minor modifications to your main program. This would be a very simple variation:
program Daemon;

{$APPTYPE CONSOLE}

{$R *.res}

uses
System.SysUtils,
Posix.Unistd,
Main in 'Main.pas';

begin
try
if fork()<>0 then begin
exit;
end;

while Working do begin


sleep(100);
end;

except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

The function "fork" from the unit "Posix.Unistd" is relevant here. With its call, it creates an exact copy
of your program, only the standard input and output are ignored.
You can find a description of fork here:
https://linux.die.net/man/3/fork
If the return value of fork is equal to zero, it means that we are currently in the generated copy. And
that is what we want to use, so the program continues.
If the return value is <> 0, we are in the original process, which we want to quit, we simply do not need
it, because the work is supposed to run in the background process. With "Exit", the original process is
terminated at this point.
To have the option to end the copy of the generated process (the daemon), we have a boolean variable
"Working" in use here. This is preset with "True" and thus ensures that the program continues to run. If
you set the variable to "False" later, the daemon is terminated.
But how do you do that, when no input is available via the console?
The simplest way would be to communicate over TCP / IP. Your client application can then transmit an
agreed signal to the server program, which sets the working variable to "False". In the example from
Section 5, this could be done in the OnExecute event of the Indy server component.
Section 7: Delphi units for Linux
Here you can see which units you currently can use (April 2017) under the Linux platform.
System-Units
SysInit.pas
System.Bindings.Consts.pas: this and following units have Live Bindings support
System.Bindings.CustomScope.pas
System.Bindings.CustomWrapper.pas
System.Bindings.EvalProtocol.pas
System.Bindings.EvalSys.pas
System.Bindings.Evaluator.pas
System.Bindings.Expression.pas
System.Bindings.ExpressionDefaults.pas
System.Bindings.Factories.pas
System.Bindings.Graph.pas
System.Bindings.Helper.pas
System.Bindings.Manager.pas
System.Bindings.ManagerDefaults.pas
System.Bindings.Methods.pas
System.Bindings.NotifierContracts.pas
System.Bindings.NotifierDefaults.pas
System.Bindings.ObjEval.pas
System.Bindings.Outputs.pas
System.Bindings.Search.pas
System.Bluetooth.Components.pas
System.Bluetooth.pas
System.Character.pas: this unit has Unicode support at the codepoint level
System.Classes.pas: basic classes for TComponent etc
System.ConvUtils.pas: convert units of measurement
System.DateUtils.pas: dates processing
System.pas: the core unit
System.Diagnostics.pas
System.Generics.Collections.pas: as the name says, generic collections
System.Generics.Defaults.pas
System.Hash.pas: hashing support has been extended with file hashing
System.HelpIntfs.pas
System.IniFiles.pas: these clone the Windows INI files
System.Internal.DebugUtils.pas
System.Internal.ExcUtils.pas
System.Internal.JSONHlpr.pas
System.Internal.StrHlpr.pas
System.Internal.VarHlpr.pas
System.IOUtils.pas: support for modern file system access (input/output utilities)
System.JSON.BSON.pas: this and the following units have JSON and BSON streaming support
System.JSON.Builders.pas
System.JSON.Converters.pas
System.JSON.pas
System.JSON.Readers.pas
System.JSON.Serializers.pas
System.JSON.Types.pas
System.JSON.Utils.pas
System.JSON.Writers.pas
System.JSONConsts.pas
System.Linux.Bluetooth.pas
System.Masks.pas
System.MaskUtils.pas
System.Math.pas: Core mathematical functions
System.Math.Vectors.pas
System.Messaging.pas
System.Net.FileClient.pas: the "Net" units implement the HTTP client library
System.Net.HttpClient.pas
System.Net.HttpClient.Linux.pas
System.Net.HttpClientComponent.pas
System.Net.Mime.pas
System.Net.Socket.pas
System.Net.URLClient.pas
System.NetConsts.pas
System.NetEncoding.pas
System.RegularExpressions.pas: these units have RegEx support
System.RegularExpressionsAPI.pas
System.RegularExpressionsConsts.pas
System.RegularExpressionsCore.pas
System.RTLConsts.pas
System.Rtti.pas: Core RTTI access unit, or reflection support
System.Sqlite.pas
System.StartUpCopy.pas
System.StdConvs.pas
System.StrUtils.pas: core string processing utilities
System.SyncObjs.pas: threads synchronization
System.SysConst.pas
System.SysUtils.pas: the most classic system utilities
System.Tether.AppProfile.pas: AppTethering support
System.Tether.Comm.pas
System.Tether.Consts.pas
System.Tether.Manager.pas
System.Tether.NetworkAdapter.pas
System.Tether.TCPProtocol.pas
System.Threading.pas: this unit defines the Parallel Programming Library (or PPL)
System.TimeSpan.pas
System.Types.pas
System.TypInfo.pas: the more traditional RTTI access
System.UIConsts.pas
System.UITypes.pas
System.VarCmplx.pas: these units offer limited variants support
System.VarConv.pas
System.Variants.pas
System.VarUtils.pas
System.Zip.pas: compression support
System.ZLib.pas
System.ZLibConst.pas

Linux und Posix Units

Posix.ArpaInet.pas
Posix.Base.pas
Posix.Dirent.pas
Posix.Dlfcn.pas
Posix.Errno.pas
Posix.Fcntl.pas
Posix.Fnmatch.pas
Posix.Grp.pas
Posix.Iconv.pas
Posix.Langinfo.pas
Posix.Limits.pas
Posix.Locale.pas
Posix.NetDB.pas
Posix.NetIf.pas
Posix.NetinetIcmp6.pas
Posix.NetinetIn.pas
Posix.NetinetIp6.pas
Posix.NetinetTCP.pas
Posix.NetinetUDP.pas
Posix.Pthread.pas
Posix.Pwd.pas
Posix.Sched.pas
Posix.Semaphore.pas
Posix.Signal.pas
Posix.StdDef.pas
Posix.Stdio.pas
Posix.Stdlib.pas
Posix.String_.pas
Posix.StrOpts.pas
Posix.SysMman.pas
Posix.SysSelect.pas
Posix.SysSocket.pas
Posix.SysStat.pas
Posix.SysStatvfs.pas
Posix.SysSysctl.pas
Posix.SysTime.pas
Posix.SysTimes.pas
Posix.SysTypes.pas
Posix.SysUio.pas
Posix.SysUn.pas
Posix.SysWait.pas
Posix.Termios.pas
Posix.Time.pas
Posix.Unistd.pas
Posix.Utime.pas
Posix.Wchar.pas
Posix.Wctype.pas
Posix.Wordexp.pas

More units
You can also use the Indy components, FireDAC, Dataspan, EMS, WebBroker and DunitX.

In so far a quite extensive kit, with which one can work.

Tip: With the use of FmXLinux (see www.fmxlinux.com), almost all units are available as usual, with
which you can also create desktop programs, eg. FMX.Forms, FMX.Dialogs, etc.
Chapter 6: Working with Graphics in
FireMonkey
1. FireMonkey TBitmap versus Windows TBitmap

The Windows bitmap and FireMonkey bitmap are different. This particularly concerns the pixel
formats. While the Windows bitmap has, beside the 24-bitmap format, other bitmap formats available,
some output formats in FireMonkey are missing. In particular, for 1-bit, 4-bit, 8-bit, 16-bit and 24-bit,
no output format is available if you want to save the bitmap with the extension ".bmp".

While you can read and write the property "PixelFormat" in Windows TBitmap, it is just as a reading
property available in FireMonkey. There is a private "SetPixelFormat" procedure in the TBitmap class.
You could perhaps make this available with a class helper function, but it probably it will not make
much sense since FireMonkey always operates with a 32-bit bitmap internally.

However, when using the formats that typically include an alpha-channel, e.g., the format "PNG" or
"TIF", these relevant informations are also written to the image file.

When loading an 8-bit bitmap file in BMP format, the colors are taken over correctly into the
FireMonkey bitmap. But after saving the bitmap file, it is a 32-bit bitmap file.

And, unfortunately, the informations will be lost if one loads a Windows 32-bitmap file with an alpha-
channel into a FireMonkey bitmap. The values of the alpha channel will not be adopted. They all have
the value of 255 (which stands for completely visible). I am not quite clear whether this is a bug.

2. TBitmapData instead of ScanLine for bitmap manipulation

While you can process bitmaps in the VCL with the "ScanLine" function, it will be replaced by the
TBitmapData record under FireMonkey, from Delphi XE3 on.

This record is defined as follows:


TBitmapData = record
private
FPixelFormat: TPixelFormat;
public
Data: Pointer;
Pitch: Integer;
property PixelFormat: TPixelFormat read FPixelFormat;
function GetPixel(const X, Y: Integer): TAlphaColor;
procedure SetPixel(const X, Y: Integer; const AColor:
TAlphaColor);
end;
Before you can access the pixels, you must ask for the access to the bitmap with the function "MAP",
with which, depending on the type of the access, this bitmap will be disabled for other processes. This
contributes to the thread safety of bitmap editing. If you have finished editing the bitmap, you release
the access with "UNMAP".

The access type is determined by the TMapAccess, which is defined as follows:

TMapAccess = (maRead, maWrite, maReadWrite);

For example, to manipulate a particular pixel in a bitmap, it looks, as a whole, like the following
(assuming myBitmap is a global bitmap-variable):

procedure TF_Main.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,


Y: Single);
var
vBitMapData : TBitmapData;
vPixelColor : TAlphaColor;
begin
if MyBitmap.Map(TMapAccess.maWrite, vBitMapData) then begin
vBitmapData.SetPixel (Round (x), Round (y), TAlphaColors.Red);
MxBitmap.Unmap(vBitMapData);
end;
end;

If you also need a reading access, you must use "maReadWrite". If you need only read access, use
"maRead".

The example is simple and then easier to understand. But in your own projects, you should, of course,
work with TRY ... FINALLY constructs to ensure that the blocking of the bitmap will be cancelled in
the case of a fault.

3. How to change the alpha channel of a TBitmap

If you want to change only the alpha value of a pixel, you need to use a little trick. Use the
TAlphaColorRec to get directly access to the byte which is responsible for the value of the alpha-
channel.

This works as follows:


procedure TF_Main.SetAlpha(X,Y: Integer; AVal: Byte);
var
vBitMapData : TBitmapData;
vPixelColor : TAlphaColor;
begin
if MyBitmap.Map(TMapAccess.maReadWrite, vBitMapData) then begin
vPixelColor := vBitmapData.GetPixel (x, y);

TAlphaColorRec(vPixelColor).A := AVal;

vBitmapData.SetPixel (x, y, vPixelColor);


MxBitmap.Unmap(vBitMapData);
end;
end;

4. How to draw on the canvas of a bitmap


As it is known from the Windows bitmap, it is also possible to draw on the canvas of a FireMonkey
bitmap. You can use this as "Bitmap.canvas.Fillrect", "Bitmap.canvas.Fillelipse" etc. The well known
"canvas.textout" function under Windows is not available here. Use the "canvas.Filltext" instead.

Finally a special note: You must call "Canvas.BeginScene" before all the drawing actions on the canvas
and "Canvas.EndScene" after completion.

So this would look like, for example, as follows:

If Bitmap.canvas.beginscene then begin


try
Bitmap.canvas.Filltext (...);
finally
Bitmap.canvas.EndScene;
End;
End;

With Delphi 10.2, TBitmap, TCanvas and TContext 3D were made threadsafe. This means that bitmaps
can be generated and used by several threads "simultaneously". This is done in that way, that the
accesses are serialized internally (added into a queue). In fact, only one thread at a time has access to
the resource at the same time, but you do not have to worry about it, because Delphi takes the work off
you.

An explicit synchronization as before, is therefore no longer necessary! Of course, a "logical


synchronization" is still necessary. So you have to take care fore, that objects are not deleted by a
thread, while there are still in read or write accesses.

As a consequence of this innovation, you should keep access to the canvas in iterative loops as short as
possible so that accesses are not blocked too long.

Instead of as before:

Canvas.BeginScene;
try
for I := 0 to 10000 do
begin
// thread code
// drawing code
end;
finally
Canvas.EndScene;
end;

your write now:

for I := 0 to 10000 do
begin
// do "normal" thread work
Canvas.BeginScene;
try
// do drawing code
finally
Canvas.EndScene;
end;
end;

5. How to turn graphics, flip, invert or color to gray


A series of graphics processing functions are already available by default in FireMonkey.

Want to rotate an image left or right? Nothing easier than that:

Bitmap.rotate (270); // Turn bitmap left


Bitmap.Rotate (90); // Turn bitmap right

To mirror a graphic horizontally or vertically:

Bitmap.Fliphorizontal; // mirroring the picture horizontal


Bitmap.FlipVertical; // mirroring the picture vertical

To invert an image or to change to gray color, you can use one of the most extensive filter functions
supplied in FireMonkey.

A nice example among the FireMonkey demos is the one that demonstrates what filters are available in
FireMonkey. You will find it in the examples-folder in the subfolder "shader filter".

Although the demo can demonstrate a lot, it's a pity that it written in a way that many functions are
generated at runtime or dynamically. If we look into the source code, it is not very helpful
unfortunately.

I have, therefore, created a simple function which illustrates the use of filters.

For example, to invert an existing bitmap, it is sufficient to call the function as follows:

MyBitmap.Assign (ImgByFilter (MyBitmap, 'Invert'));

And so looks the function (the unit "FMX.Filter" is required):

function ImgByFilter(bm: TBitmap; FilterName: string): TBitmap;


var
bmold: TBitmap;
Filter: FMX.Filter.TFilter;
begin
Filter := TFilterManager.FilterByName(FilterName);

bmold := TBitmap.Create (0,0);


bmOld.Assign(bm);
if Filter <> nil then
begin
// set input
Filter.ValuesAsBitmap['Input'] := bmOld;
// set Target only for transition
Filter.ValuesAsBitmap['Target'] := bm;
// apply and get into result
Result := TBitmap(Filter.ValuesAsBitmap['output']);
Filter.Free;
end;

bmOld.Free;
end;

To change a bitmap into gray color, call the filter function as follows:

Myitmap.Assign (ImgByFilter (MyBitmap, 'Monochrome'));

Of course, with this function, only the filters are applied that work without additional settings.

In the FireMonkey demo mentioned above, the use of filters with settings has been solved so that a
filter attribute-record with the possible attribute-names and setting values of the filter will be filled at
runtime. This information is used to generate dynamically TTrackbars that are generated with the
minimum and maximum values of the filter attributes. This TTrackbars are also associated with an
event handler to respond to changes in TTrackbars and then apply the filter to the bitmaps accordingly.

This is also the reason why the demo is not so easy to understand when you look at the source code of
the demo.

You could extend the "ImgByFilter" function, for example, to integrate the following settings:

Filter.ValuesAsPoint
Filter.ValuesAsColor
Filter.ValuesAsFloat
Filter.ValuesAsTexture

For example, in order to apply the sepia-filter with a specific value you would - before calling the
ValuesAsBitmap with the "output" option - use the following line in the above function before:

Filter.ValuesAsFloat ['Amount'] := 0.2;

Here you would get a 20% sepia-coloring of the bitmap.

It should be noted, however, that not all settings are used with "Amount". Some use, for example,
"Levels", "Length" or "Opacity" and the settings are not always between 0 and 1, they can have also
other minimum and maximum values.
To get this informations, the shader filter demo mentioned before can help you. There, you can simply
click on the filter name and you will get the individual settings name and the value ranges will be
shown. Here is an example of the "sharpening" function:

And here is an example of the "Emboss" function which uses two setting attributes at the same time:

Overall I think, that the graphics capabilities of FireMonkey are quite impressive.

6. Hot to draw a bitmap scaled

From the VCL, you know, for example, the "StretchDraw" function which allows you to draw scaled
graphics. In FireMonkey, you can use the function "DrawBitmap". I use this, for example, also in my
App Store program "MultiScreenCopy":
So just assumed you have a bitmap in the TImage component "Bild" in the size of 1680x1050 pixels. It
can be scaled, for example, as shown here, down to 1024x640 pixels (proportional).
if ShowModal = mrOK then begin
bm := TBitmap.Create(StrToInt (ceNewWidth.text), StrToInt
(ceNewHeight.text));

bm.Canvas.BeginScene;

bm.canvas.DrawBitmap(Bild.Bitmap, RectF(0, 0,
Bild.Bitmap.Width, Bild.Bitmap.Height),
RectF(0,0, StrToInt (ceNewWidth.text), StrToInt
(ceNewHeight.text)), 1, False);

bm.Canvas.EndScene;
End;

So you first create the bitmap in the desired new size and then paint on the canvas with "DrawBitmap".
You can use the original bitmap, its size and the new desired output size as a parameter. To define the
transparency, you set it to "1" for fully visible, and the interpolation-mode ("HighSpeed" =) set to
"False".
Chapter 7: 3D-Programming
Section 1: Overview
1. 3D-Objects
With 3D elements, you can display objects in 3D space. This is achieved by adding the value "Depth"
for the 3D objects (TSphere, TCube, TCone, and so on) next to the value for "Width" and "Height".

A special feature applies to (normally) flat 2D objects in 3D space, such as TImage3D and
TTextlayer3D. There is a hard-coded value for the depth, namely 0.01, so there is also no adjustable
property for "Depth".

In addition, there is also the value "Position.z" for the position next to "Position.x" and "Position.y",
which indicates the spatial arrangement forwards or backwards.

I ncr easi ngposi tive Zvalues ​means t hat th e object i s moved fur t her backwar ds int o t he s pace , sot he
o bject i s r educedi ns i ze as viewed f r omt he fr ont . Negat i ve Zval ues ​br ing i t cl oser t o the foregr ound,
so it is viewed from the front, larger.

The spatial impression is also generated by a changeable viewing direction. This can be achieved e.g.
with a TCamera component from which you can have one or more on your form, so you can switch the
direction of view.

2. Cameras
Each view of the 3D space is controlled by a camera. The position and orientation (3D rotation) of the
camera determines what you see.

Each 3D form already has an integrated camera (design camera) which is activated by the
"UsingDesignCamera" property by default. It is used in the Form Designer and by default at runtime.

A 2D form does not have this property, thus not a standard camera. In 2D forms, you display 3D
elements within a 3D ViewPort, and this component also has the "UsingDesignCamera" property. In
either case, you can disable this option and add one or more custom camera components.

3. Screen Projection
By default, the Projection setting for each 3D object is set to "Camera". Therefore, if the camera
position change, the 3D object will be possibly out of view. With a setting "Screen" for projection, you
can ensure that objects remain in the view regardless of the camera view (for example, position
information or other status information).

Attention: If you change this setting to "Screen", the values of units changes to pixels . Then, the upper
left corner becomes again the zero point, but the 3D position still indicates the center of the object, not
its upper left corner as in 3D:
Here you see: Although the object is in the upper left-hand side, the values X and Y are not zero
because, as I said, the center of the object still determines the values for X and Y.

4. Rotations
A spatial impression can also be produced by turning the objects. You can use the individual values
RotationAngle.x and RotationAngle.y.

For example, if you have a cube (TCube), you can rotate it horizontally around its own axis by
changing the RotationAngle.Y value. Up and down view will not change, but you see, if you change
the values with a TrackBar from 0 to 360, with one turnaround 4 pages, from right to left. The same
thing is done with RotationAngle.X, then the cube makes forward roles, it rotates from top to bottom.

5. Light
Of course, the light also plays a part in creating a special view. With the TLight component, you can
illuminate objects (which are connected to a TLightMaterialsource) differently. The light emits white
by default, but you can also change it using the Color property. There are also 3 different light types.
The best way to understand the effects of the different light types (LightType property) is to have
several objects in the room. In the following we have the description of the different light types and
screenshots for the respective result:

Directional
Directional light is constant from a given angle. Important is the direction in which the light appears,
which is defined by its RotationAngle and the RotationAngle of the parent object. The following figure
shows that all objects are illuminated from the same direction and distance:

Point
Spot light is comparable to the light of a light bulb. It radiates in all directions and decreases with the
distance. The value of the RotationAngle has no effect. His position, which is influenced by the
position and rotation of his parent objects, is important. The closer the object, the greater the point of
irradiation, the farther away, the more diffuse the effect:

Spot
Spotlight is dependent on position and rotation and decreases with distance. Thus, the orientation of the
light source is of the greatest importance. Objects that are outside the light cone are not irradiated, here
the right-hand cube:

With the Spot setting, the two "SpotCutOff" and "SpotExponent" properties still have an effect on the
light. Just experiment a little by modifying the default values.

Tip: Turn the light off by setting enabled to false, you'll immediately see the difference.

6. Materials
The surface of a 3D object is determined by its material. You can use the "MaterialSource" property to
assign different surface types to the 3D object:

ColorMaterialSource
The simplest "material" consists of a simple color. Color transitions are not possible.

TTextureMaterialSource
You can create any 3D object with a texture, that means cover the surface with an image. Depending on
the 3D object being used, it is important whether the texture appears "seamless", that means without a
visible transition. There are image processing programs which can calculate such things for graphics,
e.g. the FilterForge program.

Instead of using a fixed graphic, the texture can also be generated as a bitmap animation, so the
transition from one graphic to the other.

TLightMaterialSource
Here too, the surface is given a color, but it is appearance depended from the light with witch the color
is irradiated. Three different settings are relevant:

Ambient: Specifies the visible base color for surfaces. In the real world, light is reflected from many
directions onto a surface; but in the 3D scene, it would be extremely difficult to define all of this light.
Therefore, the surrounding color is activated by any light in the room (without light it has no effect.)
The surface is displayed completely uniformly in this color. With directional light - which does not
have to be directed to the surface - everything would look flat. With spotlight the colors become weaker
with distance.

Emissive: This color determines whether a surface emits its own light or is illuminated. The default is
zero (no visibility): objects do not normally light up and require light to be seen. By setting a color,
surfaces appear without light in this color.

Diffuse: This color interacts directly with light and with the incidence angle of the light. Without light
it has no effect. It is common to set Ambient and Diffuse to the same color.

Specular: This color simulates a glossy surface by reflecting incident light at a specific angle, instead of
diffusing light in many angles. With no light, Specular has no effect. It defaults to white to reflect the
light without altering its color.

Note: Extruded objects (such as TText3D) have the additional properties "MaterialShaftSource" and
"MaterialBackSource".

TMaterialBook
You can load several materials into a TMaterialBook, which is then used as a material library.

Example:

var
Form26: TForm26;
mabook: TMaterialBook;

implementation

{$R *.fmx}
procedure TForm26.Button1Click(Sender: TObject);
begin
cube1.MaterialSource := maBook.Materials[1];
Viewport3D1.Repaint;
end;

procedure TForm26.FormCreate(Sender: TObject);


begin
mabook := TMaterialbook.Create(self);
// Material 0
mabook.AddObject(ColorMaterialSource1);
// Material 1
mabook.AddObject(LightMaterialSource1);
end;
Section 2: The 3D Coordinate System
One of the difficulties with the introduction of 3D programming with FireMonkey is that you have to
get used to a different and extended coordinate system.

While in 2D programs, the zero point is at the top left of the form (as in the VCL system), the zero
point is located in a 3D form or in a Viewport3D with the projection "Camera" exactly in the middle,
both horizontally, vertically and in the depth.

Here is a screenshot, where I inserted a Viewport3D in a 2D form. The Viewport3D contains a 3D-
object (TCube). At the same time I inserted the 2D-object TRectangle, which is not in, but over the
ViewPort3D element (so parent of the TRectangle is the form). Both objects - TRectangle and TCube
have the value 0,0, or 0,0,0:

In the 2D world you specify height and width in pixels, in the 3D world (projection=camera) in units.

Optically, both objects have the same width, the rectangle has 63 pixels for Width and Height, and the
TCube object has a Width, Height, and Depth value of 4, respectively.

It is also interesting when you change the size of an object, which is located in the 2D world on the
surface or in the 3D world in space, in the width and height:

In the 2D world, the object grows to the right and down, while in the 3D world, the change in size
effects uniformly in all directions.

In the next picture, I have enlarged the object in the width, compare the starting positions and the end
positions:
As you can see, the 3D object has expanded evenly to the left and right, while the 2D object has only
expanded to the right.

The value for RotationCenter also has different meanings in 2D and 3D: For 2D objects the variables X
and Y can have a value between zero and one, the rotation center point is therefore 0.5 for X and also
0.5 for Y. In the following graph, the single "x" under the horizontal line indicates the center of
rotation:

With a value of 0,0 for "RotationCenter" the rotation would have its center point at the top left of the
component.

For 3D objects, the value is set initial to 0,0,0. You can not change this at the design time, only at
runtime (!).

In the following picture you can clearly see the center of rotation exactly in the center of the 3D object:

Here it is also worthwhile to look at the corresponding demo program, that I have developed, in order
to see the different points in the 3D space. At the design time the program looks like this:

At runtime, you can remove or add individual surfaces from the cube and rotate the entire object in the
room and view it from different perspectives.

The program is impressive, because it was created purely with visual live bindings, there is no single
line program code.

Note: If you run the program in a virtual machine (here: Linux as host, Windows 10 as Client, with
"VirtualBox"), the text3D elements are not displayed correctly. This is not a program error but it is a
result of running in the virtual machine.

Here you can download the program:

http://www.devpage.de/download/fmbook4/StrokeCube.zip
Section 3: 3D-Application "Atomic Model"
When you create a new "multi device application", you have only one option that creates a "real" 3D
application:

Only the "3D Application" selection creates an application with the form "TForm3D" (instead of
TForm).

Although you have basically two possibilities to accommodate 3D elements in your program:

· The real 3D application


Here you can directly insert the desired 3D objects (for example, TCube, TLight, etc.) into the
form. If you need 2D-controls in the program interface (e.g. buttons), use a "TLayer3D", preferably
with the projection "Screen", then you can also use the Align property of the "TLayer3D".

· The normal application with 3D elements


In a normal application (formerly called HD application), you can add 3D objects to the program
through a TViewPort3D component.

However, in the two approaches, we have significant differences in speed. If you use complex
animations with movements of illuminated objects in your program, you can quickly reach performance
limits, i.e. jerks in the display, via the HD application with 3D elements.

You should therefore first consider what your program should do. In the "Solar-Model" demonstration
program discussed here in the book, where several planets are rotating in the solar system, the speed in
the real 3D application is OK, the HD application with 3D elements would lead to jerks.

Demoprogram "Atomic Model"

Here, we will first create an animated water molecule using 3D objects and LiveBindings only. The
result looks like this and can be rotated on different axes:
Now we create the program step by step:

1. Create a new 3D application

2. Insert a TLayer3D element, select "Screen" under the property "Projection" and set the width to
150 pixels and "Align" to "Right".

3. Insert a TDummy object.

4. In t he TDu mmy obj ect , ins er t a TSphe r e ( Spher e1). Set the val ues ​f or Posit i on. x to- 3. 5and
Position.y to 3.

yobj ect and se t the val ues ​f or Posit ion. xor


5. Addanother TS pher e objec t ( Sphere2) to t he TDumm
Position.Y to 3.5 and 3 respectively.

6. Paste the last TSphere object (Sphere3) and leave it at the insertion position (0,0,0).

7. For all TSphere obj ect s, set the Widt h, Hei ght , andDepthvalues ​t o "3" toget a large r round ball.

yobj ect ands et t he val ues ​for pos it ion.X t o-1. 5


8. Nowi nser t a TCy l i nder object int o the TDumm
and for position.Y to 1.5. Set Depth to 0.2 for Width and 0.2 for Height, and 6 for Height. Finally,
set RotationAngle to 52 (the two hydrogen atoms are at an angle of about 104 degrees to each other,
with 52 we have the left wing).

9. Now insert another TCylinder object into the TDummy and set the values for Position.x and
Position.y to 1.5. Set the values for Width, Height and Depth as in No.8, only the value for
RotationAngle you set to 308.

10. Now add 3 TLightMaterialSource, for the values Ambient you set "Red", "Darkblue" and
"Gray", for the "Diffuse" value "WhiteSmoke".

11. In Sphere3 for "MaterialSource" you assign the component LightMaterialsource1 (Red), for
Sphere1 and 2 you assign LightMaterialSource2 (Darkblue) to "MaterialSource".

12. In order for the objects to be illuminated, we add a TLight object and position it in the upper
left corner.

If you now run the program, you will see the Atom model, but still immobile.

13. We bring the movement into the project with 4 float animations.

Set the FloatAnmiation1 values as shown in the screenshots below:

For FloatAnimation2, set it just the same, but select "RotationAngle.Y" for PropertyName, and for
Enabled, select "True".

For FloatAnimation3, set the values as well, but for "PropertyName", "RotationAngle.Z" and set
Enabled to False.

For FloatAnimation4, set the values as shown below:


14. Now add 4 TSwitches and 4 TLabels into the TLayer3D and name the text properties, as
shown in the first screenshot above.

15. Now click on the TDummy object with the right mouse button and select the "Visually
Binding" command.

The LiveBindings Designer opens, where you create the links as follows:

Note: If some objects should not be displayed here, click on the object in the structure view and select
the "Visually Binding ..." command.

You can display the individual properties here in the boxes by clicking on the 3 small dots and selecting
the required properties in the dialog that appears.

You then click on a value (for example, IsChecked) with the left mouse button and, while holding
down the mouse, drag the connection to "Enabled".

This completes the program and you can run it. Note that it does not need a single line source code.

How does it work now?

The LightMaterialSources give the 3D objects a surface, which reflects the light according to the effect
of light.

The values "StartValue" and "StopValue" in the FloatAnimations ensure that the TDummy object
rotates around its own axis with all the contained objects (X, or Y axis, with values from 0 to 360) ,
forward or rear (Z-axis, with values from 30 to 0).

The respective animations are activated by the switches (TSwitch), depending on whether enabled =
True or False.

Another tip: If you set the Spheres values to "SubdivisionsAxes" or "SubdivisionsHeight" to 45, the
spheres have a smoother effect on the curves, which is particularly noticeable when you enlarge the
shape.

Here you can download the demo:

http://www.devpage.de/download/fmbook4/AtomicModell.zip
Section 4: 3D-Application "Solar Model"
The next 3D application is a solar model, where the ways of our planets around the sun is simulated.
Also, this is an impressive example of what you can do with FireMonkey for effects without creating a
single line of code.

Here is a screenshot of the application:

At runtime, the individual planets rotate around the sun at different speeds, in relation to their actual
orbital period around the sun.

I will not give you a step-by-step guide, but explain some basic things. On YouTube there is also (in
the near) a small video, that one can look at it additionally.

The structural overview of the program provides an overview of the design:


The sun does not move and is in the center. It consists of the 3D object "TSphere", as well as the
planets. Directly in the middle of the sun is the Light1 (TLight) placed, thus the light source. The light
type is "point", therefore the light radiates uniformly in all directions.

The object "DummySpace" is a TDummy object, which contains the other 3D objects. This
DummySpace object can then also be changed with the two sliders (in size and in position).

But the most important are the respective planets. They each consist of a dummy object that contains a
TSphere, a TFloatAnimation, and a Path3D object:

The Path3D3 object symbolizes the path data (the way which the object has to take), here the
DummyEarth object (visible is the "sEarth" object).

The FloatAnimation is linked to the value "RotationAngle.Y" of the DummyEarth object via the
property "PropertyName". For StartValue, the value is equal to zero and for StopValue is equal to 360.
The object moves in the circle. The "duration" stands at 36.5 seconds, which should correspond to the
365 days of a year. Venus needs only 225 days to circumnavigate the sun, so the FloatAnimation of
Venus has got the value 22.5. The other values correspond to the respective relative orbital times. For
interested parties here the exact values:

Mercury: 88 Tage
Venus: 225 Tage
Earth: 365 Tage
Mars: 687 Tage
Jupiter 4329 Tage
Saturn: 10751 Tage
Uranus: 30664 Tage
Neptun: 60148 Tage

The planets Uranus and Neptune I have not included in the animation, because of the long round times
one would have to wait some time until you see the animation.

The size of the planets and the distances to each other and to the sun are not true to scale, since
otherwise things could not be so clearly displayed.

I created the orbits of the planets (circles) with a Tpath3D object, placed centrally in the middle and
each took a different width and depth to get different dimensions and distances.

The data for the circle I created with my image processing program PixPower, which can generate SVG
data from the painted objects:

The SVG data generated in this way can then be copied to the clipboard in PixPower and inserted into
the Tpath3D object using the "Path, PathData" property.

Here you can download the demo program:


http://www.devpage.de/download/fmbook4/SolarModell.zip
Chapter 8: Animations, Transitions and Effects
FloatAnimations have already been used for various demos in the book ("Atomic Model" and "Solar
Model").

Besides the FloatAnimation, the TColorAnimation is also interesting. This allows you to change a color
to a different color in a desired time period.

Here is a small demo, which is supposed to simulate a laboratory experiment, where a blue liquid
changes into a yellow liquid in a test tube.

This demonstration project shows at the same time, with how little effort you can achieve interesting
results. This is how the project looks in Design mode:

In the structure overview, you can see how the objects have been constructed:
The test tubes consist of a simple combination of rectangles and ellipses. The colored liquid consists of
2 ellipses and a rectangle, the rectangle has for opacity the value of 0.9, so that it is a little bit
transparent and you can almost see the back edge of the ground. With two TShadowEffect components
inserted into the two lower ellipses, the test tube throws a slight shadow to the ground.

These are very simple but also impressive constructions. With a little imagination and creativity you
have almost endless possibilities.

The required program code for the program is quite short:

procedure TForm11.bnStartAnimationClick(Sender: TObject);


begin
ColorAnimation1.Enabled := false;
ColorAnimation1.Enabled := true;
end;

procedure TForm11.ColorAnimation1Process(Sender: TObject);


begin
EllipseTop.Fill.Color := Rectangle2.Fill.Color;
EllipseBottom.Fill.Color := Rectangle2.Fill.Color;
end;

With ColorAnimation.enabled = True the animation will be started (reset with False). Since the
ColorAnimation can only be bound to one object, we use the "ColorAnimation1Process" event to adjust
the lower and upper ellipse of the color liquid with the Rectangle element.

At runtime and after clicking on start, after 2 seconds ("duration" of the ColorAnimation has this value)
the result looks like in the picture below, whereby, of course, a smooth transition is to be seen:
A similar animation as the TColorAnimation represents the TGradientAnimation, except that we have
not a whole area simultaneously changing its color, but a change of the color gradient. We do not use
the property "Fill.Color" but "Fill.Gradient". So if you are using a TRectangle, for example and you
want to achieve a pulsating color, you must first set for "Fill", the property "Kind" to "Gradient",
because the default is "Color".

There are other animations:


To go into all of them in detail would go beyond the scope of the book.

Therefore, I will give only a brief description of the animation types that have not yet been discussed,
and hints where you can find more information:

TRectAnimation: This is used to change animated the edges of an object (for example, TRectangle),
which is per Align = client in another object. More info:

http://docwiki.embarcadero.com/Libraries/Tokyo/de/FMX.Ani.TRectAnimation

TBitmapAnimation: When you insert it into a TImage, you can display a transition from one image to
another. As PropertyName, use "Bitmap". More info:

http://docwiki.embarcadero.com/Libraries/Tokyo/de/FMX.Ani.TBitmapAnimation

TBitmapListAnimation: Animate a single bitmap by dividing it into frames and displaying the frames
successively, like a timed clip. More info:

http://docwiki.embarcadero.com/Libraries/Tokyo/de/FMX.Ani.TBitmapListAnimation

TColorkeyAnimation: Transitions through a list of colors. Similar to TColorAnimation, but one can
create different "ColorKeys" (property "Keys"), which should be used for the transitions.

TPathAnimation: Changes the 2D position of an object according to a TPath with optional rotation
along the path.
Chapter 9: Sending and receiving messages with
the TMessageManager
From Windows, you know the API functions "SendMessage" and "PostMessage", which can be used to
send messages program wide or system wide.

You could use these functions also on Windows with FireMonkey, but under MAC OS or on other
platforms, they would not work.

Therefore Delphi brings with the RTL its own message solution, the TMessageManager from the unit
"System.Messaging". This unit must always be included if the MessageManager is to be used.

At first you must decide to use your own MessageManager (which you derive from TMessageManager)
or the default MessageManager ("TMessageManager.DefaultManager"). The latter is always available,
so it does not have to be generated separately or released to the end of the program.

However, you can save a little bit of typewriting by declaring a global variable "MessageManager" of
the type "TMessageManager" to which you assign the DefaultManager at program start:

var
MessageManager: TMessageManager;

begin
MessageManager := TMessageManager.DefaultManager;

In many cases, it will be best to specify the types of messages you want to be notified directly from the
program start.

This is like on the Internet with the newsletters, you subscribe or unsubscribe.

In the following are two demos to show the actual usage.


Section 1: Simple Messaging-Demo
As a finished program, the demo looks at runtime as follows:

Here you can see that the text "Eine Textinfo" has been sent to the program and there is an instance (a
"listener") that responds to it and shows the received text.

The source code for the program is very short, since both the standard MessageManager and a standard
message type were used here:

procedure TF_Main.bnSendClick(Sender: TObject);


begin
With TMessageManager.DefaultManager do begin
SendMessage(Sender, TMessage
<UnicodeString>.Create(Edit1.Text));
end;
end;

procedure TF_Main.FormCreate(Sender: TObject);


begin
With TMessageManager.DefaultManager do begin

SubscribeToMessage(TMessage<UnicodeString>,
procedure (const Sender: TObject; const M:
TMessage)
begin
ShowMessage ('Message of kind "TMessage
<UnicodeString>" received:' +
#13#13+ (M as
TMessage<UnicodeString>).Value);
end);
end;
end;
Let us first look at the function "SubscribeToMessage". This is from the Unit "System.Messaging" and
looks like this in the declaration:
function TMessageManager.SubscribeToMessage(
const AMessageClass: TClass;
const AListenerMethod: TMessageListenerMethod): Integer;

A TMessage class is expected as the first parameter. This can be a class you have created, and is
derived from TMessage (I will go into this later in the second example). Or it can be an existing "class",
in this case "TMessage <UnicodeString>.

In the second parameter, a "Listener-Method" is expected. This is of type "TMessageListenerMethod"


and is simply a procedure of object:

TMessageListenerMethod = procedure (const Sender: TObject; const M: TMessage) of object;

The second parameter therefore looks like this:

procedure (const Sender: TObject; const M: TMessage)


begin
// Do what you want here with
// (M as TMessage<UnicodeString>).Value);
end);

The MessageManager will always call this listener method when it receives this type of message.

Within this listener method, you can then do what you want with the information. You also have full
access to your form and its components within this method. So you can paste this text into a list box or
whatever else you want to do with it.

Sending the text message is simple and always works according to the pattern:

MessageManager.SendMessage(Sender:TObject, Message: TMessage);

That means like this::


SendMessage(Sender, TMessage <UnicodeString>.Create(Edit1.Text));

In this case, "Sender" is simply the button that was clicked, and a Unicode string with the contents of
Edit1.Text is created as a TMessage object.

The thing is more interesting if we pass not only a string, but a component, e.g. a Listbox, a StringGrid
or whatever else, so that you have than full access to their properties and the recipient of the message
can evaluate this state and can react to specific states if necessary. We will look at an extended
possibility in the second example.
Section 2: Enhanced Messaging-Demo
In this example, we use our own MessageManager and our own TMessage classes.

In the finished program, you can send a text that is received and processed by different listeners (each
added text to a listbox). In addition, it can also be sent as information that a selection has changed in a
list box (ItemIndex changed).

The listeners are set up (subscribe) at runtime by the user and, if necessary, they can be uncoupled
(unsubscribe) later from the MessageManager.

At runtime, the program looks like this (after pressing the upper and lower Subscribe button, text was
sent twice and different entries were clicked in the upper listbox):

First, let's see what happens when the "Subscribe 1 Msg" button is pressed:

procedure TF_Main.bnSubscribeToTxtMsgClick(Sender: TObject);


var
TextMessageListener : TMessageListener;
begin
TextMessageListener :=
procedure(const Sender: TObject; const M:
TMessage)
begin
ListBox1.Items.Add('TTextMessage Received.
Text = ' + (M as TTextMessage).Text);
end;

fSubscriberID1 :=
MessageManager.SubscribeToMessage(TTextMessage,
TextMessageListener);
end;

Here, a listener method is first declared, which can then be used as the second parameter in the
SubscribeToMessage function. I have separated this method for the sake of better readability, but you
can of course do everything in one step, as shown below, when further listener methods are declared
and passed to the MessageManager.

But first a few words about the used TMessage object. It is of type TTextMessage, which we declared
as a class:

TTextMessage = class(TMessage)
private
fText: string;
public
property Text : string read fText;
constructor Create(const Text : string); virtual;
end;

The variable "fSubscriberID1" is declared as an integer in the private section and is required later e.g.
to use the unsubscribe function (with this ID we can then identify us).

Here is the setup of two other listener methods:

procedure TF_Main.bnSubscribeMsg2Click(Sender: TObject);


begin
fSubscriberID2 := MessageManager.SubscribeToMessage
(TTextMessage,
procedure(const Sender : TObject; const M :
TMessage)
begin
ListBox2.Items.Add('TTextMessage Received.
Text = ' + (M as TTextMessage).Text);
end);

fSubscriberID3 := MessageManager.SubscribeToMessage
(TListboxIndexChangedMessage,
procedure(const Sender : TObject; const M :
TMessage)
begin
ListBox2.Items.Add
('TListboxIndexChangedMessage Received.
Item = ' +IntToStr((M as
TListboxIndexChangedMessage).
Listbox.Selected.Index));
end);
end;
Again a listener method is created that responds to the sent text.

Additionally, a listener method is created that responds when the value of the ItemIndex changes to
Listbox1. The message type was created by us as follows:

TListboxIndexChangedMessage = class(TMessage)
private
fListbox: TListbox;
public
property Listbox : TListbox read fListbox;
constructor Create(const Listbox : TListbox);
virtual;
end;

Where is the source code, where we can see how the relevant messages are sent?

First of all the sending of the text message:

procedure TF_Main.bnSendTextClick(Sender: TObject);


begin
MessageManager.SendMessage(self, TTextMessage.Create(EdTxtToSend.Text));
end;

And here the sending of the information that the ItemIndex of Listbox1 has changed:

procedure TF_Main.ListBox1Change(Sender: TObject);


begin
if cbPublishSelChange.IsChecked then
MessageManager.SendMessage(self,
TListboxIndexChangedMessage.Create(ListBox1));
end;

The "ListBox1" is specified in the TListboxIndexChangedMessage.Create as parameter. Thus, the


recipient of this information obtains full access to the ListBox1 and can retrieve all required
information.

If we want to remove the listener methods from the notification later, we use the Unsubscribe function
together with the previously noted ID:

procedure TF_Main.bnUnsubcribeFromTxtMsgClick(Sender: TObject);


begin
MessageManager.Unsubscribe(TTextMessage, fSubscriberID1);
end;

procedure TF_Main.bnUnsubscribeMsg2Click(Sender: TObject);


begin
MessageManager.Unsubscribe(TTextMessage,
fSubscriberID2);

MessageManager.Unsubscribe(
TListboxIndexChangedMessage, fSubscriberID3);
end;

This gives you a very flexible way to send messages and this works on all platforms supported by
Delphi.

For example, in your program has several open forms that need to respond to certain changes in the
main form, then send a message and all forms that have previously been subscribed to receive this
message receive this info and can respond accordingly.
The potential for using this technique in applications are manifold. Also, in the context of reducing the
necessary references of units to other units, messages can be a help.

Let's take the case that the results of different database queries should be displayed in several different
non-modal windows.

With the closing of the database we wish that all the corresponding information windows should also
be closed.

To do this, simply send a text message with the content "CloseQueryDisplayWindow" and all windows
that have subscribed for receiving text messages can respond to this message if it is relevant to them.

You could make the following entry in the FormCreate event of the DisplayForm:

with TMessageManager.DefaultManager do begin


SubscribeToMessage(TMessage<UnicodeString>,
procedure(const Sender: TObject; const M:
TMessage)
begin
if (M as TMessage<UnicodeString>).Value =
'CloseQueryDisplayWindow' then
self.Close;
end);
end;

And if you then send the following in a suitable place in your program:

with TMessageManager.DefaultManager do begin


SendMessage(sender, TMessage<UnicodeString>.Create
('CloseQueryDisplayWindow'));
end;

all instances of the generated display forms will be closed. This approach is quite practical in so far,
that you do not have to manage the generated display-forms in a list and it can also be forms of
different types, which are subscribed for the receipt of this message.
Chapter 5: Useful third-party components for
FireMonkey
1. TMS-Components

The TMS components have already been mentioned several times in this book. The TTMSFMXGrid
provides a number of useful features, e.g., in the combination with the possibility to export the content
of the grid as Excel, RTF or PDF file (TTMSFMXGridExcelIO, TTMSFMXGridRTFIO,
TTMSFMXGridPDFIO).

The TTMSFMXRichEditor which is a worthy replacement for the VCL RichEdit component was also
briefly mentioned. With the TTMSFMXRichEditorFormatToolbar component and the
TTMSFMXRichEditorEditToolbar two ready to use toolbar components are delivered, which you
can combine with the editor.

The TMS-Rich Editor can save, if necessary, his text as a RTF or HTML file.

For the Rich Editor, I have placed a short video on YouTube. Just have a look here:

https://www.youtube.com/watch?v=_BjlRX_CjX4

Good news: From the version 2.9.0.0 of TMS Pack for FireMonkey on, one can even attach a complete
spell check to the Rich Editor. The dictionaries are provided, inter alia, in German, English, French,
Spanish and Italian.

Here is the link to the TMS Pack for FireMonkey:

http://www.tmssoftware.com/site/tmsfmxpack.asp

Finally, I will mention here the TMS cloud Pack for FireMonkey: With that you will get access under
Windows, MAC, iOS and Android to the various cloud services, which are offered for those platforms.
For instance, DropBox, Google Drive, Windows OneDrive BOX and services on iOS.

Here is the link to the TMS Cloud Pack for FireMonkey:

http://www.tmssoftware.com/site/tmsfmxcloudpack.asp

Even when you want to or must use certain native components for the MAC (or iOS), TMS can help
you with the mCL components (or iCL on iOS).

In a cross compile project, however, you have a bit more work in using of this components, because, of
course, the native MAC OS components will not work for the Windows Desktop. But, sometimes, it's
the only way to realize a project for the MAC at all.
For example, to display PDF files in a separate form in my invoice program on the MAC, I use the
TMSFMXNativeNSView component from TMS (while I use a with a Hydra-module integrated
component of Gnostice under Windows).

The direct generation of PDF invoice file I'll do on the MAC with the TMSFMXNativeMacPDFLib
component.

Here is the link to the TMS mCL components:

http://www.tmssoftware.com/site/tmsmcl.asp

However, TMS has more FireMonkey components to offer. A complete overview of the TMS
Components for FireMonkey can be found here:

http://www.tmssoftware.com/site/products.asp?t=fm

2. Report-generator: FastReport FMX

With Fast Reports FMX, you can create reports like you used to do it under the Windows VCL. In
Delphi 10, although a standard version of Fast Reports FMX is included, it contains a no re-
distributable report- designer. This is obtained when one acquires the commercial version of Fast
Reports.

Here is the FastReport designer at design time:

These components provide a large amount of options for creating reports (incl. PDF output).
Here is the link: https://www.fast-report.com/en/product/fast-report-fmx

3. RemObjects-Application Framework (Hydra)

Hydra are not components, but is a framework that allows you to mix VCL and FMX components in
one form. For example, you can directly integrate FMX components in a VCL Forms (or vice versa).

This is surely a way that should not be the focus of your work with FireMonkey. But it offers additional
ways and opportunities to pick all possibilities from the VCL and FMX worlds out.

Here is the direct link: http://www.hydra4.com/hydra/default.aspx

Here, too, I deposited a short video on YouTube. Take a view as you like:

https://www.youtube.com/watch?v=0K8mEzDmlaM

4. Other components

Other component manufacturers have announced to support FireMonkey (e.g., Gnostice) or to wait a
bit for a decision (DevExpress, ImageEn, TRichView). But there are also other developers and FMX
supporters, who have developed a set of components and provide you them predominantly free of
charge.

Here I recommend you to take a look at http://www.fmxexpress.com and to examine whether there is
something you need here (where most components rather refer to iOS or Android development).

You can also click at the Delphi start page on the "Get Add-ons from GetIt" and here, search for FMX:
Chapter 11: How to - tips & tricks for FMX
Here you will find a number of different questions and answers. As a tribute to my beloved "cookbook"
- the Delphi series by Walter Doberenz and Thomas Kowaslki - I follow the "recipes" (in German it
means a kind of written description to handle something) from these books, using the R-numbering.

R1... Get the display resolution?

For this purpose, we used a platform service.

Example:
procedure Tfrm_Main.FormCreate(Sender: TObject);
var
ScreenSvc: IFMXScreenService;
Size: TPointF;
begin
if TPlatformServices.Current.SupportsPlatformService
(IFMXScreenService, IInterface(ScreenSvc))
then begin
Size := ScreenSvc.GetScreenSize;
end;
end;

Size.x give us the width and the height is in Size.y.

In the meantime, this is much easier, since Delphi 10 Seattle has multi-display support, which can be
accessed via the global screen variable (TScreen.Screen).

Screen.DisplayCount gives you the number of monitors which you can then query via ScreenDisplays
[x] for individual properties (width, height, WorkAreaRect, etc.).

Here you can find more details:

http://docwiki.embarcadero.com/RADStudio/Seattle/en/Multiple_Display_Support

R2 ... Check whether the Escape, Ctrl or Alt key is pressed


Sometimes you need an offer to interrupt or cancel an ongoing process. In the VCL, under Windows, it
works like this:
if (Getkeystate (VK_CONTROL) < 0) then begin // Is the Shift-key pressed?
// yes -->
exit;
end;

And you do it so under FireMonkey:


{$IFDEF MACOS}
Uses
MacApi.AppKit, MacApi.Foundation, Macapi.CocoaTypes;
{$ENDIF}

function IsControlKeyPressed: Boolean;


begin
Result := NSControlKeyMask and
TNSEvent.OCClass.modifierFlags = NSControlKeyMask;
end;

In a Cross-platform way, you handle it like this at best:


function IsControlKeyPressed: Boolean;
begin
{$IFDEF MSWINDOWS}
Result := GetKeyState(VK_CONTROL) < 0;
{$ELSE}
Result := NSControlKeyMask and
TNSEvent.OCClass.modifierFlags = NSControlKeyMask;
{$ENDIF}
end;

function IsShiftKeyPressed: Boolean;


begin
{$IFDEF MSWINDOWS}
Result := GetKeyState(VK_SHIFT) < 0;
{$ELSE}
Result := NSShiftKeyMask and
TNSEvent.OCClass.modifierFlags = NSShiftKeyMask;
{$ENDIF}
end;

function IsESCKeyPressed: Boolean;


begin
{$IFDEF MSWINDOWS}
Result := GetKeyState(VK_Escape) < 0;
{$ELSE}
// Result := NSEscapeKeyMask and
// TNSEvent.OCClass.modifierFlags =
// NSEscapeKeyMask; (so it does not work)
{$ENDIF}
end;

What I still don't know is how to query the ESC key on the MAC. Please share it with me if anyone
knows the solution.

R3 ... Use folder names under Windows and MAC properly


Under Windows, you use the "\" character to specify directories and files in a file path, e.g.:

"D:\Data\Forms\File.doc".

Under MAC, it is the "/" character which is to be used. Example:


"/Users/harrystahl/Documents/Datei.doc".

In order to make sure, that you always use the right delimiter, use the defined constant "PathDelim".
Depending on whether you compile on Windows or Mac, the correct version is used. The excerpt from
the unit "System.SysUtils" below shows, that also the constants "DriveDelim" and "PathSep" are
available:
const
PathDelim = {$IFDEF MSWINDOWS} '\'; {$ELSE} '/'; {$ENDIF}
DriveDelim = {$IFDEF MSWINDOWS} ':'; {$ELSE} ''; {$ENDIF}
PathSep = {$IFDEF MSWINDOWS} ';'; {$ELSE} ':'; {$ENDIF}

Thus, the root directory always starts with "/" under the MAC. If you want to find associated drives on
the MAC, you must query the entries of the first-level directory under "/Volumes".

The following source code reads in the existing drives ("volumes") on my MAC (under Windows, it
doesn't work like this, of course):
// Units System.IOUtils und System.Types will be needed

procedure TForm9.FormCreate(Sender: TObject);


var
sdaDrives: TStringDynArray;
sDrive: string;
begin
sdaDrives := TDirectory.GetDirectories('/Volumes');
for sDrive in sdaDrives do begin
Listbox1.Items.Add(sDrive);
end;
end;

Here are the results:

Note: with "Add (copy (sDrive, 10, 255))", you would get only the drive name.

On Linux, the drives are usually mounted under "media" and there under the current user account.
Here, the following query would lead to a result:

procedure TForm1.Button1Click(Sender: TObject);


var
sdaDrives: TStringDynArray;
sDrive: string;
begin
sdaDrives := TDirectory.GetDirectories('/media/' +
ExtractFileName (GetHomePath));

for sDrive in sdaDrives do begin


Listbox1.Items.Add(sDrive);
end;
end;

R4 ... Use search-mask for "all files" in Windows and MAC OS X properly
If you want to view "all files" in file searches under Windows you use the mask "*.*". Under MAC, it
seems also to work well. However, the right mask is "*" . The mask "*.*" would not display files, that
are without a file extension.

I, therefore, use the following solution with a constant, which always has the right content in the used
context:
{$IFDEF MSWINDOWS}
const
AllMask = '*.*';
{$ENDIF}

{$IFDEF MACOS}
const
AllMask = '*';
{$ENDIF}

R5 ... Avoid looping symlink folders (Alias)


On the MAC, you can create an "alias" for folders and files. An alias folder is only a reference to the
folder that is on another location on the hard drive. If you make a recursive search for files in a folder,
including subfolders, you can possibly get an endless loop situation. If an alias in a folder "A" points to
a folder "C" and a reference back to the folder "A" is found in this folder, the search will continue
endlessly.

Therefore, in a recursive file search the attribute "faSymlink" has to be filtered out.

Here is an example of searching for all the ".pas" files in the folder "D:\Delphi" and storing the result in
a TStringList :

// Finds all files that match the specified criteria


procedure FindThisFiles (pa: String; subDirs: Boolean; sl: TSTringList);
var
Search : TSearchRec;
begin
if FindFirst (Pa, faAnyFile-faDirectory, Search) = 0 then repeat
sl.add (ExtractFilepath (pa) + Search.name);
until FindNext (Search) <> 0;

FindClose (Search);

if SubDirs then begin


if FindFirst (ExtractFilePath (pa) + '*', faDirectory-
faSymLink, Search)=0
then begin
Repeat
if ((search.attr and faDirectory)=faDirectory)
and (search.name[1]<>'.')
then begin
FindThisFiles (ExtractFilePath (pa) + Search.Name+
PathDelim + ExtractFileName (pa), SubDirs, sl);
end;
until FindNext (Search) <> 0;
end;
FindClose (Search);
end;
end;

procedure TForm11.Button1Click(Sender: TObject);


var
sl: TSTringList;
begin
sl := TStringList.Create;
FindThisfiles ('D:\Delphi\*.pas', true, sl);
end;

R6 ... In which situations file symlinks functions play a role otherwise

If you want to get the attributes from a file you use "TFile.GetAttributes"

Attributes: = TFile.GetAttributes ('MyFileName');

But it may be that the file is an alias. By default, you will receive not the attributes of the alias file, but
the attributes of the file to which the alias file points (target file).

You can use "TFile.GetAttributes", therefore, by adding another parameter (which, if not specified, is
just true) in order to avoid the pointing to the target file:

Attributes: = TFile.GetAttributes ('MyFileName', false);

The function is declared in "System.Ioutils" as follows:

class function GetAttributes (const Path: string; FollowLink: Boolean = true): TFileAttributes; inline; static;

The FollowLink parameter also exist in a number of other file functions such "TFile.exists",
"TDirectory.Exists", etc.

When using file functions, you should always let show the parameters that you can use there because
there are possibly even more than you would expect.

Unchecked symlink properties can lead to unwanted results. Let's say you want to copy a file. If it is an
alias, you do not copy the alias file with perhaps only 30 bytes, but the target file that is possibly
several gigabytes in size. This can ever lead to surprises when you, for example, perform a backup of
files in a directory. You should, therefore, always check files and test whether it is an alias file, and
then react to it as needed.

Normally it should be sufficient to examine the attribute "faSymlink" in the file attributes. However,
I've found, under the MAC, that some files here (for whatever reason) have the attribute although there
are obviously no alias files.

Just to be on the safe side, you could use the following example to check this:
Function IsASymlinkfile (Filename: string): Boolean;
Var
SymlinkRec: TSymLinkRec;
attr: TFileAttributes;
begin
Result := False;
TFile.GetAttributes (Filename, false);

if TFileAttribute.faSymLink in Attr then begin


TFile.GetSymLinkTarget(Filename, SymlinkRec);
if SymlinkRec.TargetName <> '' then begin
Result := True;
end;
end;
end;

R7 ... Determine the control under the mouse position


If you want to find out which control is located at the current mouse position, you can do this with the
function "ObjectAtPoint".
Here is an example of how to get the class name of the object, over which the mouse pointer is:
procedure TForm9.Timer1Timer(Sender: TObject);
var
obj: IControl;
begin
obj := ObjectAtPoint (Screen.MousePos);

if obj <> NIL then begin


Label2.Text := TControl (obj).ClassName;
end;
end;

R8 ... Find out on which MAC OS X operating system the program is running

The record "TOSVersion" is available in the unit "System.SysUtils", which allows you to query the
operating system both under Windows and MAC OS X.

With "TOSVersion.ToString", you get several relevant parameters summarized together. Under
Windows 7, the example looks as follows:

Under MAC OS X, then:

Under Linux so:


If you look at the structure of the record once, you will see what features and data are available at all:

TOSVersion = record
public type
TArchitecture = (arIntelX86, arIntelX64);
TPlatform = (pfWindows, pfMacOS);
private
class var FArchitecture: TArchitecture;
class var FBuild: Integer;
class var FMajor: Integer;
class var FMinor: Integer;
class var FName: string;
class var FPlatform: TPlatform;
class var FServicePackMajor: Integer;
class var FServicePackMinor: Integer;
class constructor Create;
public
class function Check(AMajor: Integer): Boolean; overload; static; inline;
class function Check(AMajor, AMinor: Integer): Boolean; overload; static; inline;
class function Check(AMajor, AMinor, AServicePackMajor: Integer): Boolean; overload; static; inline;
class function ToString: string; static;
class property Architecture: TArchitecture read FArchitecture;
class property Build: Integer read FBuild;
class property Major: Integer read FMajor;
class property Minor: Integer read FMinor;
class property Name: string read FName;
class property Platform: TPlatform read FPlatform;
class property ServicePackMajor: Integer read FServicePackMajor;
class property ServicePackMinor: Integer read FServicePackMinor;
end;

R9 … Get the current user name in Mac OS X / Linux / Windows

Under MAC OS and Linux simply take the second entry from the directory "GetHomepath" command,
under Windows the third:

function GetComputerUserName: string;


begin
{$IF DEFINED (MACOS) or DEFINED (Linux)}
result := ExtractFileName (GetHomePath);
{$ENDIF}

{$IFDEF MSWindows}
result := ExtractFileName (ExtractFileDir
(ExtractFileDir (GetHomepath)))
{$ENDIF}
end;

R10 … Send files as an attachment of an e-mail with the system mail program

A frequently used function is sending files that was created by your own program. The simplest
solution is to transfer the files to the mail-program used by the operating system.

Under Windows, you can use the Microsoft MAPI. I assume that you probably already know how to do
that in Windows, using the MAPI interface. If not, you can download my unit "uSendMail.pas" from
my devpage website, which contains the "Send Files" function used here:
http://www.devpage.de/download/fmbook/uSendMail.pas

The cross-platform solution for Windows and MAC is the following (using an example of a form with
a listbox, in which you have selected one or more files, and then click on the button "send mail"):

Uses

{$IFDEF MACOS}
POSIX.Stdlib,
{$ENDIF}

{$IFDEF MACOS}
uSendMail.pas,
{$ENDIF}

procedure Tf_Main.SendMailClick(Sender: TObject);


var
L: Integer;
app, s: String;
sl: TSTringList;
begin
sl := TStringList.create;

{$IFDEF MSWINDOWS}
for L := 0 to lbBilder.count - 1 do begin
if lbBilder.listitems[L].isSelected then begin
sl.add (lbBilder.Items[L]);
end;
end;

// E-mail this here with the MAPI files


SendFiles (sl);
{$ENDIF}

{$IFDEF MACOS}
for L := 0 to lbBilder.count - 1 do begin
if lbBilder.listitems[L].isSelected then begin
sl.add (' "' + lbBilder.Items[L] + '"');
end;
end;

s := StringReplace (Trim (sl.text), #10, '',


[rfReplaceAll]);

app := '/Applications/Mail.app';

_system(PAnsiChar ('open -a' + AnsiString (app + ' ' +


s)));
{$ENDIF}

sl.free;
end;

In the MAC OS X solution, a string is made of the files to be sent. It holds the file names in quotes and
separates them by a space. The Apple Mail program is used as a mail program here.

The parameters 'open -a' simply mean that the function "_system" should start an application, to which,
separated by a space, you pass over the files to be sent.

On Linux, it is enough to pass the data as parameter, first the program-name and than the filenames,
separated by a space.

Note: If you want to give the user the option to use another mail program, you could offer the
appropriate options in a own setting dialog. He could, then, simply select the mail program which he
used from the Applications folder.

Instead of

app := '/Applications/Mail.app';

you would use

app := '/Applications/UserMailprog.app';

where upon "UserMailprog" is the mail program selected by the user. One requirement is, of course,
that this e-mail program must also support the reading of the passed parameters for the file names.

R11 … Provide the user with help-files under Win & MAC & Linux

Under Windows, you can use the Microsoft HTML Help Workshop to create a ".chm" help-file or
another professional program which generates these files.

The Help Workshop use as source HTML files. Other professional programs work either with HTML
files or can issue, at least, the help text in such a format.

And this is the solution: use HTML files under both Windows and MAC OSX. That will allow you to
call and view the help from your program.
The files are then displayed in the browser.

In Delphi, you can include the required HTML files in your application bundle by using the
deployment feature and transfer the files to the MAC OS folder.

Here you can see an example of the "index-en.htm", which contains the help text for the English
language version and the "index.htm" for the German language version. For more extensive programs
you can also create multiple HTML help texts and include them in your program.

In your program, call the HTML files with the "pf_ShowHelp" function ("AktLang" is here a global
variable, managed by the program. It keeps the information about the language used currently):

procedure Tf_Main.mnu_ContentClick(Sender: TObject);


var
fn: string;
begin
if AktLang = 'DE' then begin
fn := IncludeTrailingPathDelimiter (AppPath) +
'index.htm';
end;

if AktLang = 'EN' then begin


fn := IncludeTrailingPathDelimiter (AppPath) +
'index-en.htm';
end;

pf_ShowHelp (fn);
end;

Whereby the procedure "pf_ShowHelp" is defined as follows:

Uses

{$IFDEF MSWindows}
uses
Windows,
ShellApi,
Classes;
{$ENDIF}

{$IFDEF MACOS}
Uses
System.Classes,
POSIX.Stdlib;
{$ENDIF}

...

procedure pf_ShowHelp (HTMLFile: string);


begin
{$IFDEF MSWINDOWS}
ShellExecute (0,'open',Pchar (HTMLFile),nil,nil,0);
{$ELSE}
_system(PAnsiChar ('open ' + AnsiString (HTMLFile)));
{$ENDIF}
end;

By the way, I recommend that you do not directly call a windows function from a unit, where your
program logic is. It is better to place this in extra-units, e.g., a "WinOnly.pas" and a
"Shared.plattform.pas". In "WinOnly.pas", you ship features that only exist under Windows, and in
"Shared.plattform.pas", the functions that are available for several platforms.

This approach also has the advantage that you do not have to work all the time with IFDEF's in your
program. Then it is also much easier to expand your program for another platform (e.g. Linux) later.

R12 ... Drag and drop text from external source (e.g. browser) to a TEdit box
At the request of a customer, I have integrated a extension in my accounting program with a
functionality that one can drag a text selected from the browser onto an edit-field of the program. And
the text will be placed there, when the user release the mouse-button.
For this, you only need to complete the relevant events as follows:
procedure Tf_Bill.Edit1DragOver(Sender: TObject;
const Data: TDragObject; const Point: TPointF; var Operation: TDragOperation);
begin
Operation := TDragOperation.Copy;
end;

procedure Tf_Bill.Edit1DragDrop(Sender: TObject; const Data: TDragObject;


const Point: TPointF);
begin
if (Data.Data.TypeInfo <> NIL) and (Data.Data.TypeData <> NIL) then
begin
TEdit (Sender).Text := Data.Data.ToString;
end;
end;

Except for the Internet Explorer of Windows, this functionality is supported by most browsers and
indeed also by cross platforms (e.g., even Safari on the MAC). The drag and drop of text works even
with many word-processing programs.
R13 ... Store additional information in standard objects
From the VCL, you might know the property "Tag" which has almost every object and represents an
integer value.
In FireMonkey, you can set such a Tag value also, for example, with the Objectinspector. Except for
that, however, there are also existing the properties "TagString", "TagFloat" and "TagObject" for an
object or a control (TFMXObject). This is extremely useful if you want to store additional data,
information or objects to a specific control. These values, however, can be read or written only at
runtime (not via the Objectinspector). Overall, it's a very useful extension under FMX.

R14 … Using ActiveControl

If you have placed various controls in a form and make a query on the variable "ActiveControl", this is
- unlike under the VCL - always NIL. Whether this is intentional or a bug is not clear. Anyway, while
the program is running, it will sometimes be useful to know which is the active control (i.e. the one that
has the focus) right now.

You can directly use the property "Focused" of the form here, which is the control, that has the focus.

But if you want to set the ActiveControl variable, you can do the following in the event
"OnFocusChanged" of the form:

procedure TForm9.FormFocusChanged(Sender: TObject);


begin
ActiveControl := TControl (Focused.GetObject);

//if ActiveControl <> NIL then begin


// Label1.Text := 'Aktives Control: ' +
// ActiveControl.ClassName + ' (' +
// ActiveControl.Name + ')';
//end;
end;

You can activate here the disabled lines in the source-code above and see what will be shown when you
change the focus to an individual control (and, for example in a grid with F2 to get into the edit mode).
R15 … Replace OnDrawItem event of the ListBox from VCL with the OnPainting
event of the TListBoxItems

Indeed, the ListBox has no "OnDrawItem" event as the VCL listbox, but the ListBoxItem has an
"OnPaint" or "OnPainting" event.

Here you can, just like in the old VCL-way, do drawings as you want. That is, for example, useful if
you have data stored in internal objects that have been connected to the ListBoxItems (or otherwise
holded somewhere in a list or database). For the demo below, I have kept it simply and demonstrate it
without associated data objects. The ListBoxItems do not contain any text (and they should not because
it will be drawn by FireMonkey by default). I get text from the name of the ListBoxItems then.

So only the drawing process is demonstrated here:

procedure TForm22.ListBoxItem1Painting(Sender: TObject; Canvas: TCanvas;


const ARect: TRectF);
var
Flags: TFillTextFlags;
begin
With TListBoxItem(sender) do begin
canvas.BeginScene;

canvas.Fill.Kind := TBrushKind.bkSolid;

Flags := [TFillTextFlag.ftRightToLeft];

if Name = 'ListBoxItem3' then begin


if ListBox1.ListItems[ListBox1.ItemIndex] <>
TListBoxItem(sender) then
begin
Canvas.Clearrect (Arect, TAlphaColorRec.Yellow);
end;
end;
if Name = 'ListBoxItem2' then
Canvas.Fill.Color := TAlphaColorRec.red
else
Canvas.Fill.Color := TAlphaColorRec.black;

Canvas.FillText(ARect, name, true, 1, flags,


TTextAlign.taTrailing, TTextAlign.taCenter);

canvas.EndScene;
end;
end;

Note again: The property "Text" of the TListboxItems has no content itself, of course, because this
would result in duplicate drawings (text overlays). You can use the "Tagstring" instead of the "Text"
property of a ListboxItem if you want to keep the data in a Listbox-Item.

Here is the result:

R16 … Load Bitmap from resource file (for retina display)

From Delphi XE5 on, you can easily add to your program resources that can later be loaded into a
component. To distinguish: here, we talk about a program resource but not about the MultiResBitmap,
into which you can load multiple bitmaps at design time (also with different resolutions - in so far, this
tip here is an alternative solution).

When you run the program on MAC OS X, it may be that the user uses a screen with double
resolution, the so-called. Retina display. If it is important for your program that certain bitmaps can also
be displayed with the double resolution, you can create a bitmap with a normal resolution and save
additionally one with the double resolution in the program resource.

At runtime, examine the present resolution, and then load the appropriate bitmap in your image
component.

First, how do you get the bitmaps in the program resource? Here, you can use the command "Resources
and Images" under the "Projects" menu.

Add a normal sized image and provide the file name with a "1" at the end. Images with the double
resolution will be provided with a "2" at the end. Rename also the identifier as "Dia1". Warning: Upper
and lower case counts! So if you enter here "Dia1" and use in the source code "dia1", the resource will
not be found.

It is also important that you change the resource type of "BITMAP" to "RCDATA", otherwise it will
not work.

At runtime, for example, in the OnCreate event, you can do it like this:

// Unit FMX.Platform is required


procedure TForm1.FormCreate(Sender: TObject);
var
RS : TResourceStream;
ScreenSrv: IFMXScreenService;
scale: single;
begin
if TPlatformServices.Current.SupportsPlatformService
(IFMXScreenService, IInterface(ScreenSrv))
then
Scale := ScreenSrv.GetScreenScale
else
Scale := 1.0;

if Scale < 2.0 then begin


RS := TResourceStream.Create(HInstance,'dia1',
RT_RCDATA);
Image1.MultiResBitmap.LoadItemFromStream(RS,1.0);
end else begin
RS := TResourceStream.Create(HInstance,'dia2',
RT_RCDATA);
Image1.MultiResBitmap.LoadItemFromStream(RS,2.0);
end;

FreeAndNil(RS);
end;

So check first with the ScreenService which screen resolution is present. "1.0" would be normal and
everything else has a higher resolution. Then, you create a resource stream, load the bitmap into it and
then load the bitmap from the stream into the image component. The detour via the resource stream is,
unfortunately, necessary because the image component cannot load directly the image from a program
resource.

R17 … Swap items in a listbox

From the VCL, you know the function

ListBox1.Items.Exchange();

To swap two items, for example, you could use the following source code (assumed that there would be
10 entries in the listbox):

procedure TForm38.Button1Click(Sender: TObject);


begin
Listbox1.Items.BeginUpdate;
Listbox1.Items.Exchange(2,1);
Listbox1.Items.EndUpdate;
end;

Under FireMonkey, you have to do this:


procedure TForm4.btExchangeClick(Sender: TObject);
begin
lb.ItemsExchange(lb.ListItems[2], lb.ListItems[1]);
end;

Unlike under the VCL, you must omit here the BeginUpdate and EndUpdate.

Explanation of this: Internally, the function "Items.Exchange" uses a "Listbox.BeginUpdate" and a


"Listbox.EndUpdate" by itself. If you use, by yourself, "BeginUpdate" before, the internal routine
assumes that the list box is being updated and does not perform the change.

Unfortunately, this is not documented anywhere, but once you know it, OK.

R18 … Swap items in a Listbox via Drag & Drop

The listbox property "Allowdrag" must set to "True". In the "OnDragDrop" event you must respond to
the drop:
procedure TForm1.ListBox1DragDrop(Sender: TObject; const Data: TDragObject;
const Point: TPointF);
var
LI: TListBoxItem;
begin
if Data.Source is TListboxItem then begin
LI := ListBox1.ItemByPoint (Point.X, Point.Y);
Listbox1.ItemsExchange(LI, TListboxItem (Data.Source));
end;
end;

R19 … Using FMX functions in a VCL application via DLL

Converting an existing VCL application to a FireMonkey application can be done in a radical approach,
which means to convert everything in one action. The disadvantage is that this can take a long time on a
larger VCL project and in the meantime you cannot change much in the current application any more.

The alternative could be a softer transition. For example, you can outsource step by step dialogs and
related functions into a FireMonkey DLL. Here, you can also use the extended capabilities (graphics,
GPS-functions, etc.) so that the current application can directly benefit from it.

Perhaps you may not want to convert the project in general to FireMonkey, but for certain
functionalities, you would like to use FireMonkey. In both variants, it makes sense to provide this
function by using a FireMonkey DLL. This is not that difficult and it works much like under the VCL.

Here, I show you an example of how I have added a new filter effect to the VCL-image editing
program "PixPower Photo & Draw" with a FireMonkey DLL. Although the DLL has generated a size
of about 4 MB, it affects my installation package only with a value of 1.3 MB.

In the VCL application, I have a bitmap that I save as a bitmap stream and pass it to the FMX-DLL.
Unfortunately, I cannot directly pass the bitmap as "TBitmap" to the DLL, because VCL and
FireMonkey bitmaps are incompatible with each other.

In the DLL, the bitmap is displayed in a dialog in the" TImageViewer" component, to which I've added
a "PaperSketch" effect. The intensity is adjusted via a track bar.

If the user confirm the result with "OK", the effect will be then applied to the bitmap and this is written
back into the stream. Here a little trick is used because FireMonkey writes a bitmap as a PNG stream by
default. Therefore, a separate class "TMyBitmap" derived from TBitmap is used. It overwrites the save
stream procedure and adjusts it in a way, that the stream can be saved as bitmap stream.

That's the way how to do it: With the menu "File", command "New", create a new Dynamic-link
library:
library FMXFilters;

uses
FMX.Forms,

System.SysUtils,
System.Classes,

FrmFilter in 'FrmFilter.pas' {F_Filters};

{R *.res}

exports
ShowBitmapFromStream;

begin
end.

If you have created the library, the elements marked here in bold are not available automatically.

You have to add manually the Unit FMX.Forms so that it is clear that it should be a FireMonkey DLL.
Depending on whether you are creating the library in an already open VCL project or separately, it may
be that Delphi indicates that the DLL here is taken as a FireMonkey project. You can positively
confirm this query.

The Unit FrmFilter is a form unit that I have created under "File", "New", "FireMonkey Form":

Note: This command is only displayed when you have activated the DLL project in the Project
Explorer:
The form looks like this:

In the structure view, it looks like this:

In the source code I have defined the following function before the "Implementation" section:

Function ShowBitmapFromStream (ms: TMemoryStream): Boolean; export;

This is the function provided as externally callable function available through the "exports" statement
in the library file.
Note 1: If you want to pass a string instead of a bitmap to the DLL, you should use either a ShortString,
PChar or WideString. So you don't need to take the ShareMem unit into the unit uses and you don't
need to deliver the BORLNDMM.DLL with your application.

Note 2: If you want to ensure that the generated DLL can be called not only from Delphi programs but
also from the programs created by other development environments, you should use an "IStream" rather
than a memory stream.

Here is the implementation of this function in the form file (the units FMX.Filter, FMX.Effects,
FMX.Filter.Effects and FMX.Surfaces are required under uses):

function ShowBitmapFromStream (ms: TMemoryStream): Boolean;


var
Filter: FMX.Filter.TFilter;
begin
Filter := TFilterManager.FilterByName('PaperSketch');

try
F_Filters := TF_Filters.Create(Application);
F_Filters.ImageViewer1.bitmap.LoadFromStream(ms);

if F_Filters.ShowModal = mrOK then begin

Filter.ValuesAsBitmap['Input'] :=
F_Filters.ImageViewer1.bitmap;
Filter.ValuesAsFloat ['BrushSize'] :=
F_Filters.TrackBar1.Value;
F_Filters.ImageViewer1.bitmap := TBitmap
(Filter.ValuesAsBitmap['Output']);

TMyBitmap (F_Filters.ImageViewer1.bitmap).
SaveToStream (ms);

Result := True;
end else begin
Result := False;
end;

finally
F_Filters.Free;
end;
end;

Here are the adjustments, required to store the bitmap stream:

Type
TMyBitmap = class (TBitmap)
procedure SaveToStream(Stream: TStream);
end;

Implementation

procedure TMyBitmap.SaveToStream(Stream: TStream);


var
Surf: TBitmapSurface;
begin
Surf := TBitmapSurface.Create;
try
Surf.Assign(Self);
TBitmapCodecManager.SaveToStream(Stream, Surf,
'.bmp');
finally
Surf.Free;
end;
end;

With the Filter-Manager and the name of the filter function the wanted effect "PaperSketch" is selected.
Then, the FMX dialog is created. The bitmap stream is loaded into the bitmap of the TImageViewer.
If the user has set the desired intensity of the effect with the trackbar and then confirms this with "OK",
the setting of the trackbar-value will be applied.
Then,the modified bitmap with the derived class is stored as "BMP' bitmap stream (i.e., a bitmap in
RGB format).
In the VCL application the following unit is now added:
unit uFMXLink;
interface
uses
Windows, Dialogs, Classes;

type
TShowBitmapFromStream = function(ms: TMemoryStream):
Boolean;

var
ShowBitmapFromStream : TShowBitmapFromStream = nil;
DllHandle : THandle;

implementation

initialization

if DllHandle = 0 then begin


DllHandle := LoadLibrary('FMXFilters.dll');
if DllHandle > 0 then begin
@ShowBitmapFromStream := GetProcAddress(DllHandle,
'ShowBitmapFromStream');
end else begin
MessageDlg('The function „ShowBitmapFromStream“ / ' +
' the DLL-file "FMFilters.dll" ist not available',
mtInformation, [mbOK], 0);
end;
end;

finalization
if DLLHandle <> 0 then
FreeLibrary(DLLHandle);
end.
Under "Type", a function is defined, which corresponds to the export function of the DLL.

Under "Var", "ShowBitMapFromStream" is introduced as procedure variable.


In the initialization section, the DLL is loaded and, as a result, our previously declared procedure will
be assigned to the memory-address of the function in the DLL.
If you include this VCL-unit in your VCL application, you can call the function
"ShowBitmapFromStream" from here.
So, for example:
Var
MemStream: TMemorySteam;
TempFileName: String;
Begin
TempFileName := … // impose temporary file name
ABitmap.saveToStream (MemStream); // Save the bitmap as a
// MemoryStream
MemStream.position := 0;
If ShowBitmapFromStream (MemStream) then begin
MemStream.Position := 0;
ABitmap.LoadFromStream (ms) // load Bitmap back
end;
End;

NOTE: To get it to work, you have to include the unit "Winapi.GDIPOBJ" in the main form of your
VCL application, in fact directly in the USES section of the interface-section (not in the uses-clause in
the implementation section, which would be not enough).
This unit is required so that the GDI function can be initialized also for the VCL application. This can
only be done through the main form.
If you are interested, you can take a look at the functionality in the program once here
(www.Pixpower.info) or simply in a YouTube video in my PixPower channel, where I have
demonstrated this filter: http://youtu.be/W21uxyPsJvc.
Note: I've seen it in some situations that the call to "FreeLibrary" in the Finalization section causes the
program to hang. If you are experiencing problems, just remove the call, Windows automatically
removes the DLL from memory when the program has finished.

R20 … Draw text in TGrid right, or centered

From XE6 on, the TGrid contains, under "TextSettings", the possibility to choose the horizontal
alignment. With "HorzAlign", you can set the text to be left-justified, centered or right-justified.

However, this setting applies to all columns that are displaying text. The TColumns or TStringColumns
you have placed into the TGrid have no property like the TGrid. But this could be necessary, if the text
in one column should be displayed justified on the left, and in another column to the right (e.g.
monetary amounts).

Here, it is useful to set at first the Tag-value of the TStringColumns that should be aligned to the right
with the value of "1". For all columns that have the value "1", we give in the Grid.GetValue an empty
value back so that the grid itself does not carry out a drawing action here:

procedure TForm9.Grid1GetValue(Sender: TObject; const Col, Row: Integer;


var Value: TValue);
begin
if TGrid (sender).ColumnByIndex (col).Tag = 1 then begin
exit;
end;
....
end;

In the event of the OnDrawColumnCell of the TGrid, we call the DrawCellRight function for all
columns:

procedure TForm9.Grid1DrawColumnCell(Sender: TObject; const Canvas: TCanvas;


const Column: TColumn; const Bounds: TRectF; const Row: Integer;
const Value: TValue; const State: TGridDrawStates);
begin
DrawCellRight (Sender, Column, canvas, bounds, Row, Value);
end;

And now to the user-defined function. Here, we also use the OnGetValue event of the TGrid to get the
value of the cell. Temporarily we set the Tag value of the TColumn to "0" so that the value is returned
to us (Remember: We have installed a supplement above so that the value is only returned if the Tag
value of the column has the value "0".

It will look like this:

procedure DrawCellRight (Sender: TObject; Column: TColumn; canvas: TCanvas; bounds: TRectF; Row: Integer; Value:TValue);
var
B: TRectF;
V: TValue;
begin
if Column.Tag = 1 then begin
Column.Tag := 0;
V := Value;
TGrid (Sender).OnGetValue(Sender, column.Index, Row, V);
B := Bounds;
B.Right := B.Right-1;
DrawTextEx(canvas, B, TAlphaColorRec.black, V.tostring,
TTextAlign.Trailing);
Column.Tag := 1;
end;
end;

And here is the helper-function, which draws the text for us:
procedure DrawTextEx (Z: TCanvas; aRec: TRectF; ATextColor: TAlphaColor; S: String; a: TTextalign);
var
r: TRectF;
tf: tfilltextflags;
h: TTextalign;
begin
h := TTextAlign.taCenter;

Z.Fill.Color := ATextColor;

Z.BeginScene;
Z.FillText(arec,S,false,1,tf,a,h);
Z.EndScene;
end;

The result is that, for all columns whose tag-value are "0", the drawing starts at the left margin (done by
the TGrid itself), and for all those with tag-value "1", the text is right justified (done by our drawing
function):

R21 … Draw text in TStringGrid right or centered

Even in TStringGrid, you can set only the same text-alignment for all columns. We have also to draw
the text by ourself in the "OnDrawColumnCell"-event. This solution assumes that the string grid does
not hold the data but only displays the relevant content (as it should be). Let the left-aligned text output
as default and set the Tag value of the column that you want to be right-justified to "1" and that to be
output centered to "2". Then it looks so in the draw-event:
procedure TForm9.StringGrid1DrawColumnCell(Sender: TObject;
const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF;
const Row: Integer; const Value: TValue; const State: TGridDrawStates);
var
Flags: TFillTextFlags;
ar: TRectF;
S: string;
begin
Flags := [TFillTextFlag.ftRightToLeft];
canvas.BeginScene;
ar := bounds;

ar.Inflate(-1,-1);

canvas.ClearRect(ar);
canvas.Fill.Color := TAlphaColorrec.Black;

S := Row.ToString;

case column.Tag of
0: canvas.FillText(bounds, S, True, 1, flags,
TTextAlign.taTrailing,TTextAlign.Center);
1: canvas.FillText(bounds, S, True, 1, flags,
TTextAlign.taLeading,TTextAlign.Center);
2: canvas.FillText(bounds, S, True, 1, flags,
TTextAlign.taCenter,TTextAlign.Center);
end;

canvas.EndScene;
end;

Here is the result:

R22 … Working with the "visible" property of controls

From XE7 on, there is no longer the property "DesignVisible", that was available beside the property
"Visible". When now you set at design time a control to "Visible = False", it is also no longer visible at
design time. That makes it a little difficult to choose the control, e.g., for settings to make in the
Objectinspector. Use in this case the tree view where you can easily select and activate the invisible
control. If you upgrade a project from a previous version of Delphi, and a control no longer appears to
be available, check if you can find it in the structure view and if the "Visible" property just stood to
"False".
R23 … Prevent unintended shortening of TLabel text

From XE6 on, under "TextSettings the default setting for "Trimming" is "character" to display the label
text. AutoSize is turned off. This can sometimes lead to the situation that the text is shortened at
runtime with "..." when the display width is not wide enough. You should either set "AutoSize" to True
or turn off "AutoSize" and the trimming and leave more space for the display of the text in advance.
This is also to be considered because, for example, under Windows the display width is sufficient but
not under MAC OS, because the fonts sometimes are slightly different. It is therefore advisable under
all platforms to look at the dialogs at runtime.
Currently I suggest rather to turn off AutoSize, because I have found that relatively many times the text
is not displayed correctly in the hight (e.g. "g" and "p" has missing parts above and below), in particular
on MAC OS X. You should then also slightly enlarge the height of the Label component.
R24 ... How to show a pop-up menu at a special position
Maybe you are faced with the situation that the user can click on a specific item and then a pop-up
menu should be displayed above or below it. Here is an example that the user clicks on a panel and then
a pop-up menu will be displayed below it:

You can do it like this:


procedure Tfrm_Main.pnMahnungRangeMouseUp(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Single);
begin
hs_ShowPopup (self, pnMahnungRange, popMahnung, unten);
end;

The function "hs_ShowPopup" is defined as follows:


procedure hs_ShowPopUp (frm: TForm; bn: TControl; pop: TPopupMenu; ObenUnten: Integer);
var
FP: TPointF;
begin
FP.X := 0;
FP.Y := 0;
//Transposes the coordinates in the context of the form.
FP := bn.LocalToAbsolute(FP);
//Transposes the coordinates in the context of the screen.
FP := frm.ClientToScreen(FP);
//Display the popup menu at the calculated coordinates.
pop.Popup(FP.X, FP.Y + bn.height);
end;
This feature also works on multi-monitor systems.

R25 … Determine the document directory

For all platforms you can use the record "TPath" from the unit "System.IOUtils". It contains a lot of
information about directories that can be accessed with the corresponding functions:

procedure TForm2.FormCreate(Sender: TObject);


var
DocPath: String;
begin
DocPath := TPath.GetDocumentsPath;
end;

Here is the result for DocPath on my MAC or Windows computer:

under MAC OS X: „/Users/harrystahl/Documents“


under Windows: „C:\Users\Harry-Dev\Documents“
under Linux: "/home/harry/Dokumente"

Tip: Click once on "TPath", holding down the Ctrl key and Delphi will show you the TPath record.
Explore the individual variables and functions that are available there (in the public sector). It is really
worthy, some of them you can use again later, for sure. For example, you can also use the images
directory (TPath.GetPicturesPath) or the video directory (TPath.GetMoviesPath).
R26 … Improve the font quality (especially on Windows)

Under Windows, the display quality of the fonts under FireMonkey is not optimal. I improve it by
turning off the Direct2D functionality in the project file before the initialization:
begin
FMX.Types.GlobalUseDirect2D := False;
Application.Initialize;
Application.CreateForm(TF_Main, F_Main);
Application.Run;
end.

Hint: The unit FMX.Types must be included.

R27 … Select a folder with a dialog

With "SelectDirectory", you can now select a folder:


procedure TForm2.Button1Click(Sender: TObject);
var
dir, root: string;
begin
root := '';
// root := System.IOUtils.TPath.GetPicturesPath;
if SelectDirectory ('Bitte wählen Sie ein Verzeichnis', root,
dir)
then begin
ShowMessage ('You have selectd: ' + dir);
end;
end;

The Unit FMX.Dialogs must be integrated (for "ShowMessage"). The first parameter is used to specify
a description that is displayed in the title bar of the dialog. In the second parameter, you can specify a
directory, from which the dialog starts (by default, it is the document directory). The third is a VAR
parameter and returns the selected directory name.
R28 ... Let a column in a string grid occupy the remaining space
You have a string grid with multiple columns and when changing the size of the grid, you want always
that a particular column responds to the changes in size and occupy the free space in the grid.
It should be noted that the calculation can work properly only when the grid has been shown once,
which means the OnShow-event of the form has been once run through completely.
For this purpose, you must use a timer set enabled in the OnShow event with the interval of 25 ms. At
the first time the timer event is triggered, you disable the timer and make the calculation as wanted
(later you do the calculations on the OnResize event of the form or the grid).
It could look altogether as the follwoing:
var
Form9: TForm9;
VarCol: Integer;

implementation

{$R *.fmx}

procedure TForm9.FormCreate(Sender: TObject);


begin
VarCol := 0; // für die Erste
// VarCol := StringGrid1.ColumnCount-1; // z.B. für die letzte Spalte
end;

function AColWidth(Grid: TStringGrid;


VarCol:Integer): Extended;
var i:
integer;
aWidth: Extended;
add: Single;
begin
aWidth := 0;
for i := 0 to Grid.ColumnCount - 1 do
if i <> VarCol then aWidth := aWidth +
Grid.Columns[i].Width + 1;
if (Grid.VScrollBar <> NIL) and
(Grid.VScrollBar.Visible) then add :=
Grid.VScrollBar.Width else add := 0;

Result := Grid.ClientWidth - aWidth -add-


Grid.columnCount;;
end;

procedure TForm9.FormResize(Sender: TObject);


begin
TStringColumn (StringGrid1.ColumnByIndex
(VarCol)).width:= AColWidth(StringGrid1, VarCol);
end;

R29 ... Create missing components with Frames

You may need a replacement for a component that is either not already available for FMX or a
particular platform. Suppose you need a simple chart component, then create a simple solution with a
frame.
Below is a screenshot of a frame that provides a simple BarChart solution, here at runtime:

The only task in the later use of this frame is to fill the StringGrid with a few meaningful data and then
call the method "UpdateChart" of the frame. The StringGrid can be displayed or hidden with the "Show
data list" checkbox at runtime, or directly at the first display.

And this is how the code looks like:

procedure TForm71.bnMakeChartClick(Sender: TObject);


var
L: Integer;
begin
With FrBarchart1 do begin

ChartHeader.Text := 'Jahresumsätze von 2007 bis


2016';
scTxt.Header := 'Jahr';
scVal.Header := 'Umsatz';

sgData.RowCount := 10;
for L := 0 to sgData.RowCount-1 do begin
sgData.Cells[0,L] := (2007 + L).ToString;
sgData.Cells[1,L] := (8000 + Random
(12000)).tostring;
end;

UpdateChart;
end;
end;

Here you can set a header for the graphic with "ChartHeader.text". With "scTxt" and "scVal" you
access the headers of the TStringColumns and set a meaningful heading for the data. Then we simply
created the year dates and a few random sales figures for a 10-season period.
So also the use of the FrameBarChart is kept fairly simple. Developing the FrameBarChart did not take
much time, so the frame looks at design time:

When we look at the structure list, we see that the chart frame consists of TLayouts, rectangles, TText
components, and a TPlotGrid.

So everything is just standard components of FireMonkey, with which you can easily generate
something new.

Finally we created here with the TLayout "LayBar" a kind of template, which is simply duplicated and
then the corresponding values are set. Your copies are placed in the initially empty TLayout
"LayChartInner". Then you can delete the copies with "LayChartInner.DeleteChildren" when you re-
use or update the chart.

The functional source code of the unit does not have more than 80 lines of text. In addition to the
generation of the chart, the system responds to resizing events and gives a function to copy the
generated chart graphic to the clipboard.

And so the function "UpdateChart" looks:

procedure TFrBarChart.UpdateChart;
var
barcount, L: Integer;
lay: TLayOut;
barwidth, barspace, barHeightMax, barval,
barvalMax: Single;
T: TText; bartxt: string;
begin
LayChartInner.DeleteChildren;

PlotGrid1.Frequency := rtInner.Height / 12;

barspace := 20;
barcount := sgData.RowCount;
barwidth := ( LayChartInner.Width - (barspace *
barcount) ) / (barcount+1);

barHeightMax := LayChartInner.Height-30;

for L := 1 to barcount do begin


barvalmax := max (barvalMax,sgData.Cells[1,L-
1].ToSingle);
end;

for L := 1 to barcount do begin


Lay := TLayOut (rtInner.FindStyleResource
('layoutbarprototype', true));
Lay.Width := barwidth;

bartxt := sgData.Cells[0,L-1];
barval := sgData.Cells[1,L-1].ToSingle;

if lay<> NIL then begin


lay.Parent := LayChartInner;
lay.Position.X := ((L * BarSpace) + (L *
barwidth)) - barwidth; // - barwidth / 2);
lay.Height := (barheightmax / (barvalmax /
barval));
lay.Visible := True;

lay.Position.Y := LayChartInner.Height -
lay.Height;

T := TText (lay.FindStyleResource ('bartext',


false));

if T <> NIL then begin


T.Width := Barwidth + BarSpace;
T.Text := bartxt;
T.Position.Y := Lay.height;
T.Position.X := - (BarSpace / 2);
end;

T := TText (lay.FindStyleResource ('barval',


false));
if T <> NIL then begin
T.Text := barval.ToString;
T.Width := Barwidth + BarSpace;
T.Position.X := - (BarSpace / 2);
end;
end;
end;

LayBar.Visible := false;
end;

As a reminder, it is only a small demo, so you can still optimize and expand it. Add a Y-axis with
appropriate values, possibly display an optional legend, etc. Or display the values of the numbers in the
right-aligned grid (see tip R20).

Here you can download the demo including frame and use it for your own purposes:

http://www.devpage.de/download/fmbook4/FMXBarChart.zip

R30... Moving controls at runtime in the form

Possibly you want to offer in your program a functionality with which the user can move the elements
(Editfield, Memo, Listbox, whatever) in the form to create their own data masks.

The TSelection component is well suited for this task. Generally it is designed to be moved on the
surface of the form and to be resized by using selection points.

In this respect, you simply have to temporarily put the element that is to be moved into the TSelection,
turn off the hit property of the element to be moved, and move the element (in truth the TSelection)
with the next click. If that is done, you set back the former parent of the moved component and that was
it already.

Here is a screenshot of the demo program at design time and the source code:
Here is the source code:
procedure TForm10.FormCreate(Sender: TObject);
begin
Selection1.Visible := false;
Switch1.IsChecked := false;
end;

procedure TForm10.Rectangle2MouseDown(Sender: TObject; Button: TMouseButton;


Shift: TShiftState; X, Y: Single);
begin
if Switch1.IsChecked = false then exit;

selection1.Visible := True;

if selection1.ChildrenCount > 0 then begin


with TControl (selection1.Children[0]) do begin
Align := TAlignLayout.None;
SetBounds (Selection1.position.X,
Selection1.position.y,
selection1.width, Selection1.height);
hittest := true;
Parent := self;
end;
end;

With TControl (Sender) do begin


Selection1.SetBounds(position.X, position.y,
width, height);
Align := TAlignLayout.Client;
Parent := selection1;
Hittest := false ;
end;
end;

procedure TForm10.Switch1Switch(Sender: TObject);


begin
if Switch1.IsChecked = false then begin
if selection1.ChildrenCount > 0 then begin
with TControl (selection1.Children[0]) do begin
Align := TAlignLayout.None;
SetBounds (Selection1.position.X,
Selection1.position.y,
selection1.width, Selection1.height);
hittest := true;
Parent := self;
end;
end;
selection1.Visible := false;
end;
end;

Here is the download link for the demo:


http://www.devpage.de/download/fmbook4/MoveElements.zip
Chapter 12: Outlook
Other topics
While the first versions of the Delphi XE3-XE7 book were designed to make the leap from the VCL to
the FMX developer, I would like to focus on FireMonkey's specific abilities and capabilities, e.g. in the
field of 3D programming. Also the topic of Linux development can certainly be expanded.

Do you have wishes or proposals? If yes, send me an email.

Book review
A request at the end: If the book was or is a help, then I would be glad if you would give a positive
review at Amazon. Writing such a book is hard work. Unfortunately, the motivation to give a rating is
often higher among people who have found a hair in the soup than those who have just taken a plate of
soup and are satisfied. So I would be glad about your support, it would be a great motivation to
continue my work.

You might also like