Вы находитесь на странице: 1из 225

Author:

Harry Stahl
Publisher: Harry Stahl
Location: Bonn
Copyright (2015), All rights reserved
Delphi and FireMonkey are registered trademarks of Embarcadero.
Apple, OS X, iOS, iPad, and iPhone are trademarks of Apple.
Windows is a registered trademark of Microsoft.
Should I have used this book a trademark, without referring to the owner, please send me a
short message, I will add it immediately.

1st edition

Content

Foreword

Introduction

The book

The author

Contact information

Chapter 1: Using 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
Revert to inherited settings
Creating a Platform-specific event handler with the Multi Device Designer

Section 3: Selected FireMonkey components


TButton (mit Trimming)


TEdit (without PasswordChar)


TForm (furthermore with caption)


TFrame

TPanel

TCheckbox, TRadioButton (IsChecked)


TSwitch

TImage

TImageControl

TImageViewer

TImageViewer (to use with LiveBindings-Designer)


TLabel (New property FontColor)


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 availalbe - but replacement via 3rd-party possible)


TPageControl (Not available - but replacement available)

TStringGrid (works different)


TGrid (Image and other elements in the Grid)


TStringGrid-alternative (TMSFMXGrid)

THeader (no 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 4: The FireMonkey Style-Designer


a) Using the Styles Editor


b) Styles in FireMonkey - an overview


c) Convert VCL Styles to FireMonkey Styles


d) Using FireMonkey Styles



Section 5: Convert VCL programs into FireMonkey programs



a) Working with the Mida Basic Edition


b) Working with the Mida Pro / Studio Version


c) Strategic approach to working with the Mida Converter



Chapter 2: 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: Sandboxing and persistent access to Bookmarks


Section 7: Use MAC APIs (POSIX, CORE and Cocoa) in Delphi



Chapter 3: 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


1. Submission to the APPLE App Store


2. Create a .dmg file for distribution outside the Apple App Store

3. Create your own setup package with Application Developer ID / Installer


a) Request a Developer ID certificate and an Application Developer Installer ID


b) Working with the codesigning tool and Package Maker



Chapter 4: Working with Graphics in FireMonkey


1. FireMonkey TBitmap versus Windows TBitmap


2. TBitmapData instead ScanLine for bitmap manipulation


3. Change the alpha channel of a TBitmap


4. Draw on the canvas of a bitmap


5. Turn graphics, flip, invert or color to gray


6. Drawing a bitmap scaled



Chapter 5: Useful third party components for FireMonkey


1. TMS-Components

2. Report generator FastReport FMX


3. RemObjects-Application Framework (Hydra)


4. Other components

Chapter 6: How to - tips & tricks for FMX



R1 Get the display resolution?


R2 Check if 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 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 determine the current user name in Mac OS X / 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 After uploading to App Store: Invalid binary - causes and remedies

R13 Application rejected: Some reasons for refusal, which you can avoid

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 Dealing with the visible property of controls


R23 Prevent unintended shortening of TLabel text


R24 Use hints in FireMonkey: How it goes


R25 Determine the document directory


R26 Improve the font quality (especially on Windows)


R27 Select a folder with a dialog


R28 Get access to a cell control of TGrids


R29 Show pop-up menu at a special position


R30 Store additional information in standard objects


R31 Drag and drop text from external source (eg browser) to a TEdit box

R32 A column in a string grid should occupy the remaining space


Chapter 7: Upgrading from Delphi XE3-XE6 to XE7


Chapter 8: Outlook

Attachment 1: Unit HSW.FMXSandbox.pas


Atachment 2: Newly implemented Open and Save dialogs for sandboxing



Foreword

After the first version of Delphi with FireMonkey in Delphi XE2 in late 2011, it happened
in quick succession: at the end of 2012 already XE4 was published and at end of
September 2013 it was followed by Delphi XE3, April 2014 then XE5, April 2014 XE6
and in September 2014 then XE7. And XE8 will be expected in April 2015.

With XE7 you cant only develop programs for Windows and Mac OS X, but also for IOS
and Android. These are fantastic opportunities that are now possible with a single
development environment. Nevertheless, the IOS and Android development is not the
subject of this book. It remains as in the previous version of the book to Delphi XE3-XE5
in cross-platform development for Windows and MAC OS X. For IOS and Android, I
must still refer to other deals (where they exist).

Whether you are still beginners for FireMonkey programming or have already gained
experience in this field, I am assuming that you will find information in the book that will
help in the development of your projects.

If you are still working with Delphi XE3-XE6, I recommend that you purchase the
previous version of this book, because from the beginning of XE7 a lot has changed in
FireMonkey and Delphi. It would be best, if you would have the current compiler version
XE7 available (or XE8, when it is out).

Experienced users who previously developed for Windows are faced - when migrating to
FireMonkey - with a series of questions that are sometimes not easy to answer. First of all,
there is the establishment of the connection between Windows PC and MAC and the
various setting dialogs that need to be found out.

Often there are only small differences between FireMonkey- and VCL components, but
because of that, one fails in development work. It consumes a lot of time to find the
differences. This is what the book is made for. It explains the small and large differences
among the most important of the well-known VCL components.

The use of FireMonkey components makes especially sense, if they are used for crossplatform development (beside this, if exceptional graphics processing is required). But
under Windows and MAC some functions are completely different, for example passing
parameters at the start of the program.

To save you hours of search, be sure to find some of the answers you need in this book.
This book is also under construction for an extended period of time (usually until there
is a new Delphi XEx version published).

If the MAC is new to you, as well you wont even know basic information about the
handling of files, available storage locations or otherwise required developer tools. Its all
here in the book, at least with the most important information.

If you have any suggestions on topics which should be included here, write an email to the
author.

The only warning: This book is not about the topic databases. This is due to the personal
circumstance that the author doesnt use database components of Delphi but a personal
solution for work with databases.

One last note: The book was originally written by me in German language. After that I
have translated it into English by myself. Please excuse, if one or the other translation is
not as gallant as it should be. I would be pleased to receive hints concerning possible
improvements.

Bonn in April 2015

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 the internet, personal studies to the sources of Delphi and
the MAC API libraries were necessary to introduce the solutions offered in the book here.
Im using these solutions in my own programs, such as in TEditor for MAC

http://www.hastasoft.de/TEditor.htm,

with which one can read and write ANSI text and UNICODE in different formats. For me
it is an indispensable tool, because for example the MAC text editor cant read Windows
ANSI texts (German Umlaut problem).

The book
This book will be once again only available in an electronic -kindle- version (later a print
version is planned). But it will be also available in PDF format to download from my
Devpage website (http://www.devpage.de). The Kindle version has got the advantage that
you can use the book across multiple devices.

How to read the book: If you have not set up your Mac to work with Delphi yet, maybe
you start with the relevant chapter (Chapter 3, Section 1). If you have already completed
the setup, you can just start reading from the beginning.

Note: To facilitate the search of specific issues (e.g. information on updates of this book),
in some places a heading Quick finder xxx was be inserted as reference (where xxx is a
number).

The author
Im a little longer time underway with software development. I started with Turbo Pascal
version 3.0 in the eighties, then I bought 5.0 Turbo Pascal for 700 DM, which was at that
time a lot of money for me. Apart from a small detour to Visual Basic, after this I
remained faithful to Delphi with any version from number 1 to the current version XE7.
All Doberenz / Kowalski Delphi books are in my library, as well as anything you can get
about Delphi by Elmar Warken and pretty much everything from the C & L Publishing.
Also books by Marco Cantu and Bob Swart I call my own.

By now Ive written dozens of programs with Delphi. With it you can develop robust and
sophisticated programs which more than 5,000 customers with 10,000 users can
acknowledge.

Contact information

You can find my general web page at www.hastasoft.de, 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.

By e-mail, you can reach me at info@hastasoft.de.

Chapter 1: Using the FireMonkey


components

Section 1: Getting Started


This book is not so much to describe to you the many new components that offers
FireMonkey. For that you can use the supplied program examples that give a good
overview here. For example, open the included demo Control Demo. The FireMonkey
demos can be found as:

From the Start menu select the Embarcadero program group to go to the demo directory
(examples).

This book is more about to remove the stumbling blocks, if you plan to convert a
previously developed VCL-program into a FireMonkey-program or start new projects
with FireMonkey.

If you want to have programmatic help to convert existing VCL applications, you can use
the Mida converter. For a while the converter was as a free gift with purchase of Delphi
XE7 in a BASIC version here:

In the paid Pro version (from about 90, - ) also third components will be converted (for
example TMS components). In addition, the source code is restated as far as possible (site
of the manufacturer: http://www.midaconverter.com).

In Chapter 1, Section 5, I present the basic use of the program and have a look at the
specifics of Pro / Studio version of the program.

Section 2: New FireMonkey project


Doubtless the question arises of how and when to go into detail about the individual
differences between Windows and FireMonkey.

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

FireMonkey desktop application (Multi Device Application)


So, there you go. As with any Windows project you start developing a FireMonkey
program to run both on Windows and the Mac under the File menu with the command
New Multi Device Application.

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

The Empty Application corresponds to the former HD application from Delphi XE6 or
earlier. The other templates you would normally use rather for mobile applications (i.e.
iOS and Android). But here we want develop a normal desktop application, so select the

Blank Application. Then click OK.


Result:

By default the Target Platforms on the right side of the node are collapsed. Unfolded, it
looks like this:

The 32-bit Windows platform is enabled by default. If you would compile the program
now, you will get a 32-bit Windows program. Delphi has also been automatically added a
MAC OS platform and the mobile platforms iOS and Android (but you can ignore the
mobile platforms, if you dont need it).

You could simply select one of the listed target platforms by double click and compile it,
and you would have a corresponding target platform application. The program will be able
actually to run on a Mac or on a mobile device, but you still have to set up the
environment according to Delphi. How to do this for the MAC, described later in this
book.

Using the Multi Device Designer (Fire UI)


Here you can now get started to add components, set their properties, and write event
handlers to interact with the user. If you develop a cross-platform project, it is generally
recommended to add at the very beginning a view for the MAC OS Platform. So you can
also compile the MAC-version from time to time and look, how it works.

However, you should work as much as possible at the view Master and only use the
other views to make specific settings for the relevant operating system.

With the left drop-down list, you can also use the master view with different styles.

In the various created views (OS X, Windows, etc.) you can adding 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 the component. Logically, it is then removed
from the other views. The same procedure is valid for the name of the component: Here
also you can - because the name must be unique - change it only in the Master View.

The use of the Multi Device Designer facilitates the development of cross compile

applications enormous. For example, the buttons in dialog boxes are located at the bottom
left on Windows, and under MAC OS at the bottom right. Also, buttons on MAC OS
normally do not have graphics.

So here is the view of a dialog from my accounting program that is available for Windows
and Mac OS X:

At the bottom left are the buttons with the images. Now to the MAC OS X view:

Here, the buttons are moved to the right side and images in the Objectinspector are set to
Visible = false. The checkbox has been moved to the left side.

The nice thing is, that you simply can use the designer here and not have to make any
programmatic transformations for the dialog at runtime. In the past that was the common
and 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 appropriate platform. If you look at the files created once
in a file manager, then this looks as follows:

FMandant.fmx is the master form file. This file serves as a master for the respective
generated platform. So if we had only this master form and create the program for MAC
OS X, then only this master form is used.

Since we want to create both a Windows program as well as one for MAC OS X, we have
here selected OS X Desktop as view in the right drop-down list so that Delphi then
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 like the
TFrame). When we open the MAC form in a text editor, then 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 at the right side, instead of the left, so it has a different
horizontal position and this value (here in green) will be detected. The image symbol in
the button should be visible under Windows, but not under MAC OS X so even this is
noted accordingly (blue background).

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

Revert to inherited settings


If you have made changes to a control on a different platform view, you can partially or
completely revert the settings, to the values that are in the master view. Example:

Here the button has been moved on the MAC view to the right and the text was changed to
MAC.

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

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

Do you want e.g. only restore the original text of the button, click once on the button with
the left mouse button to select it. Then, in the Objectinspector, right-click on the word
Text (ie 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 in Button1.

So it is possible only to bring back 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 yet a further simplification: Until now you

have edited in an OnClick event the source code for the use for different platforms.
Normally you use the IFDEF switch to make the source code compileable for either
Windows or for Mac. Even actually it might be the right way, if you have only a few
different platform-specific statements in the code. But should the code very different for
each platform, you have a lot of text in the event handler and the matter can become
unmanageable. It lends itself directly to create 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 and press the enter key. Already Delphi creates a new empty event
handler, and 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
again with IFDEF, but only once, because you can souround the whole inner frame with
{$ IFDEF MACOS} {$ ENDIF}.

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


There is another innovation in XE7: For a number of components you can for certain
properties, e.g. instead of Top or Bottom in the TTabPosition a TabControls select the
property PlatformDefault as initial setting.

So even if you have only one Master View and do not generate platform-specific views,
because of the Platform Default an all platforms everything would be on in its proper
place. The functionality mentioned in this example would be more relevant for iOS and
Android, because there the tabs should be in a standard equipment down or up.

Section 3: Selected FireMonkey components


On the basis of the individual components and features that you normally use here, Im
now going one by one to the individual components.

TButton (with Trimming)


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

But there is also something that the VCL button does not have: The property Trimming.
With that you can specify how to deal with the displayed text of the button, if the text is
too long to display.

The default setting is now ttCharacter. So if the text will be displayed with a letter and 3
points between the outer button border. E.g. in the text Repetition like Repet .

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

With ttWord if available, the border to drawings is set at a full word-border.


So Print and Close would be shortened to Printing and when the word Close
would 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, but also the property Stay Pressed must be enabled.

You cant assign an image to the button (as well as the TSpeedButton not). So you need to
add an extra image into the button when it is to have a symbol.

Unfortunately, the TButton and all other FireMonkey components has not the property
Hint. Again, initially remains the principle of hope, that here a corresponding property
will be added in the following updates.

But you can use my HS_FMXHints.pas unit with which you can easily add hints to the
controls. Look here in Chapter 6, at hint 24 at the end of the book.

TEdit (without PasswordChar)


If an edit field shall 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 ist used on the platform in your
form (currently only on iOS). This can be an advantage for some things, when it comes
about by the use of a screen reader or similar things, or even to get a 100% integration
platform appearance. If you choose this option, it is already showing this control changes
at design time:

In addition to the TEdit, the Control Type property is also available for the TCalendar
control, effective only for iOS. I mention this functionality here, so you know now the
meaning, an on the other hand, I assume that over time, further controls are provided with
this property and if you also use iOS and Android next to the MAC OS platform, this is an
important information for you.

TForm (furthermore with caption)


While actually all FireMonkey components are not using the caption property, but the
Text property, the form also has furthermore the property caption.

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

With the property FullScreen you can have the form directly start in full screen mode.

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

TFrame
Since XE 4 frames are also available, basically it works like in Windows. Under File,
New, Other menu, you can first create a TFrame. Then you can click from the
component palette on the frame icon 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.

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


A speciality has the component TPanel which does not have the property Caption nor
the Text property. 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 from, for
example, to set to Align-property to alClient. Even here you have to use a qualifier, the
term TAlignLayout.

Example: Panel.Align: = TAlignLayout.alClient;

TCheckbox, TRadioButton (IsChecked)


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

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 appeareance
(Styles, e.g. StyledSettings, StyleLookup and StyleName, but more on that later).

TSwitch (Alternative to the TCheckbox)


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

Thus, 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
and influence the way of displayit 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, that you can directly save as many bitmaps as you need for these resolutions
according to use. If you call here the wizard for editing, you may get the following dialog:

Here you can add individual bitmaps by first click on the first icon on the top left (for new
or add). Then you can click the open icon in the text input line to select a 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 glass icon in the toolbar.

Note: If you click on the green check arrow, a query arise, whether the information
available at design time, are to be deleted. If you confirm this with Yes, the information
to the source directory of the bitmaps will not be displayed at the next time when using the
dialog. As long as you are still need this information, you should close the dialogue on the
red window close button.

At runtime, you access the individual bitmaps on demand via the items property, eg as
follows:

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


It is not necessary to save in this component images with various resolutions. You can also
save different pictures with same resolution, thus quasi achieve a replacement for the VCL
ImageList. However, you cant enter 2 times the same scale value.

It is handy that you can drag from Windows Explorer or any other file manager files
directly into this image editor.

Details and further information you can display, by pressing the F1 key in the open bitmap
editor dialog, you get a fairly detailed description of the dialog.

The property WrapMode you can use to influence the type of presentation.

The following modes are available here:


WrapMode:
iwFit

(Default) - adjusts the image to the square of the component by 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

Centers the image horizontally and vertically centered in the component

TImageControl (automatic scaling)


A graphic that is loaded by using the property bitmap, is scaled automatically. When
you click at runtime at the displayed image, a file-open dialog will be opened, where you
can load an image into the component. If this default behavior should not be given as a
standard, you can avoid this with the following assignment:

Imagecontrol1.EnableOpenDialog: = false;

TImageViewer (pictures scaled by user)


The Image Viewer is a very interesting component. Again, you load the graphics with the
property bitmap into the component. By default, the properties MouseScaling and
Mouse tracking are activated here. As a result, the user can scale the size of the graphic
with the mouse wheel, when he is with the mouse 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 component is ideal for producing a dynamic link to another component using the socalled live bindings. Here is once demonstrated how such a thing goes with the trackbar
component:

Here in the Objectinspector we have clicked Bind visually on the property


LiveBindings. This will automatically show the LiveBindings-Designer. There you
can now use the property BitmapScale from the ImageViewer1 with the property
Value to connect the Trackbar1 component.

Just click on the ImageViewer1 element on the 3 dots, it will pop up then a dialog
Bindable Members. There select the property BitmapScale and confirm with OK:

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

Result:

If you do this for the first time, you can also use also the LiveBindings-Expert. To do
this, click in the LiveBindings designer on the item ImageViewer1. 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 Next.

In the dropdown list select the component ImageViewer1. Select then as property
BitmapScale:

Then click Next and select on the next page the control Trackbar1. Then click
Finish.

In the designer now you can see, that the property BitmapScale from the
ImageViewer1 is linked to the Value property of Trackbar1.

Because the property BitmapScale with value of 1 stands for 100% image size, set the
property value Max of the trackbar 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 with using the slider.

This is very easy to do, and there was no need to to create a single line of source code for
it. Below the runtime result:

TLabel (New property FontColor)


If you wanted to change the color of a label, 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. Since FireMonkey 2, this is much easier. Just use the
property in the Objectinspector TextSettings and then FontColor and select the desired
color.

Note: The selection in FontColor only works if StyledSettings in the FontColor option
is disabled:

Whether this mixing of the various settings (direct via Styles and settings in the
Objectinspector) is useful or not, I just let stand there. Anyway, you should know this,
because otherwise you may be looking quite long, why the color that you choose in
FontColor is not displayed. The property StyledSettings refers to the current style.
This can be the default style, but this can also be a user-defined style. The settings of the
style override any other settings when it is enabled here in StyledSettings.

TImageList (Not available - but compensation possible)


The so extremely useful VCL TImageList that serves as a container for graphics and is
editable at design time via a convenient dialog, does not exist under FireMonkey. A
workaround could be, to take an extra form that you can provide with many TImage
components.

If you bought the FireMonkey Pack from TMS software, there is the
component TMSFMXBitmapContainer of interest. This gives namely an easy way to
load on a slip multiple graphics files at the component or add someones to it.

To do this, click the right mouse button on the component and select one of the top two
commands from the pop-up menu. If you have already loaded files and want more to add,
select Add files and NOT Load files, otherwise existing entries are removed and only
the new ones are added and stays in the list:

You can select a bitmap and delete it, using the delete key here in the tree view. In the
Objectinspector, you can check by name, if youve hit the correct bitmap:

Warning: Do not use the dialog Bitmap Editor. When you delete the bitmap in this
dialog, the bitmap is not removed, but the content is deleted!

At runtime, you can use the bitmaps from the container eg to display them in a grid, here
is an example:

For this purpose, the appropriate code would be:



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

if aCol = StringColumn1 then
Value := intToStr (Row+1)
else if aCol = ImageColumn1 then begin
if Row <= TMSFMXBitmapContainer1.Items.Count-1 then begin

Value := TMSFMXBitmapContainer1.Items[Row].Bitmap;
end;
end;
end;

Another useful feature of the container is FindBitmap, with that you can access the
stored bitmap by name:

Bitmap := TMSFMXBitmapContainer1.FindBitmap(computer.bmp);

I like this very much. It is better than the TImageList where you could access the bitmaps
only with integer values.

The demo can be downloaded at:


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

But you need the TMSFMXStringGrid installed or an installed TMS Pack for
FireMonkey.

TListBox (no TCheckListbox, but ShowCheckboxes)


Dont waste time so search a TCheckListBox, there is not such a component for
FireMonkey. If you want to display a listbox with checkboxes, select instead the
ShowCheckBoxes option. Quite interesting is perhaps the property ListStyle, hereby
you can display the entries in the list box next to each other (lshorizontal).

Heres an example:

Although the listbox works in general things also like the VCL component (eg
Items.LoadfromFile, etc.), however, is to distinguish whether you are working at the
design or at runtime using Items or ListItems:

In the illustrated listbox on the left side, the entries were added by using the Strings
property of the listbox. In the second listbox I had clicked with the right mouse button on
the listbox and then select 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 at designtime select the
individual ListBoxItems by mouseclick and make the appropriate changes to the settings
in the Objectinspector.

Warning: if you in the second listbox now edit the entries over the property
Strings and take the changes, all ListBoxItems would be converted to normal strings, so
that all their previous settings would be lost to the individual ListBoxItems. So here you
have to take care how to proceed.

Here an example how to access the items. At design time:


At runtime, you can address the individual items of a ListBox both on the property
ltems (= list of strings) and the properties of 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 read if in a ListBoxItems 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 click the right mouse button on the listbox, you can
add not only simple text entries, but have a choice:

With a TListboxGroupHeader you can e.g. use in the listbox subheadings. Very useful is
the TSearchBox. When you enter text there, the listboxitems will be filtered and displayed
so that only the matching entries are shown. For example, if the list box offer quite a lot of
options, the user can enter a keyword and he immediately receives the appropriate setting
option. That is something comfortable, what the VCL listbox does not offer!

A list box with a group header and a SearchBox:


And now this list box after the user has made some text input into the SearchBox:

If you click at design time with the right mouse button on the SearchBox, you can, for
example, add a ClearEdit button that clears the input at runtime in the text SearchBox
again. 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 to get the wanted representation.

Explore the supplied example CustomListbox in the example directory. You will find it
usually here:

C:\Users\Public\Documents\Embarcadero\Studio\15.0\Samples\Object Pascal\
FireMonkey Desktop\CustomListBox

Here is the compiled sample:


If you look at the source code, you can see how a listbox entry is generated:

procedure TfrmCustomList.Button2Click(Sender: TObject);
var
Item: TListBoxItem;
begin
// create custom item
Item := TListBoxItem.Create(nil);
Item.Parent := ListBox1;
Item.StyleLookup := CustomItem;
Item.Text := item + IntToStr(Item.Index); // set filename
if Odd(Item.Index) then
Item.ItemData.Bitmap := Image1.Bitmap // set thumbnail
else
Item.ItemData.Bitmap := Image2.Bitmap; // set thumbnail


Item.StylesData[resolution] := 1024x768 px; // set size
Item.StylesData[depth] := 32 bit;
Item.StylesData[visible] := true; // set Checkbox value

// set OnChange value
Item.StylesData[visible.OnChange] :=
TValue.From<TNotifyEvent>(DoVisibleChange);

// set OnClick value
Item.StylesData[info.OnClick] := TValue.From<TNotifyEvent>(DoInfoClick);
end;

Also note how easy it is, to assign an event handler to the entry:

Item.StylesData[visible.OnChange] := TValue.From<TNotifyEvent>(DoVisibleChange);

It is therefore very flexible, it might even be possible to assign to those entries, different
event handlers, depending on the content or meaning.

You will understand the whole thing right, when you view the properties that are in the
TStyleBook used. To do this, double-click the TStyleBook component to take a closer
look to the properties. In the structure view, you can select the individual elements:

However, there is also another option, to draw the listbox element (text color and
background) . Check out Hint 15 here in the book OnDrawItem event of the ListBox
from VCL replace with OnPainting event of the TListBoxItems

All Components (except the form)


While the TForm contains properties such as Left and Top, all other components does
not have that. Rather, here using 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 has
the type extended.

Several Components (Properties with additional type-qualifying)


In many components you can use known properties as in the VCL components in the
Objectinspector. 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 youre going to make you supposedly 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 if you know this once, it is OK.


However, theres 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 (without ImageIndex)


You do not need to search for the property ImageIndex, which does not exist here. But
you can use the property Bitmap (in the VCL component it is Glyph) to assign a
image. It should also be noted here that in FireMonkey component IsChecked is used
instead if Checked.

TMainMenu (Handling MAC Menus)


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

In the Windows version you have to select TEditor invisible, in the MAC version, you
make it visible and hide Exit from File-menu. Here 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 on the Mac:

This solution you can use, if you only have one master view and no specific view for the
MAC platform. However, if you have a specific form for the Mac, you can also direct edit
the properties of the menu by the Objectinspector. In the OS X Desktop view you
change you set the text for the TEditor-menu to visible and hide the Exit-entry under
file-menu, because the exit-command normally is in the system-menu under MAC. Dont
forget, that the name of the first visible menu item is replaced with MAC OS always by
the program name (executable). 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), whether the memo has
been altered or not and then, for example, reset it, for example, if you have saved the
contents of the memo on the hard disk.

Clipboard.HasFormat(CF_Text) -replacement
Under the VCL there is the Clipboard, under FireMonkey not. Although for the memo
component are existing 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 since XE5 with the ability to make a spell check for the
text has been entered.

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


You enable or disable the spell checker just with the boolean variable CheckSpelling
(either in the Object Inspector or at runtime). For the spellchecking a Platform service is
used. Thus, 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. As a template, you can take the implementation for the
MAC platform and rewrite it accordingly for Windows. Well, the dictionary function as
such, one must of course also create, but there are also several solutions on the market.

FindNext-Replacement
Unfortunately for the FireMonkey Memo component we have not the most useful function
Memo.FindNext (Memo.text, StartPos, EndPos, Search Options) available, as it exists
in the VCL. Even in the standard actions, there is no corresponding action. So you have to
use a own solution. Subsequently, I demonstrate a simple approach, which I have used in
my program TEditor for the Mac and Windows (http://www.hastasoft.de/TEditor.htm).
Here is a simplified version of the TEditor program for the FindReplaceDemo:

When you click on the search icon (magnifying glass), the following routine is 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 run. If you want to search after that directly 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 its 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 is
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 a typical cliff by the way once again. It takes some time, until I find out that I have
to use TInsertOption as a qualifier. Anyway, you can download the simplified
FindReplaceDemo (which then also contains the source code for the click on the button
Replace All) from my homepage. In the routine, you will then find 3 positions where
Application.ProcessMessages is executed. Sometimes this function is needed, when any
interactions has to take place with the host system, e.g. to set the position of the cursor.

Heres 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 is under FireMonkey unfortunately a bit more complicated. While under
the VCL component functions such as Canvas.textout (x, y, Text), etc. were available
and have functions that cause an automatic line break for longer texts, here 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 a
factor of 10, which is taken into account in the calculation with corresponding IFDEFs.
So here is a simple example that works on both Windows and the Mac (the unit
FMX.Printer 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)


Who has been programmed with Windows and the VCL components may know that one
had to activate drag and drop with the Windows Explorer in the application only once by
calling the function DragAcceptFiles. 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.

So here we take a look at the supplied ControlsDemo, I mentioned before. Generally, it


is thought, that one uses the TDropTarget component in its application, 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). So at runtime, move the rectangle or the circle on the drop target
icon and release the mouse button. In the edit box is then displayed as a result the class
name of the dragged element.

Lets see in the DragOver event of the DropTarget inside:



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, that it is not allowed. In addition it exists
yet Move and Copy. A special visual feedback you have (under Windows) only when
you drag a file from Explorer onto the target:

For Mac OS X, an appropriate icon appears even when you drag the components inside
the form.

By the way, does the demo works only since XE7, in all previous versions of the demo,
the property HitTest of a parent component was disallowed, so no evaluation of the
corresponding mouse actions was processed.

When the property Filter is empty, all dragged files are accepted. Do you want, that only
certain file types can be dragged on the drop target, enter an appropriate filter. So you
change the setting of the filter to * .txt in Windows or MAC, if only text files shall be
allowed for drag & drop actions.

But do not use the pipe symbol to separate groups, use instead only the semicolon:
*.EXE;*.DOC;*.PAS.

Note: Currently (03/10/2015) the filter does not work, due to a bug in Delphi. I have made
here a entry for Quality Central, I hope there will soon be a BugFix.

The arrow is animated and it appears the symbol Link (or Copy or Move), which
means, that the DropTarget will accept the files. Now the question is, how to evaluate it,
when the user released the mouse button. Unfortuanally for the demo was not created a

procedure that shows the evaluation of the DragDrop event once. I hereby deliver it:

procedure TfrmCtrlsDemo.DropTarget1DragDrop(Sender: TObject;
const Data: TDragObject; const Point: TPointF);
var
I: Integer;
begin
for I := Low(Data.Files) to High(Data.Files) do
ShowMessage (Data.Files[i]);
end;

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

Unfortunately I cannot copy the link arrow that appears similar to Windows, with the
MAC screenshot program. It can be seen here only weak the name of the dragged file.

Here again, the file name is displayed when you have dropped the files on the DropTarget.

Of course you can also drag and drop some files on a normal edit box. To see how to do
this, look at tip R31 below.

So, now nothing should prevent you, to use drag and drop in your applications.

TRichEdit (Not availalbe - but replacement via 3rd-party possible)


A RichEdit component is for FireMonkey not yet available, you have to use a component
from third parties. My great hope is here, that the TRichView components used by me
(with that is also written my word processor program Tipptext) will soon be available
for FireMonkey. According to the manufacturer (with whom I am in e-mail contact for
years), it is planned to implement, but it is be not soon. Update: (03/10/2015) The
manufacturer has not yet started to develop a FireMonkey implementation. It might be
worthwhile, therefore, to look around for alternatives.

And in fact, there is recent a useful RichEdit component from TMS available, the
TAdvRichEditor: http://www.tmssoftware.com/site/advricheditor.asp.

I deposited a short video on Youtube:


https://www.youtube.com/watch?v=_BjlRX_CjX4
Recently, there is even a spell checker offered, what is really helpful.

TPageControl (Not available - but replacement available)


This under Windows so extraordinarily useful component cannot be found at FireMonkey,
however, the TTabControl component provides a reasonable compensation (see below).

TStringGrid (works different)


This component is also very often used in VCL programs. Like the FireMonkey listbox
also the FireMonkey StringGrid does not have an OwnerDraw event (but an OnPaintingevent, later more on that).

When you insert a new StringGrid in the form, you will get this situation:

As you can see, the grid element contains no columns. You will also in the Objectinspector
not find a property Columns.

You can add columns, by clicking on the StrinGrid-component with the right mouse
button and from the context menu, select either the entry Items Editor or Add
TStringColumn command.

How you can see below, we have add 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. The header you may show or hide with the property ShowHeader.

The property RowCount exists also in the FireMonkey StringGrid. By default, a grid has
initially 100 lines. Therefore, the assignments here in the FormCreate shown, are
working correct:

Assignments made, without adding Columns before, would not work. The columns can of
course also be generated only for the duration of the program, if necessary.

But how can the font display (e.g. bold, font, fontcolor) be changed?

The StringColumn component has not a font-property, but the StringGrid-component has
one under Textsettings. But all the settings you can make here, are valid for the whole
grid. If you want it different for one or more 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:


So as requested, the text output is in bold and in red text color. One note: there is a
separate event for drawing on the column header: OnDrawColumnHeader.

You can then draw the text of the column left or right, or centered, depending on the
program logic. And you set this by using the different TTextAlign parameters, eg
TTextAlign.taCenter for centered text.

TGrid (Image and other elements in the Grid)


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

Click with the right mouse button on the TGrid and select the required entry:

In XE7 (and already in XE6) is new here the TDateColumn and TTimeColumn. The
remarkable thing is, when you edit these fields, specific editors are offered therefore (a
TDateEdit and a TTimeEdit).

To display text or images, you do not use OnApplyStyleLookUp event but the
GridGetvalue event.

The first picture shows the form in design state and the second at runtime:

Here you can see the source code 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;

Depending on the program-logic you need, you can show images, text or checkboxes. So
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 will gladly refer you to an alternative that
could make the the transition from the VCL StringGrid to a FireMonkey Grid more
easyly: The TMSFMXGrid which, in addition to the usual VCL properties (ColCount
DefaultRowHeight, etc.) has many other skills (such as export to an Excel file), you
usually can hardly find elsewhere anyway. For more info:

http://www.tmssoftware.com/site/tmsfmxpack.asp.

There are provided a number of demos that you will find after installing the gridcomponent under

C:\Users\Public\Documents\tmssoftware\TMSFMXPack Demos

With the export to Excel, you can, for example, look how this works with the XLSIO
demo under

C:\Users\Public\Documents\tmssoftware\TMSFMXPack Demos\TMSFMXGrid Feature


Demos\XLSIO

The Excel-file generated by the TMSFMXGrid:

I can open exactly in my spreadsheet program (PC-Spreadsheet - and of course in Excel


itself):

To address the capabilities of the Grid more in detail here, is beyond the scope of the
book. But thats also not necessary, because in the Windows program group you will find a
link to a detailed PDF documentation of the grid (over 130 pages):

THeader (no sections, but items)


The component here also exists. In the VCL we have sections, here in FireMonkey we
have Items instead, which are THeaderItems.

THeaderControl (is not available under FireMonkey)


This component does not exist under FireMonkey. Use THeader therefore. Since the
individual THeader items also works as a container, you can easily insert here required
elements, such as checkboxes or bitmaps.

TProgressBar (not position but value)


This component works in essential like the VCL component, but you do not use in
FireMonkey components the property Position, but Value. Position is, after all, used
with Position.x and Position.y to set the horizontal and vertical offset of the component.

TTabControl (no Ownerdraw)


This component works much like the VCL version. So this means that you generate
individual TTabItem elements and then insert here your components. You can TTabItems
of course first create at runtime and then paste into the TTabControl.

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

TTrackbar (helpful property tracking)


The FireMonkey component contains, in contrast to the VCL component, the property
Tracking. If you set this to False, this means, that the OnChange event is only
triggered when the move of the Trackbutton has been completed. This can be especially
helpful, if even the slightest change in position is associated with a program task that
triggers a very intensive process (e.g. complex graphics routines). Then the event is fired
only just when the moving of the trackbutton is done (the user has released the mouse
button).

TSpeedButton (without Bitmap)


While in the VCL component with the property Glyph of the speed button, an image can
be assigned, this is under FireMonkey unfortunately not possible. But the FireMonkey
button works as a kind of container, you can insert a TImage component, and then use this
as button image.

To make the job easier, you can create a component template from the FireMonkey
components. So select the button, which includes the relevant items and choose from the
menu Component the command Create Component Template. In the dialog that
appears, enter then an appropriate name and click OK.

As of now, the new component is available on the Tool Palette. In generating the template,
you should ensure, that the individual elements are not yet connected to event handlers.
For even the source code would otherwise copied (which might be useful only in special
cases).

TStatusbar (a way to compensate the missing Panels)


In the VCL status bar you can display a text either on the property Panels or via the
SimpleText property. In the FireMonkey status bar there is nothing of the sort, no text
property. So you could use the status bar as container and there, for example, insert Labels
.

Better solution: Just take a TGrid! As you can see in the screenshot shown below, I have
included in the StatusBar a TGrid. In the TGrid I have 2 TStringColumns, a
TImageColumn, again a TStringColumn and a TProgressColumn added.

In the Objectinspector, I have set for the TGrid:




ShowHeader = false

Canfocus = false
HitTest = false
ShowHorzLines = false
ShowScrollbars = false
ShowSelectedCells = false
DisableMouseWheel = true

Furthermore, one must have a user defined style for the grid to create and change the
settings there under content for focus and selection for the FillColor, otherwise the
grid will appear in a sort of bluish color:

At runtime you can then set the required values for the corresponding TColumns in the
OnGetValue event of the TGrid. In the following example, we call the function
Grid.Repaint in a timer (call every 500 ms). This causes then, that the OnGetValue
event is triggered where you can do the following:


procedure TF_Main.gStatusGetValue(Sender: TObject; const Col, Row: Integer;
var Value: TValue);
var
aCol: TColumn;
begin
aCol := gStatus.ColumnByIndex(Col);

if aCol = StrPos then begin
Value := Row, Col: + IntToStr (Memo1.CaretPosition.Line+1) + ,
+IntToStr (Memo1.CaretPosition.pos+1);
end;

if aCol = strSize then begin
value := Len: + intToStr (length (memo1.text));
end;

if aCol = ImgInfo then begin
if length (memo1.lines.text) > 10 then begin
Value := Image1.Bitmap;
end else begin
Value := NIL; //ImgINfo.Width := 0;
end;
end;

if ACol = progressInfo then begin
VAlue := Memo1.PosToTextPos (Memo1.CaretPosition);
end;
end;

At run time, it looks like this:


In the example above, the row, the column and the length of the text are displayed in the
status bar. If the text is longer than 10 characters, the yellow warning-image appears. The
green progress bar shows always the total length of the text.

Note: This demo is used only to demonstrate the basic operations. Whether this could
really be a useful solution for you, will depend on your needs.

The Statusbar with TGrid solution is a little effort, but it is much easier to hide a
TColumn, instead of working with a group of TLabels. Also it is an easy way to show
images, process indicators or text.

The program can be downloaded as a demo, under


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

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 worked here under Windows:



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

does not work with FireMonkey so.


Here it should look as follows:



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

So here too, the qualifiers are again required


I have some time needed to figure out how it works. This should be spared to you with this
information.

Section 4: The FireMonkey Style-Designer


a) Using the Styles 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 a component or
select Edit Default Style, if you want to change the default for all components of this
type in the current project.

You will then see the following screen:


On the left side, in the tree view, you can select the individual styles and assign
appropriate values in the Objectinspector. Over the treeview 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 for the work with FireMonkey a very great importance,
since this way is often used to achieve the same optical results that could be achieved in
the VCL components using the OwnerDraw events otherwise.

When you have called the style editor under Delphi XE2, the control was then selected in
the treeview and in the middle of the IDE-window the control itself was drawn.
Unfortunately that is since XE3 not more so. On the left hand side, only the structure list is
shown and no item is selected. Accordingly, the style element is not displayed in the
middle of the window. If the structure list is slightly longer, its not so easy to find the
desired item quickly. Many user understands not at all what they can do here at this place.

A little help you can get with the using of DDevExtensions by Andreas Hausladen. This is
a free Delphi extension that integrate in Delphi a Structure View Search:

Over the treeview a searchbox is installed, where you can search for the name of the
element. So if you are searching for listboxItem7style1 in the search line, it is sufficient
when a part term 7style1 is entered and you get directly the desired element. By clicking
the entry with the mouse or pressing enter, the item will be selected in the treelist and the
corresponding object is displayed in the center of the window

Click here to download the DDevExtensions.:


http://andy.jgknet.de/blog/ide-tools/ddevextensions/

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 also known in the VCL. Since XE2 in a VCL application, there is
under Project, options, application appearance, the ability to select a particular style for
Windows application. With the following XE versions the thing so far has been refined on
VCL side, because each control has received the property Style Elements. This allows

you to specify how to display elements (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 FireMonkey: StyledSettings

FireMonkey and VCL have so far moved closer by the time, that since XE3 the BitmapStyle Designer is available to edit the VCL styles and you can save these VCL-styles as
FireMonkey Styles, too.

Lets 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 XE7 e.g. under

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


These are text files, which you can load into a text editor. Load it once, to 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 runtime.

In addition, the standard styles are integrated alongside already fixed in Delphi. Even if
you put no stylebook on your form, there is already a standard style, which always is used
when you dont load another style. This standard style then ensures that your program
looks as a typical Windows program under Windows and under the Mac as a typical MAC
OS X program.

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

Well, before I answer that, let me remark that here is shown the high dynamics in the
development of the FireMonkey framework. In XE2 to XE4 there was always another
technical solution for that. Ill spare you the details (refer if necessary on my books to the
corresponding versions) and show here only as it proceeds under XE7:

The background of TButtons 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, then you can find in the Objectinspector the items NormalLink,
Focusedlink, HotLink and PressedLink that all as TBitmaplinks are defined:

If you click on such a TBitmapLink, you will get the following view. The references to
the style bitmap, you will find under SourceRect:

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

As mentioned earlier, in the styles for XE2 it has no bitmap-links. It starts with XE3. This
makes it possible to draw the representation of the components more quickly because it is
easier to use a ready bitmap for the drawing, as to draw a compicate appeareance at run
time.

You can easily create your own bitmap style by saving the Windows7Style bitmap (or
the Windows 8 desktopstyle.png as a file, change the color balance in a graphics editor
and then upload the modified file back with the bitmap dialog.

You will get access to the bitmap over the property MultiResBitmap. Here in the
Objectinspector, click on the button with the 3 points.


The following dialog appears:


Click once on the bitmap that is shown in the dialog above. Here you have then again a
property Bitmap in the Objectinspector. Click also here on the button with the three
points, the Bitmap Editor will be shown, where you can load and save the image:

On YouTube you will find also a short video for it (German language):
https://www.youtube.com/watch?v=gcMPZQiVR-w

Here I have done this once with my program PixPower Photo & Draw 6.x (command
Special colors):

The trick here is to save the bitmap as a PNG file and then load it gain as a PNG file into
the Bitmap Editor dialog of Delphi, otherwise it will not work. With the edited bitmap
here, buttons are under Windows then e.g. displayed in a light blue, and selected or
focused with a slight orange (you may need to close the form and reopen it again for the
changes to be applied).

Knowledge of the standard styles is important if you want to create a custom style for a
component for example, a TButton.

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

the following structure presented the in the treeview:


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

Another note about the MetroplisXXX.Styles. The Metropolis-style files also contain a
style bitmap. However, this is with approximately 935 pixels x 907 pixels significantly
larger than the bitmap Windows7Style (400 x 400 pixels). For the Blue-style it looks like
this:

c) Convert VCL Styles to FireMonkey Styles


Very useful, that you can convert VCL Styles in FireMonkey styles, and use it in
FireMonkey applications.

Proceed as follows: In Delphi, you call in the Tools menu the command Bitmap Style
Designer. Then open e.g. under

C:\Program Files (x86)\Embarcadero\Studio\15.0\Redist\styles\vcl


Luna-Style (Luna.vsf).

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

Save the file to a folder of your choice (preferably in one that is 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 does your application now come to the style you want? There are several
possibilities. Either you load one of the supplied style files 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 the wanted at
run time into the StyleBook. Thus, e.g. the user can select a style that he likes.

You can do this, for example, like this (the path must of course be adapted to your
program environment):

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

If you want to assign the style to the entire application, so that multiple forms that are
generated at run time later has a consistent style, using for this purpose the
TStyleManager, eg at the create event of the main form:

TStyleManager.SetStyleFromFile (D:\LunaStyle.style);

For getting this to work, you must include the unit FMX.Styles in your units list.





Section 5: Convert VCL programs into FireMonkey


programs

If you have developed a Windows VCL program with perhaps more than 100,000 lines of
code that has even over several dozen forms, the question arises how to change all this to
the FireMonkey platform.

One possibility would be to start from scratch again, so designing new forms, use copy
and paste and code as long as its going to take. I tell you, forget about this. With a small
application, one could do it this way. But what really needs time, is to designing forms,
enter text and graphics, do the assignments of event handlers, and so on. A program that
you have developed over the years, you do not want to develop again, even if it might take
a year only.

So you need to work with solutions that assist you in converting a VCL program into a
FireMonkey program.

a) Working with the Mida Basic Edition


On acquisition of Delphi XE7 you can download from the Embarcadero Developer
Network Mida the converter.

After running the setup-program, in the in the Delphi menu Tools you will find the new
command Mida - VCL to FireMonkey.

The work with it is very simple:


With Source Directory, you select your VCL-project directory and with Destination
directory the place where the program will be stored as FireMonkey version. After hat,
click on Convert Project.

The Register Custom Component is in the Basic version without function, what its all
about, I will report later in my remarks to the Pro / Studio version. The same applies to the
tab Options, where you can disable or enable only the use of LiveBindings Basic.

The blue bar indicates how much VCL components can be identify by the program. So if
you click on the button Convert Project, a red progress-bar appears during the
conversion process in addition to the blue bar. This reflects how much VCL components
could be converted into FireMonkey components.

You can now go to Delphi to the destination directory and open the FMX-project file. For
a more extensive project normally a variety of messages will appear stating that certain
properties of components are missing.

You need to click ignore it always.


The project is then normally not directly compileable and executable. Here is a small
example:

So here we have a TButton, a TBitBtn, a TSpeedButton. The first three buttons are
associated with an event handler that shows in the onclick-event, which class the button
has.

The last button is associated with a SchowMessage procedure.


Besides, there is a TImage, which contains a TBitmap.


The source code for this is as follows:



procedure TForm5.BitBtn1Click(Sender: TObject);
begin
MessageDlg (Clicked: + Sender.ClassName, mtInformation, [mbOK],0);
end;

procedure TForm5.Button2Click(Sender: TObject);
begin
ShowMessage (This is a Test.);
end;

After conversion of the dialog, it looks like below:


The graphics in TSpeedButton is missing. OK, in FireMonkey the TSpeedButton


has indeed no graphics.

The TBitBtn missing graphics and text. OK, there are no TBitBtn in FireMonkey,
so thats the reason why it was converted to a TButton.

The TImage has no bitmap.


If we want to compile the FireMonkey project, we get the following message:


These are typical cases where the type qualifiers are required, which I had earlier been
addressed. If we adjust it as follows, it runs:

procedure TForm5.BitBtn1Click(Sender: TObject);
begin
MessageDlg(Geklickt: + Sender.ClassName,
TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK],0);
end;

Very good, it runs on Windows now. But we want to use FireMonkey to develop multiplatform projects.

However, we can add on the right side of the Project window only MAC OS as the target
(by right-clicking target platforms).

Then you can do the following: Let display the source code of the program (Project menu)
and copy all the text to the clipboard. Then close the project. After that, create a new
FireMonkey project under the File menu, choose here the command New, Multi
Device Application. When the new project will be shown, remove directly the default
main form with no saving and then save the project under the name that you had converted
before, in other words, overwrite it. Then get back to with Project, View Source to the

program source code and paste the text from the clipboard.

Now when you add everything and save as previously described above, the Mac OS X
platform, works as desired.

Here is the program running on the Mac:


By the way, there are still many other aspects of shifting notes, if you work primarily with
a real, comprehensive project. All this I will describe here in the same section later in c),
first Im still on the Mida Pro / Studio version, which can be purchased as an upgrade.

b) Working with the Mida Pro / Studio Version


If you have purchased the Pro or Studio version and enter your data in License-Field,
the program is fully available.

But perhaps a few words on how to install the program. It has no separate setup procedure,
you will receive a zip file that you must unzip it into a directory of your choice:

Mida.exe is the actual program file.


In contrast to the basic version it runs non-modal, so you can the dialog leave open, while
you are still making adjustments or changes with Delphi.

What makes Mida_FM_addon.exe, I am not entirely clear.


To install the MidaImageButtons and MidaSpeedbuttons I have a project in DCU_Win_32


bit opened, compiled and installed the components. Tries with the same project in
DCU_MAC, did not work, it appears as .dlyb and the install entry in the Project menu is
missing. But is apparently not required to work at all. However, you must set both in the
FireMonkey and Windows platform as well the search paths to these directories, so each
on DCU_Win_32bit and DCU_MAC_32Bit.

With the Pro / Studio version youll have access to Options and so more opportunities to
influence the conversion process:

Here, especially lookt at the options Update Source not compatible with Fire Monkey
and Convert TImage.

So when we compile the same project as above (and earlier do not have the Mida pack
components installed), the result is as follows:

As you can see, the TSpeedButton has now an image - that is because it was converted
to the TImageButton, a Mida component.

Also the bitmap is now available in the TImage component.

If our buttons would have been bold, an appropriate style would have been created in this
stylebook.

In the source code itself has more done, in comparison to the Mida Basic conversion:

procedure TForm5.BitBtn1Click(Sender: TObject);
begin
MessageDlg (Geklickt: + Sender.ClassName, TMsgDlgType.mtInformation,
[TMsgDlgBtn.mbOk],0);
end;

Here, the TMsgDlgBtn before mbOK and TMsgDlgType before mtInformation


was inserted by the converter.

Another important support for the conversion in the Pro / Studio version can be found
under the Custom Component. Here you have the possibility to make a custom
conversion with own components or those of third party not supported yet (the screenshot
is slightly modified in order to save space):

You can enter the name of the VCL component in the upper window and the name of the
FireMonkey component which is to be used for the conversion.

In the lower window, you can specify again the VCL component and then the name of the
VCL property and the name of the FireMonkey property.

Conclusion: Even the basic version is a big help. With their help, I managed to convert a
simple calculator project within a few hours into a FireMonkey project, a few days later,
the program was already available in the Mac App Store!

The Pro and Studio version offer even much more than the basic version.

In particular, the acquisition of images, and the adaptation of the source code are another
essential assistance if you need to convert larger projects. The many hours of time saved
justify in any cases, the investment in the Pro or the Studio version. The studio version
offers besides the conversion of reports also the Mida Component Pack (currently
MidaImageButton and MidaSpeedbutton).

In fact, the program is essential for the migration of larger VCL program projects into a
FireMonkey Cross Compile project.

c) Strategic approach to working with the Mida Converter


In large projects, you must first make a series of preparatory work, before you start the
conversion. As an example: make a copy of your project, perform a conversion with the
Mida-Converter and then open the converted project. There are probably a lot of messages
about missing features and components.

Example:

The slightly older TNoteBook component is not supported by the converter. So before you
start the conversion, you must transfer manually in your VCL program the contents from
the TNoteBook into a TPageControl.

You can then view the individual forms once and check out what is missing or what not.

So you need to modify in many cases your existing VCL project previously of the
conversion of the VCL program. Here the VCL form:

And here the converted FireMonkey Form:


Currently - or rather still, a year ago it was already so - TTreeView components will only
be converted without text content.

When the TTreeView consisted of only one level, you can replace it in the VCL program
through a TListBox component and provide those with the required text entries. The
TNoteBook you can replace with a TPageControl. You can give the TabItems of the
TPageControls without problems the same name as the Tabs of the TNoteBook. So it
supports then furthermore the program logic. The Mida Converter program converts the
TPageControl then into a TTabControl. You can in TTabControl set the property
TabPosition to TpNone to hide the names of the registers and so the TTabControl
works like the TNoteBook (only more comfortable).

By the way, again the note: The color design of the label texts (bluish shown here) is
adopted using the stylebook component only from Mida Converter Pro version. In the
Basic version would all these texts be in black.

For processing in detail:


Only Forms and units that are located in the project directory will be covered

by the converter. So if you have so-called standard forms that you use in multiple
projects and these are located in a separate directory, you must copy them into the
project directory. It is best way that you click in the Project Group Window to such
a standard form with the right mouse button and choosing save as. That leaves
your original form at the original place, but makes a copy to the project directory
and place a valid link in the Project Manager to the correct location.
Often VCL TPanels are used to display a text with the caption property in a

rectangle. But in FireMonkey the TPanel has neither a caption, nor a text property,
the text content therefore is lost with the conversion(!). Therefore, you should
previously insert a TLabel component in the VCL program into the TPanel
component and apply the text of the TPanel caption property to the caption property
of the TLabel.
Unfortunately, the Mida Converter converts always entire projects. So, if you

converted the project again and have already worked with the previously converted
forms, it would overwrite this form with a renewed conversion and your previous
changes would be lost. Either therefore convert each time in a different destination
directory and copy needed forms in your project directory. Or convert any time into
the same target directory, but first save the already converted forms in an OK
directory (with the command Save As) and leave it there and continue to work
with this (so I do it). But do not forget to specify the .dpr or .dproj files because
they contain other important settings that you do not want to do edit each time. You
should copy it to the OK-directoy also and use it from this place.
Start the conversion with units or forms that do not require additional units or

other forms. For example, an Info about dialog or a Registration dialog. So if


you have completed all the preparatory work on the VCL project, go ahead and
make the first conversion. After that, remove from the FireMonkey project all
forms. You can use Remove from Project under the Project menu, where you
can select all forms. Then just add a form and work on it, until the program is
compiled and executed with this form (eg, the About dialog). Then add another
form to your project, set it as the main form and work on it, until the form can be
compiled and the program is executed.
Even at this early stage, you should relatively quickly add a MAC OS X

platform and let the program run on times even on the MAC. So you see quickly,
where specific adjustments for the Mac must be made and you can react in time.
Otherwise, it may happen that your program runs under Windows fine, but under
the MAC platform it hails error messages without end.
Take into account the MAC peculiarities, when designing your dialogs. For

example, buttons that are located at the bottom of the window, the OK button is
displayed on the right side in MAC dialogs, but in Windows dialogs at the left. So
the best would be, you are using a specific MAC-View for this.
Backup, backup !! Very fast any problems occurred, e.g. a form will be

destroyed and is no longer usable, whatever. Therefore, you should make adequate

backups, so you have access to previous versions and you can continue your work
from that.
Depending on how easy or difficult it your source code can be put into

FireMonkey logic, it can be useful to enable or disable a number of functions or


event handlers with IFDEF declarations. You will then have the whole source code
in your form and procedure by procedure you can then work through and make the
necessary adjustments. Advantage is here, that the program is compiling and you
have a base, you can add things one by one. After all, you will have even more fun
to visually see how the conversion progress goes on with a running program. And
so you can have an early look of your program at the Mac platform.
If you have also the TMSFMXGrid-component available, you should consider

to let the converter convert your VCL-Grid into a TMSFMXGrid and not into a
FireMonkey TStringGrid (see the appropriate setting under Options). This is
because the TMS Grid is the VCL StringGrid in many functions more similarly
than the FireMonkey TStringGrid and also offers many other skills. Tip: You may
need to specify the conversion of TStringGrid to TTMSFMXGrid under the register
custom component explicitly in Mida converter, only activating the checkbox
TMS TTMSFMXGrid on the Options page has had in the Mida version
available to me, no effect.
TShapes are not ok, according to my experience, so you should replace it with

a TPanel before conversion.


If updates of the Mida Converter will be available, read exactly what has

changed. Possibly the next version resolves an existing problem or has abilities that
reduce the necessary preparatory work.

If you follow these aspects, you will make it safe to convert step by step your VCL
program into a FireMonkey program and be able to offer your program for the MAC OS
X platform simultaneously.

Meanwhile, I have converted a medium-sized project (Copy Backup with no fewer than
50,000 lines of code: http://www.hastasoft.de/CopyBack-info.htm) from VCL to
FireMonkey: Within two weeks it was finished and a week later the MAC version was
ready (which requires a little more time because of some special features of the MAC-file
system).

The second, a larger project (PC-Rechnung: http://www.pc-rechnung.de) with over


100,000 lines then went again much easier. If you have once make your experiences, than
you know what to look for, and you can relatively quickly go ahead with the conversion.


Chapter 2: Tips and tricks for CrossPlatform Development



Section 1: starting other programs


Depending on whether you want to start another program on Windows or Mac (or want to
open a file with the default viewer) for each systems other routines are needed. This is
achieved with the use of different units and the use of IFDEF directives.

In the Uses clause, use the following:



{$IFDEF 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);
{$ELSE}
_system(PAnsiChar (open + AnsiString (prog)));
{$ENDIF}
end;

If you need to find out, were functions and procedures for the Mac are located, look into
the corresponding files in the following directories:

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


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

Section 2: Get the program directory and program data


directory

The program directory of the executable file you can find out both under Windows and the
Mac using the command ParamStr (0). In fact, the whole name of the executable file is
stored here, but so its easy to identify the program directory.

It is best to use a unit, in which you set up the corresponding variables in the initialization
section. Heres an example:

unit MyData;
interface

uses
{$IFDEF MACOS}
POSIX.Stdlib,
{$ENDIF}

{$IFDEF MSWINDOWS}
ShellApi,
{$ENDIF}

FMX.Forms;

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)), ); // without ext.

{$IFDEF 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;

Here please replace kindly Hastasoft with your own company name or leave this part
completely free. This supplement is especially recommended if you have developed many
programs and 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 (eg File
IO for MAC) show these folders anyway.

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

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

Section 3: Catch to the program passed start-up parameters


What under Windows could be done very easy with StartParam = ParamStr (1), will be
conducted in MAC OSX considerably more difficult, as there it works quite differently. If
you, for example, in the Finder click with the right mouse button on a file and from the
menu choose Open with, the system starts the program and then sends 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 cant compile


// from Macapi.AppKit
NSApplicationDelegateExtended = interface(NSApplicationDelegate)
// hier in eigenem Programm mit Strg + Umsch + G einen eigenen GUID erzeugen
// verwenden Sie NICHT den untenstehenden
[{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, then the following must 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 a bit complicated, but it works.


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

Here an example of an addition to the info.plist file, giving the MAC-OS the information,
that your program can open text files and .xml, and also 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 for your program name an entry in the Open
With menu. But that does not prevent other programs as the Finder to transmit
parameters to the program for opening files, so can my File Manager File IO for MAC
start every program and give at the same time a startup parameter to the program (this is
managed by the MAC system managed in the background).

Section 4: Hello World - Multilingual programs and new


markets

The Apple App Store is ideal to sell your programs internationally and it can thus open up
entirely new markets. For this purpose, however, 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 helps us FireMonkey in a very simple way with the TLang component,
of course, in a cross-platform way. If you have developed your application, add to the
main form a TLang-component.

Example:

Double clicking on this TLang component, it opens the following dialog:


You might be surprised that even the entry ffne mich Mac is offered. This source is
from the view OS X Desktop. But it will only show up, when I have here again clicked
on the buttonScan for strings. That way, you must always do, when you add new
controls to the form and want to edit the texts in the language designer.

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

The dialog then changes its appearance and behind the recognized text is now for each
entry an input field available, where you can enter the English equivalents:

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

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

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

For this demo here 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 uppercase, as shown above.

The form looks after program start like this:


What you can see here, is that the text of the form (caption) is not compiled from 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
// Sprache vorgeben
Lang1.Lang := EN;

// Je nach Sprache:
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.). It is to be hoped that the menu items are covered by this component in a
subsequent version.

And think on it, that you have also to make a difference in dialogs, when you program
runs in English or other language mode.

If a component should not be translated, you can disable the feature AutoTranslate in
the Objectinspector.

Do you have multiple forms in the project, these are 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.

But in any at runtime newly created forms, 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 at runtime
between different languages. But you can only change from the original language to other
languages. To the original language you can only come back when you restart the program
and use the original language as default. OR you specify a new language in addition to the
original language, ie Add Language here with the switch de (when German is the
default language). Since it obviously makes no sense for it to provide a translation, it
simply restored the original entries. In this case, you can also switch back so at runtime to
the original language.

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

WARNING: If you have already translated texts and then uncheck StoreInForm in the
Objectinspector:

You lost 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, it automatically receives the file extension
.lng. At runtime, you can then load the file with Lang.LoadfromFile (file name) into
the component.

Here at the demo everything works the way it should. In a larger application that I have
provided here 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 txtfile template) and then reloaded again and again for revisions. You 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 into the App Store, it must support the sandboxing
model. From July 01, 2012 on, this is the duty. Therefore, first a few words about the
sandboxing model. To understand it, it is be shown 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 for some actions your program must be run with
administrator rights). You may obtain access to the documents folder or the images or
music folder so at any time. Also, your application could write to the Application Bundles
of other applications, which so far is a target for the security of the operating system here.

If your application only plays in the sandbox, it does not realize what else run around it,
or does not have access to data that are outside the sandbox. There may be applications

where it is enough to play in the sandbox. But often it is so, that you want give the
working results of your application to other applications ore want get access to data-output
from other applications.

Here, the Entitlements come into play. So, 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 Project, Options, Entitlement List
(here again to note that this information is only displayed if you have MAC OS X set as
the target platform):

What most applications will probably need is the right to read and save files, over which
one can take access with the dialog Open and Save. This is also a setting where Apple
it lets you go through 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 it specify exactly in iTunes Connect, why you need these
rights. If you dont do this, your will have a good chance, that your application will be
rejected (already happened to me).

When you click in iTunes Connect on the page of your application on the View Details,
button, 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 require 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.

One thing 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, into the desktop-folder.

However, you must manually edit the Entitlements list. It is best to go ahead so that you
are editing this list right in the application bundle. But you have to disable in Delphi in the
Deployment the transmitting of the Entitlements list to the MAC computer since Delphi
would otherwise overwrite your manual entries again.

However, it is so that an already on the target existing file is deleted if it is not to be


transferred. So we have to outsmart the deployment manager here. Ive solved it so that
I have created a separate Entitlements file and its own Info.plist file. These are then
transmitted instead of the files that Delphi managed. You have to maintain the files

yourself manually, but this is not a problem to do so. This workflow is also required in
some other cases, when you have to enter multiline entries (<ARRAY>) into the Info.plist
file (which Delphi cant manage). The following screenshot shows it once. The yellow, by
Delphi created files are disabled and my files marked in green, are transmitted:


Here is a concrete example of an extension of the list:

<?xml version=1.0 encoding=UTF-8?>
<!DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version=1.0>
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.temporary-exception.files.home-relative path.read-write</key>
<array>
<string>/Desktop/</string>
</array>
</dict>
</plist>

The relevant entry is in this case the key:


com.apple.security.temporary-exception.files.home-relative-path.read-write

Here you can enter in an array list, for which folders, relative to the home directory of
your application, a direct read and write right will be needed.

The directory must always begin with a / and end with a /.


A general description of the use of entitlements, see the Apple online documentation, here:

http://developer.apple.com/library/mac/#documentation/Miscellaneous/Reference/EntitlementKey

Where in a sandbox-app the data are then actually really?


My multi-application screenshot shows a saved image copy with the following path:

/Users/harrystahl/Library/Containers/com.hastasoft.multiscreenshot/Data/Desktop/Bildkopie1.jpg.

As you can see, here is just a symlink, so an alias that is a reference to the actual location
of the desktop:

How to request further rights for direct access to specific folders, is described here:

http://developer.apple.com/library/mac/#documentation/Miscellaneous/Reference/EntitlementKey
CH5-SW1

Therefore it will even be possible accessing folders with absolute directory information:

<?xml version=1.0 encoding=UTF-8?>
<!DOCTYPE plist PUBLIC -//Apple//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version=1.0>
<dict>

<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<array>
<string>/Volumes/</string>
</array>
</dict>
</plist>

With that you can get directly access the folder Volumes and thus on mounted drives.

And indeed, for this location shows my ScreenShot program as storage location then

/Volumes/D/Bildkopie1.jpg.

So this means that the absolute read and write accesses, is at the real locations and not
only virtual via the data container.

By the way, there will always be an interesting question whether you can actually
convince the Apple reviewers, that your application absolutely requires these rights. An
application should at least, according to the specifications of Apple, request only as much
rights as you need to meet your requirements.

You should therefore give the description of why your application requires these rights
really hard and never make a completely meaningless statement. For my multi-screen-shot
program I had no success with it, so I have therefore changed the strategy and the storage
locations for the program.

When talking about sandboxing I want tell you, that there is until now another unsolved
problem: the Open and Save dialogs unfortunately do not work on MAC OS X Lion. The
application freezes only briefly, nothing happens, that was it.

For Mac Snow Leopard a sandboxed application loaded from the Apple Store works
properly, but not surprising, because Snow Leopard does not support sandboxing at all.

Furthermore, I have the impression that perhaps the Apple reviewer only or mainly always
conduct their tests with the latest Apple operating system, so until recently with MAC OS
X Mountain Lion and now with the new Mavericks (MAC OSX 10.9) and Yosemite
(MAC OS X 10.10). But from my own experience I know that applications can still come

into the store, although the Open and Save dialogs in Lion not work.

As a developer, you can actually not be very enthusiastic about the sandboxing, because it
is very difficult to use and it is not easy to add useful functionalities to your program.

It may therefore be the right decision to develop the application without sandboxing and
then sell it just outside the App Store. Which of course has certain disadvantages, because
the market power that Apple has now with his store, you can normally not concur with.
Unless you are representing yourself as a larger or multinational company.

Section 6: Sandboxing and persistent requests to Bookmarks


The option set out in section 5 is useful, if you want to have from the beginning on a direct
access to given directories.

If it will be clear, that the user needs persistent access to folders and files outside the
sandbox, but not clear, which folders, you can use here bookmarks in connection with
certain entitlement-keys.

There are two types of bookmarks, you can use here:


App-Scoped Bookmarks

com.apple.security.files.bookmarks.app-scope

This gets your application access to the folders and their contents, selected by the user if
the bookmark was created for a folder, or to a single file, when the bookmark was created
for a file.

Document-Scoped Bookmarks

com.apple.security.files.bookmarks.document-scope

Allows you to create a bookmark for one file. While only your own application has access
to the resource with the app-scoped bookmark, with the Document-Scoped Bookmark any
application gets access to the file when they have access to the bookmark (and the file). A
requirement for Document-Scoped bookmark is still, that the file to which the bookmark
references, is not in a folder that is only used by the system, such as private or
Library.

Here the process is described, how you will get a permanent access via app-scoped
bookmarks to a folder (and its contents) that the user has selected and how you can access
them again:

Step 1

The user selects a folder with the MACSelectDirectory function described in Annex 2.
In this moment a function will be called, that Apple describes as the PowerBox
mechanism. Indeed, if the original MAC Open and Save dialogs used - and only then - the
PowerBox function makes that the program during its runtime would continue to have
access to the selected resource (i.e. in this example the folder and its contents).

Step 2
If we want, that the program - even after restarting - has again access to the selected
folder, without re-using the open dialog, we have to use Security Scoped bookmarks
here.

What is extremely important here (without it will not work): You have to create this
bookmark at a certain moment. And this moment is after the user has selected the folder
with the Open dialog (a NSPanel object) and before the panel is released. The operating
system provides the ability to create a Security Scoped Bookmark only in this brief
moment to your application.

Therefore, we cannot use the Open and Save dialog components, that comes with Delphi.
If the execute-function of the open dilaog returns, the NSOpenPanel is released. Although
the PowerBox granted furthermore access at the duration of the runtime of the program,
there is no way to create a Security Scoped Bookmark after closing the dialog.

I have therefore the function, described in Annex 2, here for the sandboxing adjusted
(simplified illustration):

{$IFDEF MACOS}
function MACSelectDirectory(const ATitle: string; var ADir: string; CreateBookMark: Boolean): Boolean;
var
LOpenDir: NSOpenPanel;
LInitialDir: NSURL;
LDlgResult: NSInteger;
Data: NSData;
begin
Result := False;
LOpenDir := TNSOpenPanel.Wrap(TNSOpenPanel.OCClass.openPanel);
LOpenDir.setAllowsMultipleSelection(False);
LOpenDir.setCanChooseFiles(False);
LOpenDir.setCanChooseDirectories(True);
if ADir <> then
begin

LInitialDir := TNSURL.Create;
LInitialDir.initFileURLWithPath(NSSTR(ADir));
LOpenDir.setDirectoryURL(LInitialDir);
end;
if ATitle <> then
LOpenDir.setTitle(NSSTR(ATitle));
LOpenDir.retain;
try
LDlgResult := LOpenDir.runModal;
if LDlgResult = NSOKButton then
begin
ADir := string
(TNSUrl.Wrap(LOpenDir.URLs.objectAtIndex(0)).relativePath.UTF8String);
Result := True;

{$IFDEF UseSandbox}
if (AppScopedBookMarksEnabled) and (CreateBookMark) then begin
CreateAppScopedBookMark(Data, LOpenDir.URL);
end;
{$ENDIF}
end;
finally
LOpenDir.release;
end;
end;
{$ENDIF}

To ensure, that the dialog does not permanently create bookmarks, when invoked, you can
call the function with a passed variable, that will handle this (Boolean variable
CreateBookmark) and, secondly, whether the sandbox functionality should generally be
supported by the application (compiler directive UseSandbox). The compiler directive
allows you to compile the application, if required, without sandboxing. So you have an
easy option, to sell your program also outside of the App Store, if you want that.

Furthermore, it is checked whether App Scoped bookmarks are supported at all, that is
only from MAC OS X 10.7.3 on or newer the case (checked with the
AppScopedBookmarksEnabled, which you can read in the appendix).

The yellow marked text represents the time, at which the bookmark should be created.

Here, before the NSOpenPanel is released.


In the CreateAppScopedBookMark function I have done the Delphi implementation of


the NSURL function bookmarkDataWithOptions (defined in MACApi.Foundation.pas),
which generates the bookmark:

function CreateAppScopedBookMark (var data: NSDATA; URL: MacApi.Foundation.NSURL): Boolean;
var
err2: NSError;
includingResourceValuesForKeys: NSArray;
relativeToURL: MacApi.Foundation.NSURL;
begin
Result := False;

// Check whether this already contains an entry exists
// Return but existing Data Object
// For simplicity here away
//
// Bookmark does not exist, therefore create

err2 := TNSError.Create;
err2 := NIL;

includingResourceValuesForKeys:= NIL;
relativeToURL:= NIL;

Data := URL.bookmarkDataWithOptions(
NSURLBookmarkCreationWithSecurityScope,
includingResourceValuesForKeys,
relativeToURL, // NIL = App-Scope
@Err2);
try
if not Assigned (err2) then begin
Result := True;
if Data <> NIL then begin
// Bookmark in das Bookmarkverzeichnis speichern
// z.B. Data.writeToFile(NSSTr ([Dateiname]), true);
end else begin

Result := false;
//ShowMessage (Data is NIL);
end;
end else begin
ShowMessage (String (err2.localizedDescription));
end;
except
ShowMessage (Problem with err2 in CreateAppscopeBookmark);
end;
end;

Step 3
If you (after restarting the program) want to access the folder later, you must load the
previously saved bookmark and resolve it, by using the URLByResolvingBookmarkData ,
function, which is also a function of the NSURL interface.

function GetResolvedAppScopedBookMark (data: NSData; var NEWURL: NSURL; var ADir: String): Boolean;
var
Err: NSError;
relativeToURL: MACAPI.Foundation.NSURL;
begin
Result := False;

err := NIL;
RelativeToURL := NIL;

NewUrl := TNSURL.Wrap (TNSURL.OCClass.URLByResolvingBookmarkData(
Data,
NSURLBookmarkResolutionWithSecurityScope,
RelativeToURL,
0,
@Err));

if (NewUrl <> NIL) and (not Assigned (err)) then begin
Result := True;
end;
end;

Step 4
If the bookmark was successfully resolved, you have a NSURL object, with that you can
get access to the folder then.

However, you must use therefore the function


URL.startAccessingSecurityScopedResource

If this returns the value True, you can take access to the folder or the files therein.

Step 5
Then you terminate access to the resource with

URL.stopAccessingSecurityScopedResource

It is important that the calls to start and stop functions are always balanced. You can
make a couple of calls with start in sucsession, but you have make all the stop calls
again in reverse order with the respective URLs. If you come here out of balance, you
cannot call any more resources during the execution of the program (so then only after you
restart the program).

The mentioned start and stop functions are not implemented in Delphi XE3XE7 in the MACApi.Foundation.pas file in the NSURL interface. I have already left
this at Embarcadero by Quality Central as wish, that they will implement directly these
important functions. But until now, it only remains to make the missing features in a
separate interface implementation in an accessible location. This means, unfortunately an
unnecessary complication of the program logic.

To make the whole thing a little easier, I have included at the end of the book as an
attachment my current unit HSW.FMXSandbox.pas. You will also find missing constant
values that you need to use the MAC functions. Be free to use the unit, but at your own
risk. There is no guarantee that I have everything properly implemented. Even if the thing
now seems to work successfully in my programs, you should still test the use in detail in
your programs. If you find any errors or if you notice inconsistencies, I am grateful for a
hint.

In the unit that I have generated are already integrated the functions to store and manage
bookmarks, that will significantly simplifies access to the resources. This looks than like

this:

Function LoadLastFile (FileName: string): boolean;
Var
{$IFDEF MACOS}
{$IFDEF UseSandbox}
URL : HSW.FMXSandbox.NSURL;
HasAccess: Boolean;
{$ENDIF}
{$ENDIF}
Begin
Result := false;
{$IFDEF MACOS}
{$IFDEF UseSandbox}
if AppScopedBookMarksEnabled then begin

// Resource anfordern
if GetAppScopedAccessToResource (Filename, url) then begin
HasAccess := True;
end else begin
ShowMessage (Cant get access to + Filename);
Exit;
end;
end;
{$ENDIF}
{$ENDIF}

// here: open file
// Result = True or false

{$IFDEF MACOS}
{$IFDEF UseSandbox}
if AppScopedBookMarksEnabled then begin
if HasAccess = True then begin
if not CanStopAccessingSecurityScopedResource(Url) then begin
// give an error message
end;
end;

end;
{$ENDIF}
{$ENDIF}
End;

And so the data structure would then be created in your application:


In the container, in AppSupportPath the individual bookmarks are saved in the


AppScopeBookmarks.dat file (a simple TStringList);

The list entries consist of the name of the folder (or files), which has assigned a bookmark
name. To grant, that the bookmark names are unique, I use GUID names therefore. The
bookmarks themselves are stored under the name of the GUID identifier in the folder
Bookmarks.

At least it is a solution that works. Each bookmark files consist of a letters and numbers,
probably with binary content.

Special case save newly created files outside the sandbox


If you want to create a new file outside of the sandbox, there are 2 options.

Option 1: You first let the user select a folder to store files and thereby created a
bookmark. Then they have during runtime access to the folder and can create files and
have also after restarting the program access to them.

Option 2: More often, however, probably the other way would be used: The user of your
program has a new text document or a graphic or whatever created, and the content should
now be saved to a file. As long as you save the contents in the sandbox environment - no
problem, this you can do of course always.

But the user may want to place the file in the folder Documents or My Pictures or

Desktop. Of course you do not want to ask the user in each case in a first step to select a
folder and in a second step, the file name for it.

Therefore it is obvious that you want to use the Save As dialog. Again, you cannot use
the TSaveDialog of Delphi, because the saving of a bookmark can be applied only during
the lifetime of the NSSavePanel.

Now it is so that a Bookmark only for an existing folder or an existing file can be created.
But your file does not yet exist, you can create it first, after the user has given a name for
it.

The trick that we will use here, is that the extended NSSavePanel dialog will directly after selecting the file name - create a dummy file with the selected name and then it
directly generates the bookmark. Then the dialog will be released and you have a file, into
that you can write the content you need. With the bookmark you can also get access to the
file after restarting the program, e.g. if the file will be loaded automatically as Open
lastfile after your program logic.

With this second way there is a small caveat: You cant change the name after selecting
the file name by the user, because the bookmark was created already for the name selected
by the user. If the user therefore indicates an unusual extension, the program cant change
it anymore.

So if you need that a specific file extension is mandatory, you must modify the function,
where I have implemented the NSSavePanel dialog. You than have to ensure, that the
filename will be adjusted, before the bookmark is created.

Conclusion: The issue of sandboxing is not a very simple issue. Thats why I hope even
more, that this information will help you to make your app as desired sandbox-enabled.

Section 7: Use 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 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. You could here interpret the abbreviation UniStd perhaps as Unix
standard because there are just typical for Unix standard features to find.

Strictly speaking, the function is in the UniStdAPI.inc file to find. Its worth it to say
here 2-3 words on the structure, with that Embarcadero has implemented the API
functions.

Under

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


you can find the POSIX folder. He has a subdirectory called OSX. The concret
implementations are found in the .INC-files (.INC stands for include). If you look at

the individual units of the POSIX folder, for example, in the Posix.Stdlib.pas you will
find IFDEF declarations 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 safely conclude that Embarcadero is
working on a Linux implementation.

So 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 may be significantly less time than the 256
characters that I reserve here, but since I do not know, Ill go on the safe side.

For Mac OS X, the return name is normally supplemented with a .local, by which he 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 need here no
buffer in which the name must be stored, we can use this function directly as:

strUsername := UTF8ToUnicodeString (GetLogin).

COREAPI

Most of the core Apis you will find 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, its 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 behind the so-called Core API are Reference counted objects.
That means, that behind functions or data structures, are in truth objects, sometimes even
COCOA objects.

So if you want to use strings as passing parameters here, this cant be Delphi strings, but it
must be CFStrings, so 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 time, with a further button and user-defined text for the button. The
exact skills of the function you can see in the Mac Developer Library on the following
page:

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

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, cause, when they are called,
that the reference count to the object is increased by the value of 1. After using the
objects you must therefore release them with the CFRelease so that the reference count
can be reduced by the value of 1 again. If the reference count is reset to zero, the object
is total released. If you do not, you retain residual objects in memory that will remain after
completion of your program. I do not know how the programs are checked for MAC

Appstore for such shortcomings, I would advise to work carefully with such objects.

An important addition is to mention in this context for functions that have a Get in the
name. There objects are not copied or new created, you just use the original. 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, what the reference count decrement by 1.

COCOA API

The APIs from the COCOA Framework are specifically tailored for use with Objective-C.
Many objects and functions, you will find 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 Delphii XE7 also in the following folder:

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


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

NSString = interface(NSObject)

and also as

NSStringClass = interface(NSObjectClass).

The same applies, for example, for NSURL and NSURLCLASS. It is therefore important
to know, because you will sometimes need functions of the one and of the other interface.
By the way, here is implemented much, but not all, now and then there will be times when
you need to upgrade individual functions through a re-implementation itself, like I have
made it for example in the HSW.FMXSandbox.pas unit.

But again, lets look at an example of how COCOA objects are to be used, 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
often sees in forums where then asks why this so 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 therefore not work, because as a result of the Delphi Objective-C bridge just a
Raw object is supplied back that is so not usable. Moreover applies to the Workspace
object that for each program only one shared workspace object is available, 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/Classe

Properly therefore the 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 then also no need to release anything, because nothing is created, only variables
(objects) were set with content.

The most COCOA objects are reference counted, but this usually works automatically, in

the example case, therefore, neither a retain nor a release after the use of the object is
required.

In COCOA objects it is only in exceptional cases required to work with Release


functions, this simplifies working with these objects very much.

Chapter 3: Requirements for CrossPlatform 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 find the latter solution not so good, because Ive run on my Mac different
partitions with different MAC OS X versions, that I run as needed. And then its an
advantage to have your own Windows PC, with Delphi there installed.

Preparation
On your Windows PC, refer to the following directory

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


to the file PAServer15.0.pkg. Copy this file to the MAC (e.g. to the desktop). The also
in the Windows directory existing file Setup_paserver.exe - is not needed (it is used, if
you want to perform on another Windows machine a remote debugging - and than you
must installed it on the other Windows machine).

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

Click Connect. If you do the connection for the first time, this dialog will appear:

Under Name, enter the user name of the Windows PCs and also the local password (not
the password of the MAC-PC).

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

Then click on the MAC on the .PKG file, it is automatically extracted. Then double-click
the unpacked file Setup_paserver. The following setup program starts, which installs the
PAServer on the MAC.

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

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

/Applications/PAServer15.0.app

At the Internet it is also described in a docwiki how the server should be installed and
running:

http://docwiki.embarcadero.com/RADStudio/XE7/en/Installing_the_Platform_Assistant_on_a_M

To start the server, go to the Applications folder and launch the application PAServer
15.0

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

Then Apple will prompt 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 on the Windows PC produced MAC application, using the
PAServer, to the MAC into the so-called ScratchDir. This can usually be found here:

/Users/YourUserName/PAServer/Scratch-Dir.

Here Ive created a link in my MAC OS File manager directy to this folder:

Section 2: Enabling MAC OSX Platform


By default, in the Multi Device Application the MAC Platform is automatically as a


target in the Project Manager. If you have this deleted once or do not find in an inherited
project, add your FireMonkey project by using the right mouse button on the Target
Platforms button in the Project Manager and select from the popup menu Add Platform
command ,

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

You may need also 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:

Before I go into this dialog: It could be, that before this dialog appears, you have to input a
profile name. And now to the dialog:

For Host Name, I recommend instead specify the name of the MAC PC, to take the IPaddress from the MAC in the network (you will find the IP address of the MAC there
under Control Panel, Network). The connection works faster in mixed wired and
wireless networks, and sometimes it works only with the IP address (TIP: 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 conection. So if submission to the MAC does not work (it then
only gives the message Error, without further explanation), this should be the first thing
to check. You can also adjust the system settings of the MACs, so that here always will be
used a specific IP address, then you will never need to change anything here (I have done
it like that, all works great!).

Leave the port number here as specified, under password leave it blank, or enter a
password for the PAServer if you have setup one (no need usually therefor and it
complicates matters even unnecessary).

But now back to the Edit SDK dialog. Here you can select a suitable SDK. There are
here normally available one or several to choose from. Normally select the newest. Or
select on 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 than it could look like in the Project window like this:

Important Note: Depending on whether you select the target platform Windows or MAC
OSX, also the presentation of the Project Options dialog changes. Special attention is here
about the Version Info. Because with that it is later made the info.plist file that must
accompany each MAC program.

Also of importance in the Project Options is the Entitlement List, where you can specify
which rights are available to your program. In particular, if you want to distribute your
programs on the APPLE App Store, you should choose carefully the information here.

From the Entitlement List Delphi generates the .Entitlements file that is distributed with
the program (which is especially for the sandboxing model of relevance).

The Entitlement List:


The Version Info:

If needed, you can add a new key here. This is by clicking the right mouse button on the
table header Key (see in yellow):

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 at first sight good:

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

Section 3: Provisioning and deployment


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

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

If Delphi has created the program, Delphi 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 info.plist and the Entitlements.plist. The lybcgunwind.1.0.dlyb file, you need in
the delivery version of the program, otherwise it will not run.

1. Submission to the APPLE App Store


For this you have under the target platform MAC OS entry to switch from Normal to
Application Store:

Under the menu Project, Options, Provisioning, check that there is selected as a build
type OS X Application Store. And of course you use the Release configuration. In the
two input lines you make entries as listed below:

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

First check on your Mac in the Keychain Access if you have already installed the
required certificates on your MAC:

If you do not already have such a certificate, 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 transfers the required files to your Mac and calls the code signing tool that initially
signed the executable (you will be prompted to allow this). Right after that, Delphi calls
the Product Build Tool that produces a so-called Package, a file with the extension
.pkg will be created. This package is then also the installation file, and that is why it is
then also with the code signing tool signed with the Installation Certificate (you must also
allow this, unless you always allow this).

This completed Package, you can then upload to Apple with the Apple program
Application Loader. First, however, you must create a data entry for your program
through iTunes Connect and complete your input 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 at the Apple employees for review, it usually takes 3-7 days until
actually an assessment is carried out.

To stay informed about the progress, you can use a free app from Apple (ITC-Mobile), I
made below 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. These with orange dots are
either waiting for a review or are just in review. In the gray box you can see the OS X or
iOS label. That will indicate, if it is a program for the MAC or for iPhone / IPad. Under
Sales / Trends you can check for a day or for a week, how many products have been
sold. Also you can see the countries in which you have sold your programs. Surprisingly, I
was able to find even with my only German-language programs buyers in the US, Russia,
Thailand, Japan, Italy, Mexico and Slovakia.

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

A tip: Before you load a program into the Apple store, look at Apples App Store Review
Guidelines on. Here is the link to:

https://developer.apple.com/app-store/review/guidelines

2. Create a .dmg file for distribution outside the Apple App Store
I sell my programs even without the App Store. This can you do also as follows: After you
have created with Delphi a MAC OS X release version (with the project setting Normal
and not App Store), run on the Mac the Disk Utility (Applications folder, Utilities).
Select the File menu and select New, Disk Image from Folder:

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

Select the program file and click on Image. The following dialog is displayed:

I recommend to remove the extension .app and choose the location of the desk. The in
this case created TEditor.dmg file, you can now distribute it over the Internet to your
customers.

After downloading, the customer clicks double on the .dmg-file and from the window that
appears, drags the file into the program folder and the program is than installed.

3. Create your own setup package with Application Developer ID / Installer


Another possibility would be 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 benefits?


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

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

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

So if you want to play it safe, you need to have 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
under 3 b) Working with CodeSign tool and Package Maker.

a) 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 at the top right on to Member Center.

Then click on the link below is shown, then you will be forwarded into the area where you
can manage your certificates:

Then click on the table: MAC Apps, Certificates. This takes you to the page where the
management of MAC certificates takes place. Click right there on top on the plus button,
then you will get the page where you can choose the certificate according to your needs.

Ultimately you need all certificates that are offered here (if you also want to use the App
Store). For our purposes it is at the moment sufficient, when you ask for the the lower
certificates (Developer ID, not visible in the screenshot above). Download it to your
MAC, double click on the file, so it will be installed into your Keychain Access.

I do not want to take 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: To be able to create your certificates in general, you need a


CertifacteSigningRequest file.

Where do you get them from?


Using the Keychain Access. Here you go to Keychain Access, Certificate Wizard,
Request a certificate from a certificate authority:

Then the following dialog will be displayed:


Once you have finished this dialog, you will receive the requested file. In my case I had to
create this file by the way several times, because of the different certificates and different
request files were needed.

Also an important hint: after I got my Developer IDs and this was transferred to the
Keychain Access, first it was displayed, that they were invalid because it were signed by a
Unknown Certificate Authority. It has helped to wait: 24 hours later, I have downloaded
the certificates again from the certificates area and re-installed it. Than all was perfect.

b) Working with the codesigning tool and Package Maker


If you have Delphi running 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 select as Build-type in the project
options OS X - Normal .

After running the program and the transfer 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


Which then of course replace TEditor.App with the name of your application.

Youll again be prompted to allow the sign:


So, now you have signed your program, but how do you create the setup package?

You can do this using the Package Maker, which you will find by default no longer on
newer MAC OS versions. So you must download it from Apple. This is done there in the

download area (https://developer.apple.com/downloads/index.action?


name=PackageMaker). If in doubt, search 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 cant be used (apparently) because it keeps
crashing. Ive 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 access (I have this on several of partitions on my Mac). If thats 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 the arrow, and then enter your Developer ID Installer
certificate. Also you can make basic settings, e.g. as the title of the setup, where the
program may be installed, and so on.

Next image: just drag your program from the Finder into the left side of the window, or
click beside to the line Install on the button 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 on the top right, a dialog appears where you can select the
individual installation steps in detail:

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

Then click the 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 previously with Lazarus created MAC
programs).

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 to other MAC PCs, there it works,
as expected.

Chapter 4: 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, the most with FireMonkey lack thereof in the output format. In
particular, for 1-bit, 4-bit, 8-bit, 16-bit and 24-bit output format is not available if you
want to save the bitmap with the extension .bmp.

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

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

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

And unfortunately information is lost, if one loads a Windows 32-bitmap file that has an
alpha channel into a FireMonkey bitmap. The values of the alpha channel will not be take
into account, this all have all the value of 255 (that is completely visible). I am not quite
clear whether this is a bug.

2. TBitmapData instead ScanLine for bitmap manipulation


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

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 with MAP ask for the access to the bitmap.
So with that, depending on the type of access, this will be than 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 TMapAccess, which is defined as follows:



TMapAccess = (maRead, maWrite, maReadWrite);

For example, to manipulate a particular pixel in a bitmap, that looks in a whole as follows
(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 so 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 canceled in the event of a fault.

3. 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
here 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. Draw on the canvas of a bitmap


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

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

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;

5. Turn graphics, flip, invert or color to gray


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


Rotate a image left or right? Nothing easier than that:



Bitmap.rotate (270); // Turn bitmap left
Bitmap.Rotate (90); // Turn bitmap right

A graphic horizontally or vertically reflect:



Bitmap.Fliphorizontal; // mirroring the picture horizontal
Bitmap.FlipVertical; // mirroring the picture vertical

To invert an image or to color gray one uses one of the most extensive in FireMonkey
supplied filter functions.

Among the FireMonkey demos is a fine example 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, unfortunately, it is written so, that much is
generated at runtime or dynamically. If we look into the source code, it is than
unfortunately not very helpful.

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 the function looks (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 color a bitmap to gray, 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 dynamically at run time a filter attribute record with the possible attribute names
and setting values of the filter is filled. This information is then used to dynamically
generate TTrackbars that are generated with the minimum and maximum values of the
filter attributes. This TTrackbars also be associated with an event handler to respond to
changes in TTrackbars and then apply the filter according to the bitmaps.

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

So you could extend the ImgByFilter function, by example to integrate the following
settings:

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

For example, applying the sepia filter with a specific value in the above function, you
would - before calling the ValuesAsBitmap with the output option - use the following
line 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, Opacity and the settings are not always between 0 and 1,
they can have also other minimum and maximum values.

For that the mentioned shader filter demo helps 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:

Overall, I find the graphics capabilities of FireMonkey quite impressive.



6. Drawing a bitmap scaled


From the VCL you know, for example, the StretchDraw function, which allows you to
draw scaled graphics. In FireMonkey you 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 size 1680x1050
pixels. It can be scaled for example, as shown here, scaled 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. As a parameter you use the original bitmap, whose size and the new
desired output size. The transparency you leave to 1 for fully visible, the interpolationmode leave with (High Speed =) False.

Chapter 5: Useful third party components


for FireMonkey

1. TMS-Components

Several times in this book the TMS components have already been mentioned. The most
useful TMSFMXBitmapcontainer component has already been mentioned, because it is
practically a (better) replacement for the TImageList from the VCL world. But also
provides the TTMSFMXGrid a number of useful features, e.g. in combination with the
possibility to export the content of the grid as Excel, RTF or PDF file
(TTMSFMXGridExcelIO, TTMSFMXGridRTFIO, TTMSFMXGridPDFIO).

Also briefly mentioned was the TTMSFMXRichEditor, which is a worthy replacement


for the VCL RichEdit component. With the TTMSFMXRichEditorFormatToolbar
component and the TTMSFMXRichEditorEditToolbar two ready to use ToolBar
components are delivered, that you can associate with the editor.

The TMS-Rich Editor can save, if necessary, his text as 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

This is good news: As of version 2.9.0.0 of TMS Pack for FireMonkey, one can use the
RichEditor even with a complete spell check. The dictionaries are included, 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, we must mention 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. So 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 if you have or want to use certain native components for MAC (or iOS), TMS helps
you with the mCL components (or iCL on iOS).

In a cross compile project, however, you have with the use of this components a little
more effort, because of course for the Windows desktop this native MAC OS components
will not work. But sometimes its the only way to realize a project for MAC at all.

For example, to display PDF files in my invoice program on the Mac in a separate form, I
use the TMSFMXNativeNSView component from TMS (while I use under Windows a
with a Hydra-module integrated component of Gnostice).

The direct generation of PDF invoice file Ill do on 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 youre used to do it under the
Windows VCL. In Delphi XE7 although a standard version of Fast Reports FMX is
included, however, it contains no redistributable report designer. This is obtained when
one acquires the commercial version of Fast Reports.

Here the FastReport designer at design time:


Together, 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)


These are not components, but a framework that allows you to mix VCL and FMX
components in a form. For example you can in a VCL Forms directly integrate FMX
components (or the other way around).

This is surely a way that should not be the focus of your work with FireMonkey, but it
offers additional ways and opportunities to 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 in YouTube, which you are welcomed to view, if
wanted:

https://www.youtube.com/watch?v=0K8mEzDmlaM

4. Other components

Other components manufacturers have announced support for FireMonkey (e.g. Gnostice)
or waiting to make a decision for it (DevExpress, ImageEn, TRichView). But there are
also other developers and FMX supporters, who have developed a set of components and
provide them predominantly free of charge for you.

Here I recommend you to take a look over at http://www.fmxexpress.com and to examine


whether there is something for your needs here (where most components rather refer to
iOS or Android development).

I hope I can this meager list here soon extend a little bit

Do you know good FMX components that I should mention here? If you want, send me
your suggestion with a short e-mail to (at info@hastasoft.de).

Chapter 6: How to - tips & tricks for FMX


Here you will find a number of different questions and answers. As a tribute to my
beloved cookbook -Delphi series by Walter Doberenz and Thomas Kowaslki I lean to
the R-numbering from these books, which stands for recipes (in German it means a
kind of written description to handle something).

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 the width and the height is in Size.y.



R2 Check if the Escape, Ctrl or Alt key is pressed


Sometimes you need for an ongoing process an offer to interrupt or cancel it. Under
Windows, in the VCL it works like this:

if (Getkeystate (VK_CONTROL) < 0) then begin // Is the Shift-key pressed?

// yes >
exit;
end;

And so do you do it 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 best like this (which I unfortunately still dont know
is the solution to query the ESC key on MAC (does anyone know the solution?):


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;

Here I should mention, that I have stored this functions in a shared.plattform.pas unit,
which is currently only available for Windows and MAC. But it would be better, one
would make the IFDEFs with MSWINDOWS and MACOS, then it would be, for
example, easier to extend the functions later to use with Linux.

R3 Use folder names under Windows and MAC properly


On 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.

To be shure, that 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 below excerpt from the unit System.SysUtils 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 on the

MAC associated drives, you must query the entries of the first-level directory under
/Volumes.

The following source code reads the existing drives (volumes) on my Mac (under
Windows it works like this, of course not):


// 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 (with Add (copy (sDrives, 10, 255)) you would get only the drive
name):

R4 Use search mask for all files in Windows and MAC properly

If you want to view in file searches on Windows all files use the mask *.*. Under
MAC it seems also to work well. However, * is the right mask. The *.* would e.g. 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 ultimately
only a reference to the folder that is on the hard drive on another location. If you are in a
folder and all subfolders searches for files in a recursive search, here can possibly arise a
closed loop situation. If an alias in a folder A points to a folder C and this folder is
found a reference back to the folder A, the search continues endlessly.

Therefore, in a recursive file search the attribute faSymlink has to be filtered out.

Here is an example that searches for all the .pas files in the folder D:\Delphi and
stores 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 parameters also exist in a number of other file functions such
TFile.exists, TDirectory.Exists, etc.

When using file functions, so you should let display always the parameters that you can
use there, because possibly there are even more than you would expect.

Unchecked symlink properties can lead to unwanted results now and then. Lets say you
want to copy a file. If it is an alias, so do not copy the perhaps only 30 bytes wide alias
file, 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 to see, if there 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, Ive found under the MAC that here (for whatever reason) some files have the
attribute, although there were 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 currently located at the current mouse position,
you can do so with the ObjectAtPoint.
Here is an example, to get the class name of the object over which the mouse pointer is
just:
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


In the unit System.SysUtils the record TOSVersion is available, that 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 here as follows:

Under Mac OS X, then so:


If you look at the structure of the record once, you see what features and data are available
in total:

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 determine the current user name in Mac OS X / Windows


You simply take the 2nd entry from the directory GetHomepath command:

{$IFDEF MACOS}
function mac_GetComputerUserName: string;
begin
result := GetfieldStr (PathDelim, GetHomePath, 3);
end;
{$ENDIF}

If you do not have a comparable GetFieldStr function, you make it so:



{$IFDEF MACOS}
function mac_GetComputerUserName: string;
var
sl: TStringList;
begin
sl := TStringList.Create;
sl.Text := StringReplace (GetHomePath, PathDelim, #13#10, [rfReplaceAll]);
result := sl[2];
sl.Free;
end;
{$ENDIF}

Bear in mind, on Windows it goes like this (the Windows unit is required):

{$IFDEF MSWINDOWS}
function Win_GetComputerUserName: String;
var
P: PChar;
dw: dword;
ms: String;
begin
dw := 255;
P := StrAlloc (256);


GetUserName (p, dw);

if dw > 0 then begin
ms := String (P);
end;

Result := ms;
end;
{$ENDIF}

On Windows, the result looks like this:

And then on the MAC as:


R10 Send files as an attachment of an e-mail with the system mail program

A frequently used function is the sending of files that was created in your own program.
The simplest solution is to transfer the files to the e-mail program, that is used by the
operating system.

On Windows, you can use the Microsoft MAPI. Im assuming that you probably already

know how to do that in Windows using the MAPI interface. If not, you can download
from my devpage website my unit uSendMail.pas containing the here used Send Files
function:
http://www.devpage.de/download/fmbook/uSendMail.pas

The cross-platform solution for Windows and MAC is then as follows (using the example
of a form with a listbox, in which you have selected one or more files, and then click a
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 that holds the file name
in quotes and separated by a space. As a mail program here the Apple Mail program is
used.

The parameters open -a simply means that the function _system should start an
application, and then pass over the files to be sent separated by a space.

Note: If you want to give the user the option to use another mail program, you could
offer appropriate options in a setting dialog. He would then simply select the mail
program in Apple Applications folder, which he used.

Instead of

app := /Applications/Mail.app;

you would use



app := /Applications/UserMailprog.app;

where UserMailprog the selected user mail program would be. One requirement would
be, of course, that this e-mail program would also have to support the transfer of files as
parameters.

R11 provide the user with help files under Win & MAC

On Windows, you will either have been the Microsoft HTML Help Workshop used to
create a .chm help file or another professional program which generates these files.

The Help Workshop used as source HTML files. Other professional programs work either
with HTML files or can output the help text, at least in such a format.

And this is also the solution: use under both Windows and MAC OSX HTML files that
allow you to call and view 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, that 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, then call as needed the HTML files (AktLang is here a global variable,
managed by the program. It keeps the information about the currently used language):

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 in. 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 constantly work with
IFDEFs in your program logic. Then it is later also much easier to expand your program
for another platform (e.g. Linux).

R12 After uploading to App Store: Invalid binary - causes and remedies

What has just cost me half a night: After creating the application I have made an entry in
iTunes Connect and then uploaded the binary (i.e. the package). This in so far works, but 2
minutes later, the file has been featured in iTunes Connect as Invalid binary.

The first mistake was, that I did not look in my e-mails. There was no explanations in
iTunes Connect, but Apple had sent me instructions via email.

The explanation in the mail was:



Files Only Readable By The Root User - The installer package includes files that are only readable by the root user.
This will prevent verification of the applications code signature when your app is run. Ensure that non-root users can
read the files in your app.

How could that be? Files only readable by the root user?

This was on my second error (historically, the first mistake):


In between I had installed the setup package for testing on the development system. But
strangely the installation was exactly done to the place where Delphi stores the application
program. Because the Setup package was installed with admin rights (it was done a

password prompt) the existing files are overwritten with corresponding attributes.

Later I had compile my application again with Delphi, but via transfer to the mac, not all
files were overwritten at the destination, because I had except some files in the
deployment list. So thats some of the files were marked so that only the admin has read
access on the application bundle.

The solution of the problem was then to ship the whole application bundle to the trash and
let it create Delphi again from scratch.

And then everything works again as desired.



R13 Application rejected: Some reasons for refusal, which you can avoid

Missing Command-Q Befehl



The user interface is not consistent with the OS X Human Interface Guidelines
We have found that when the user presses Command-Q, the app does not quit.

So Insert into the menu, which bears the name of your program, even a command with the
text Quit [YourProgramname] and assign the shortcut Command Q to that command,
you can select it at the Objectinspector.

Different name of the application in the System Menu and in the Programs folder

The app name to be displayed on the App Store does not sufficiently match the name of the app when installed on Mac
OS X.
iTunes Connect Name: Multi ScreenShot.
App Name when Installed: MScrShot.

So the name that appears right next to the Apple icon in the toolbar, has to match with the
name in iTunes Connect and in the Applications folder. I did indeed manage to let it
display in the menu bar as desired with a changed entry in the pInfo list (bundle name and
bundle display name) name. But that it had no effect for the name of the Applications
folder. Here I would also have to change the executable name, but Delphi allows no
project name with a space in it. So I had to change the program name in
MultiScreenShot, just with no spaces. So now is the executable file and the entry in
iTunes Connect.

The purposes required rights (entitlements) has no further explanation



In order to continue reviewing this app, we require additional information.

This app uses one or more entitlements which do not appear to have matching functionality within the app. Please
describe how and where the app uses the following entitlements by adding your comments to the Resolution Center.

I demanded in the Entitlements file (for the Apple sandbox model relevant) more rights
than are actually was needed. For example, I had requested a direct read access to the
global images folder of my application. Actually, this option would have been enough:

Normally, you should therefore only Read and write access to files selected with the
Open or Save dialog tick here. If more rights are needed, you must describe it in more
detail during the upload. Anyway, without description, the application is otherwise
rejected.

Again, you select this rights in Delphi under the Project menu, Options, Entitlement
List.

R14 Using ActiveControl


If you have placed in a form various controls and make a query on the variable
ActiveControl, this is - unlike under the VCL - always NIL. Whether this is intentional
or a bug, it is not clear. Anyway, while the program is running, it will sometimes be
usefull to know, what is just the active control (i.e. the one that has the focus).

You can directly use the property of the form Focused here. This is namely the control,
which has the focus.

So if you want set the ActiveControl variable, you can do so 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 above source-code example 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 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 lots of data stored in internal objects that have been connected
to the ListBoxItems (or otherwise holds somewhere in a list or database). For the below
demo I have kept it simple and demonstrate it with no associated data objects. The
ListBoxItems does contain no text (and they should not, because it will be drawed by
default by FireMonkey). The text I get here from the name of the ListBoxItems. 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 text property Text of the TListboxItems have no content itself, of
course, because this would result in duplicate drawings (text overlays). You can use
Tagstring instead of the Text property of a ListboxItem, if you want to keep data in a
Listbox-Item (but best would be, you use it only for drawing).

So that then it looks in the result:


R16 Load Bitmap from resource file (for retina display)

Since Delphi XE5 you can easily add resources to your program that can later be loaded
into a component. To distinguish: here we talk about a program resource and 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 so, that the user uses a screen with
twice the resolution, the so-called. Retina display. If it is important for your program that
you are using certain bitmaps that you can also display with twice the resolution, you can
create a bitmap with a normal resolution, and save one with twice the resolution in the
program resource.

At run time, 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 under the
Projects menu the command Resources and Images.

Add a normal sized image and you provide the file name at the end with a 1. Images
with twice the resolution you will provide with a 2 at the end. Rename also the identifier
to Dia1. Warning: Upper and lower case is necessary! So if you enter here Dia1 and
use in 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 runtim, 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, everything else has a higher resolution. Then you create a resource stream, load
the bitmap into it and then load the bitmapit from the stream into the image component.
The detour via the resource stream is unfortunately necessary because the image
component cant 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 in the
listbox would be 10 entries):


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, so you must leave the BeginUpdateResource and EndUpdate.

Explanation of this: Internally the function Items.Exchange used by itself a


Listbox.BeginUpdate and a Listbox.Endupdate. If you use 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 if you even know 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. So convert everything in one go. The disadvantage is, that this can take a
long time on a larger VCL project and in the meantime you cant change much in the
current application.

The alternative could be a smooth transition. By example, outsource step by step dialogues
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 in general not want to convert the project to FireMonkey, but for certain
functionalities you would like to use FireMonkey. In both variants, it makes sense to
provide this function across a FireMonkey DLL. This is not that difficult, it works much
like under the VCL.

Here I show you an example of how I have added to the VCL-image editing program
PixPower Photo & Draw a new filter effect over 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.

So in the VCL application I have a bitmap that I save as a bitmap stream and pass it to the
FMX-DLL. The bitmap I cant directly passed as TBitmap to the DLL unfortunately,
because VCL and FireMonkey bitmaps are incompatible with each other.

In the DLL, the bitmap is displayed in a dialog in the ImageViewer component, which
Ive added a PaperSketch effect. The intensity is adjusted via a trackable.

If the user then confirmed the result with OK, the effect is actually applied to the bitmap
and write the bitmap back into the stream. Here, a little trick is used, because by default
FireMonkey writes a bitmap as a PNG stream. Therefore, a separate class TMyBitmap
derived from TBitmap is used and overwrite the save stream procedure and adapted so
that the stream can be saved as bitmap stream.

Thats the way how to do it: Create on the menu File, command New a 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 yet.

You have to add manually the Unit FMX.Forms, so that it is clear that there 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 indicating that the DLL here is handled as
FireMonkey project and therefore an appropriate marking should be made. This query you
can positively confirm.

The Unit FrmFilter is a form unit that I have created under File, New, FireMonkey
Form:

Note: This command is only displayed when you view 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:

Function ShowBitmapFromStream (ms: TMemoryStream): Boolean; export;

This is the function that is provided as externally callable function available through the
exports statement in the library file.

Note 1: If you want to pass a string instead a bitmap to the DLL, you should use either a
ShortString, PChar or WideString. So you dont need to take the ShareMem unit into the
unit uses and you dont 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 programs that were 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 (under uses the units
FMX.Filter, FMX.Effects, FMX.Filter.Effects and FMX.Surfaces are required):

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 required adjustment to store the bitmap streams:



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
ImageViewer.
If the user has set the desired intensity of the effect with the trackbar and than he 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 following VCL application 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 is ShowBitMapFromStream then introduced as procedure variable.
In the initialization section, the DLL is loaded and our previously declared procedure will
then be assigned to the memory-adress of the function in the DLL.
If you include this VCL-unit in your VCL application you can then call from there the
function ShowBitmapFromStream.
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) //Datei wieder in Bitmap laden
end;
End;

NOTE: To get this to work you have to include in the main form of your VCL application
the unit Winapi.GDIPOBJ directly into the USES section of the interface-section (not in
a uses clause in the implementation section, that would be not enough).
This unit is required so that the GDI function can be initialized also for the VCL
application. This can be done only through the main program, it not belongs into the
FireMonkey unit.
If you are interested, you can 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 this filter: http://youtu.be/W21uxyPsJvc.

R20 Draw text in TGrid right, or centered


Since XE6, the TGrid contains the possibility, under TextSettings choose with
HorzAlign if the text shown is intended to be left-justified, centered or right-justified.

However, this setting applies to all columns that are displaying text. The TColums or
TStringColums you have placed into the TGrid have no property like the TGrid. But that
could be necessary, if the text in one column sould be displayed justified on the left and in
another column to the right (e.g. monetary amounts).

Here it is useful initially to set 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, then we deliver
in 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 for all columns the
DrawCellRight the function:

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;

Here, the user-defined function DrawCellRight is called. We also use the OnGetValue
event of the TGrid to get the value of the cell. Temporary we set the Tag value of the
TColumn to 0 so that the value is returned to us (remember: We have above a
supplement installed 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 help function, which then draws for us the text:

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 all columns whose tag-value is 0, the drawing starts at he left margin
(done by the TGrid itself), all whose tag-value is 1, the text is right justified (done by
our drawing function):

R21 Draw text in TStringGrid right, or centered


Also in TStringGrid you can set the text output for the columns only for all the same. So
also here we have to draw the text by ourself in the OnDrawColumnCell-event. This
solution assumes that the string grid does not hold the data, it only display the relevant
content (as it should be). So, leave e.g. 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 to be output
centered to 2. So then it looks 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 the result:

R22 Dealing with the visible property of controls


New since XE7:


Since XE7 there is no longer the property DesignVisible that was before beside the
property Visible. When you put a control at design time 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 treeview where you can
easily select and activate the invisible control. So 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


Since XE6 is available, under TextSettings, is for Trimming the default setting
character to display the label text. AutoSize is turned off. This can sometimes lead to,
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 just yet 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 the height of the
Label component slightly enlarge.

R24 Use hints in FireMonkey: How it goes


If you are looking in the Objectinspector for the property Hint for TButton,
TSpeedButton or TLabel, you will not find anything.
In reality, all the components mentioned here, has the Hint property (also for others, for
example TListBox), but it is not published. If you could set the property at run time
(which usually is not possible) the hint feature would also not be supported.
I have therefore developed a unit (HS_FMXHints.pas) that you need to include only to the
form in which you want to use hints in the controls mentioned above. The unit uses a
known hack trick. The hint property is made available and it can be set at runtime. In
addition, with the SetAHint function an OnMouseEnter and an OnMouseLeave
event is then added to the controls.
At run-time, e.g. in the OnCreate event of the form, then set the hints as follows:
SetAHint (Button1 Show Help);
SetAHint (Label1 Sum over all quarters);

Attention: If you use a label, you have by yourself set in the Objectinspector the property
HitTest to true, otherwise the OnMouseEnter event is not passed (set at runtime
curiously has no effect).
If you run your program and move the mouse pointer over a control, then a corresponding
hint is displayed. It looks e.g. like this:

Depending on whether the hint appears at the left or right edge, the upper triangle is
displayed just to the left or right. If a text is included, which is more than 200 pixels wide,
the hint is displayed on several lines. If the Hint displayed at the bottom, it may appear
over the control because it could no longer be displayed in the form otherwise.
You can also make an initialization, overwriting the default predefined values:
SetHintSetting (TimeBeforeShow, TimeToShow: Integer; DynamicShow: Boolean; TC: TAlphaColor)

The value for TimeBeforeShow indicates when the mouse pointer is over the control is,
how long to wait until the hint will be displayed.
The value TimeToShow indicates how long the hint will be displayed.
If you set DynamicShow to True, the display time is extended by a calculated value for
longer text, a very useful option. With TC you can specify a different hint color.
Restrictions:
Because the call of SetAHint overrides any existing own OnMouseEnter or
OnMouseLeave events (which youve set up themselves in your program), you cant use
this workaround in this case. However, if required you can expand the unit by checking
when setting the hint string if the control has an event function already assigned, and make
then some addaptions.
I hope that maybe from Delphi XE8 on, the hint feature is supported directly, then we do
not need such workarounds.
The unit can be downloaded here:
http://www.devpage.de/download/HS_FMXHints.zip
For you, the reader of this book, is exclusive a small tool available, which I had written for
me to read the hints from the old VCL forms and to make them so very quickly for
converted FMX program available. Just open with the program simply the VCL form
(button Select VCL Form) and you get in the memo all hints listed, ready to use for the
function SetAHint. You can then easily copy this text e.g. into the OnCreate event of the
FMX form and have than all the hints there again.

The program, which you may use freely for your needs, you use at your own risk
(disclosure to third parties is therefore not allowed). You can download it here:
http://www.devpage.de/download/GetHints.zip
Extract the contained .exe and copy it somewhere, from where you want to use the
program.
A short video, how to use the hint-unit and the tool, you can view on YouTube:
http://youtu.be/eWGbhOexrJk

R25 Determine the document directory


For all platforms, you can use from the unit System.IOUtils that you need to include in
your project, the record TPath, which 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;

Result for DocPath here on my Mac or Windows computer:


under MAC OS X: /Users/harrystahl/Documents


under Windows: C:\Users\Harry-Dev\Documents

Tip: Click once by holding down the Ctrl key on TPath, Delphi will then show you the
TPath record. Explore the individual variables and functions that are available there (in the
public sector). That it is really worth it, some of it you can use for shure again later. 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
manage it so, that I turn 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 FMX.Types the unit 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 whlen Sie ein Verzeichnis, root, dir) then begin
ShowMessage (Sie haben + dir + gewhlt);
end;
end;

The Unit FMX.Dialogs must be integrated. 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 to which the dialog is displayed (default is the document directory), the
third is a VAR parameter and returnes back the selected directory name.
Note: If you want to use the selection dialog along with a bookmark function in a
sandboxed application, you must use the replacement function shown in Annex 2.

R28 Get access to a cell control of TGrids


While we can set at runtime in a TStringGrid with

Stringrid1.cells[0,1] := Test;

the cells with values or can retrieve values from the cells, that goes unfortunately not with
the TGrid, even if it contains a TStringColumn. The TGrid, or the TColums contain only
StyledControls, which can then take the appropriate values.

Before here a way is shown how one still comes to the value and can change it (if
necessary), the following explanation: FireMonkey was always evolving with each
version, in terms of the speed of the display.

With TGrid it is so, that actually needed display elements (cells = StyledControls) are
generated at runtime. So, for example, when a grid has 1000 rows and there are displayed
only the first 20 rows, then only the first 20 rows and cells are generated at run time.
There will than also be only for the first 20 rows GetValue queries.

This of course is memory-friendly and good for speed. But you must take note that you
can therefore give only set or retrieve values for the grid in the cells when the cells have
been generated for it already. You can assume that a cell allready exists, if you want to
manipulate the value of a cell is shown.

We will subsequently use a class helper function to get access to the private function
CellControlByRow. This function is defined as follows in FMX.Grid:

function TColumn.CellControlByRow(Row: Integer): TStyledControl;
begin
if (Grid <> nil) and Grid.IsSelected(Row) then
Result := CellControl
else
Result := nil;
end;

So you already recognizes that a return value is returned by the function only when just
the desired row in the grid - and thus the cell - is selected. Because only just this ensures,
that the cell already exists.

So we declare in our main form the following Class Helper:


type
THelperColumn = class helper for TColumn
procedure SetACell (aRow: Integer; AVAL: TValue);
function GetACell (aRow: Integer): TStyledControl;
end;

The implementations looks then as follows:



{ THelperColumn }
function THelperColumn.GetACell(aRow: Integer): TStyledControl;
begin
result := TStyledControl (CellControlByRow(aRow));
end;

procedure THelperColumn.SetACell(aRow: Integer; AVAL: TValue);
var
sc: TStyledControl;
begin
if self is TStringColumn then begin
sc := TStyledControl (CellControlByRow(aRow));
if sc <> NIL then begin
TTextCell (CellControlByRow(aRow)).Text := AVAL.tostring;
end;
end;

if self is TImageColumn then begin
sc := TStyledControl (CellControlByRow(aRow));
if sc <> NIL then begin
TImageCell (CellControlByRow(aRow)).Bitmap := AVAL.AsType<TBitmap>;
end;
end;
end;

We can now set each TStringColumn and every TImageColumn, for example, like this:

imgInfo.SetACell(0, Image1.Bitmap); // Bitmap in a TImageColumn
strSize.SetACell (0, Len: + intToStr (length (memo1.text))); // Text

Or just the way to go directly to get the whole StyledControl to set several properties:

TProgressbar (progressInfo.GetACell(0)).max := length (memo1.text);
TProgressbar (progressInfo.GetACell(0)).value := Memo1.PosToTextPos (Memo1.CaretPosition);

You may be wondering for the example of why both the TImageColumn and the
TSTringColumn can use the helper function SetACell although the Helper class was
introduced for the TColumn. This is just the magic of inheritance, because TColumn is
both the ancestor of TStringColumn as well as TImageColumn so that both successors
inherit the enhanced capabilities of the previous class.

It is really been a great thing, that. The class helpers has helped me in many cases where
otherwise only the component developer could deliver a solution, if he would change his
component (which understandably the most developers dont want).

You will need such direct access to a cell of a grid rather rare, but if it ever becomes
necessary in special cases (e.g. if you want to output certain information about the
currently selected cell), then you know how you can do it.

R29 Show 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
above or below a pop-up menu should be displayed. Here is an example where the user
clicks on a panel and then 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;

Where 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.


R30 Store additional information in standard objects


From the VCL you might know the property Tag, which has almost any object and
represents an integer value.
In FireMonkey you can set such a Tag value also, for example, with the Objectinspector.
But beyond that there are existing for an object or a control (TFMXObject) the properties
TagString, TagFloat and TagObject. 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 write only programmatically (i.e. not via the Objectinspector). Overall, a very
useful extension on FMX.

R31 Drag and drop text from external source (eg browser) to a TEdit box
At the request of a customer, I have integrated a function in my accounting program with
that one can drag a selected text from the browser on an edit field of the program and the
text is placed there.
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;

Apart from Windows Internet Explorer, this functionality is supported by most browsers
and indeed across platforms (e.g. even Safari on the MAC). Also, the drag and drop with
text works with many word processing programs.

R32 A column in a string grid should occupy the remaining space


You have a string grid with multiple columns and want at changes in size of the grid that
always 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 only work properly, if the grid has been
shown once, so the OnShow-event of the form has been run through completely once. For
this purpose, you must use a timer that you set enabled in the OnShow event with the
intervall of 25 ms. At the first time the timer event is triggered, you disable the timer and
make the desired calculation (later you do the calculations on the OnResize event of the
form or the grid).
It could look a whole as:
var
Form9: TForm9;
VarCol: Integer;

implementation

{$R *.fmx}

procedure TForm9.FormCreate(Sender: TObject);
begin
VarCol := 0; // fr die Erste
// VarCol := StringGrid1.ColumnCount-1; // z.B. fr die letzte Spalte
end;

function AColWidth(Grid: TStringGrid; VarCol:Integer): Extended;
var i:

integer;
aWidth: Extended;
begin
aWidth := 0;
for i := 0 to Grid.ColumnCount - 1 do
if i <> VarCol then aWidth := aWidth + Grid.Columns[i].Width + 1;

Result := Grid.ClientWidth - aWidth;
end;

procedure TForm9.FormResize(Sender: TObject);
begin
TStringColumn (StringGrid1.ColumnByIndex (VarCol)).width:= AColWidth(StringGrid1, VarCol);
end;

procedure TForm9.FormShow(Sender: TObject);
begin
Timer1.Enabled := True;
end;

procedure TForm9.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := false;
TStringColumn (StringGrid1.ColumnByIndex (VarCol)).width:= AColWidth(StringGrid1, VarCol);
end;

Chapter 7: Upgrading from Delphi XE3XE6 to XE7


For users who have already worked with Delphi XE3, I would like to give some hints
here:
Delphi XE7 and Installation
Since Delphi XE5 I work with the Enterprise version, because then it is easier to install the
Mobile-Pack, because the point is offered directly during the installation of Delphi
(besides of course there are other advantages in the Enterprise version).
Delphi XE4 I had purchased as a Professional version. In the Professional version, you
must first install and activate Delphi itself. Then start the setup program again, select
Upgrade (here still screenshots of XE4/5):

Click on the Next-switch and enter on the next page the serial number that you received
when you purchased the pack Mobile:

Check the serial number identifies the setup program now that you want to install the
Mobile-Pack and proceeds accordingly.
It could be, that it is changed in the meantime also for the Professional-version. Someone
told me, that you can also do the whole installation directly with the serial from the
Mobile-Pack.
Delphi XE7 and usable MAC OS X versions
Mountain Lion and Mavericks can be used as development platforms. But Yosemite goes
also. Lion does support the App Store not perfect and you can certain things on this
system not test, so you should at least working with Mountain Lion or Mavericks, at least
if you want to develop MAC OS X programs for the AppStore. All other systems should
of course be available and be used as a test system. I have an iMac, where on the system
disk even Leopard and Snow Leopard are installed. On a connected FireWire hard drive I
have installed on different partitions Lion, Mountain Lion, Mavericks and Yosemite.
In addition, I have a Mac Air notebook, where I can use Mountain Lion and Mavericks as
Fresh system (i.e. without any installed development environments that could distort
any test results. Physicists always say that the test environment itself has an impact on the
test result, thats right).

Very often, Apple changed anything on the OS and so it can sometimes be that something
does not work anymore in the development process. EMBA strives always here quickly to
provide hot fixes. Explore therefore from time to time in EMBA in the developer
community (EDN Developer Network).
Take over of projects XE3-XE6
For the transfer of your project from XE3-XE5 to XE7 you should first open it again in the
previous version of Delphi. There you should then close all the forms and display only the
project source code. Then you can save the project and close it. This approach is
recommended because otherwise Delphi may again use the old folder if you take over the
project to a different folder (I also rename the older folder, because sometimes Delphi tries
to open there units again). Then copy all the files to another folder, and open the project.
Then just adjust the paths under Project, Options, Directories !!
When you load the forms, it could be, that you get a bad representation of your forms. For
example, the registers of the TabControl could be much smaller, in the memo component
the cursor will not be displayed, if no text was in, and, and, and
Solution was to open the Styles component and select Remove All and read the style
again from the hard disk. Obviously, changes in the Styles been made which have an
influence on the function of the components. The FMX styles can be found on the hard
drive under:
C:\Program Files(x86)\Embarcadero\Studio\15.0\Redist\styles\FMX
After that, everything was so again, as it should be.
But it should be noted that here the loss of custom changes threatens that had possibly
made to the styles. Instead of Remove All you could try to select Add possibly. The
components look after again OK, if custom settings are retained, you need to test it by
yourself, I did not have any in here.
Other obstacles: TLabel and TButton unknown
Already using Delphi XE4 there has been a re-ordering of the functions and components
in the individual FireMonkey units.
A description of the details can be found here:
http://docwiki.embarcadero.com/RADStudio/XE4/en/Refactored_FireMonkey_Classes_in_XE4
Button and labels are now locates in the unit FMX.StdCtrls, this you must add to the
Uses as a unit. Delphi is here sometimes a little overeager and then automatically adds
additional units sometimes twice. So dont wonder if you have the appropriate error
messages during compilation, but just then remove the duplicate entries.
In addition, the program has bitched for the Windows platform that
Application.mainform.handle is not compatible with TWindowHandle. I could
currently only deal with a work-around, as I have here simply replaced as
Application.mainform.handle with 0. But this cant be a permanent solution.
To use the TFillTextFlags in connection with the print function, I had to include the unit
FMX.Graphics, also a consequence of the above-mentioned reorganization. So if

something is declared as unknown, rember of this reordering and search the missing
untis and adjust it accordingly.
Problems with the multiview Designer (Fire-UI)
Before Delphi XE7, you add a platform in the Project window. In XE7 by creating a
Multi Device Application the other platforms are available as targets automatically:

In a project taken from Delphi before XE7, the project list looks like this:

So the other platforms are missing.


And it does not help, if you try to choose another style (e.g. IOS or Android) from Viewdropdownlist. This will end only with an error message.
The solution is again to re-create the project. First, copy the text content of the project file
(DPR) to the clipboard and then create a new project with the same name again. Than
replace the new text in the DPR file with the older from the clipboard. Now you have
available in the project window also the other targets (Android, iOS) and you can expand
your project to one of the platforms if required.

Chapter 8: Outlook

Other topics
The book is still in the expansion phase, for a period of time other topics will be added.
For now Iam converting more vcl-projects to fmx-projects. So I will add my experience
here when I update this book (so look sometimes at amazon at the time and date of the
book).

Perhaps there is also a special topic, where you get stuck. If it is a point that might fit well
here in this book, do not hesitate to send me an e-mail with a description of the problem. I
will be happy to check whether I can take it as a topic here - with an appropriate solution.

If you should noticed obvious errors or outdated information in the book, I would also
appreciate a note.

Attachment 1: Unit HSW.FMXSandbox.pas



unit HSW.FMXSandbox;

{ ** Copyright by Harry Stahl Software, Bonn, www.hastasoft.de ** }
{ ** People who buyed my ebook Cross-Platform Development mit Delphi XE7 }
{ ** & FireMonkey fr Windows & MAC OSX are free to use this unit }
{ ** Others may ask me }
{ ** Use it at your own risk, the author will not be responsible for any }
{ ** damages }
{ ** Dont forget to Define UseSandbox in your compiler conditions }
{ ** And add the entitlement key }
{ ** com.apple.security.files.bookmarks.app-scope }
{ ** to your Entitlement-file }

interface

uses
System.SysUtils, System.Classes, FMX.Dialogs,
Macapi.CoreFoundation, Macapi.ObjectiveC, MACAPI.coreservices,
MacApi.AppKit, Macapi.CocoaTypes, MacApi.Foundation;

type
NSURL = interface(NSObject)
//[{BB3BDECA-2E3A-4326-BDD8-6C339A277E34}] Original aus MACApi.Foundation
// einge eigene Implementation ist leider erforderlich, weil diese Funktionen
// dringend fr die Sandbox-Funktionalitt bentigt werden, in der MACApi-Unit
// aber leider vergessen wurden.
// Falls das in einem der nchsten Delphi-Updates nachgeholt wird, kann man diese
// Implementierung wieder entfernen
[{4997B641-85B2-4BE9-A9A9-45F64CE34955}]

function startAccessingSecurityScopedResource: Boolean; cdecl;
function stopAccessingSecurityScopedResource: Boolean; cdecl;
end;


TNSURL = class(TOCGenericImport<NSURLClass, HSW.FMXSandbox.NSURL>) end;

function AppScopedBookMarksEnabled: boolean;
function CreateAppScopedBookMark (var data: NSDATA; URL: MacApi.Foundation.NSURL): Boolean;

function GetResolvedAppScopedBookMark (data: NSData; var NEWURL: NSURL): Boolean;
function CanStartAccessingSecurityScopedResource (url: NSURL): boolean;
function CanStopAccessingSecurityScopedResource (url: NSURL): boolean;

function GetAppScopedBookMarkFromList (ADir: String; var data: NSData): Boolean;
function ExistsAppScopedBookmarkForRes (ADir: String): Boolean;

function GetAppScopedAccessToRes (ADir: String; var url: NSURL): Boolean;
function GetBaseAppScopedAccessToRes (ADir: String; var url: NSURL): Boolean;

const
// Diese Konstanten werden zum Aufruf der Bookmark-Funktionen bentigt
// ein weiterer Punkt, wo man die MACApi.Foundation erweitern msste
NSURLBookmarkCreationWithSecurityScope: NSUInteger = (1 shl 11);
NSURLBookmarkResolutionWithSecurityScope: NSUInteger = (1 shl 10);
NSURLBookmarkResolutionWithoutMounting : NSUInteger = (1 shl 9);
NSURLBookmarkResolutionWithoutUI : NSUInteger = (1 shl 8);

// Setzen Sie diese Variable auf True, wenn Sie im Debugmodus
// erweiterte Informationen erhalten wollen, wann etwas funktioniert
// und wann nicht.
ShowDebugInfos: Boolean = false;

var
// Liste, welche die Ressourcen (=Ordner oder Dateien) und die damit
// verknpften Bookmarkdateien enthlt
slBookMarks: TStringList;

// Variable fr den Dateinnamen der Bookmark-Liste
SandboxAppScopeBookmarksfile: string;

// Ordernamen fr Orter der Bookmark-Liste und die Bookmark-Dateien

SandboxAppSupportPath: string;
SandBoxBookmarksFolder: string;

{$IFDEF UseSandbox}
UseSandboxing: Boolean = True;
{$ELSE}
UseSandboxing: Boolean = false;
{$ENDIF}

implementation

// Diese Funktion muss aufgerufen werden, bevor man eine
// Ressource auerhalb der Sandbox nutzen will
// Url enthlt dabei den Namen des Ordners oder der Datei
function CanStartAccessingSecurityScopedResource (url: NSURL): boolean;
begin
Result := false;
if Url <> NIL then begin
try
if not URL.startAccessingSecurityScopedResource then begin
{$IFDEF Debug}
if ShowDebugInfos then ShowMessage (StartAccessing erfolglos);
{$ENDIF}
end else begin
Result := True;
end;
except
{$IFDEF Debug}
if ShowDebugInfos then ShowMessage (Fehler in StartAccessing);
{$ENDIF}
end;
end;
end;

// Diese Funktion muss aufgerufen werden, wenn der Zugriff auf eine
// Ressource auerhalb der Sandbox beendet werden soll
// Achtung: Die Aufrufe der start und stop funktionen mssen ausbalanciert sein!
// Ist das nicht der Fall, verliert die Anwendung whrend der aktuellen Laufzeit

// die Fhigkeit Security Scoped Bookmarks nutzen zu knnen


function CanStopAccessingSecurityScopedResource (url: NSURL): boolean;
begin
Result := false;
if Url <> NIL then begin
try
if not URL.stopAccessingSecurityScopedResource then begin
ShowMessage (Kann nicht stoppen);
end else begin
Result := True;
//ShowMessage (Has Access);
end;
except
ShowMessage (Fehler in stopAccessingSecurityScopedRessource);
end;
end;
end;

// Prft ob das benutzte OS Betriebssystem berhaupt in der Lage ist,
// Security Scoped Bookmarks zu erzeugen
// Das ist erst ab Lion, Version 10.7.3 mglich
function AppScopedBookMarksEnabled: boolean;
begin
Result := false;

if TOSVersion.Major > 10 then begin
Result := true;
end;

if TOSVersion.Major >= 10 then begin
if (TOSVersion.Minor >= 8) then begin
Result := true;
end;

if (TOSVersion.Minor = 7) then begin
if (TOSVersion.ServicePackMajor >=3) then begin
Result := true;
end;

end;
end;
end;

// Legt eine App-Scoped Security Bookmark an, Ressource kann dabei
// ein Ordner oder eine Datei sein
function CreateAppScopedBookMark (var data: NSDATA; URL: MacApi.Foundation.NSURL): Boolean;
var
err2: NSError;
includingResourceValuesForKeys: NSArray;
relativeToURL, AURL: MacApi.Foundation.NSURL;
ag: System.TGuid;
aGuid, ADir: string;
begin
Result := False;

if URL = NIL then exit;

// Prfen, ob hierfr schon ein Eintrag besteht, dann nicht noch mal anlegen
ADir := URL.path.UTF8String;
if GetAppScopedBookMarkFromList (ADir, data) then begin
Result := True;
exit;
end;

// Bookmark existiert noch nicht, daher anlegen
err2 := TNSError.Create;
err2 := NIL;

includingResourceValuesForKeys:= NIL;
relativeToURL:= NIL;

Data := URL.bookmarkDataWithOptions(
NSURLBookmarkCreationWithSecurityScope,
includingResourceValuesForKeys,
relativeToURL, // NIL = App-Scope
@Err2);

try
if not Assigned (err2) then begin
Result := True;
if Data <> NIL then begin

CreateGuid (ag);
aGuid := GUIDToString(ag);

slBookmarks.Add (URL.path.UTF8String + #1 + aGuid);

aGuid := IncludeTrailingPathDelimiter(SandBoxBookmarksFolder) + aGuid;

// Bookmark in das Bookmarkverzeichnis speichern
Data.writeToFile(NSSTr (aGuid), true);

// Liste der Bookmarks speichern
slBookmarks.SaveToFile(SandboxAppScopeBookmarksfile);
end else begin
Result := false;
{$IFDEF DEBUG}
if ShowDebugInfos then ShowMessage (Data in CreateBookmark is NIL);
{$ENDiF}
end;
end else begin
Result := false;
{$IFDEF DEBUG}
if ShowDebugInfos then ShowMessage (ERR in CreateBookmark is <> NIL);
{$ENDIF}
end;
except
{$IFDEF DEBUG}
if ShowDebugInfos then ShowMessage (Problem with ERR in CreateBookmark);
{$ENDIF}
end;
end;

// Diese Funktion erhlt als Input das Data-Objekt, welches die Bookmark enthlt
// Die ResolveBookmark-Funktion liefert dann die Ressource zurck,

// die dann spter mit dem einleitenden StartAccessing genutzt werden kann
function GetResolvedAppScopedBookMark (data: NSData; var NEWURL: NSURL): Boolean;
var
Err: NSError;
relativeToURL: MACAPI.Foundation.NSURL;
begin
Result := False;

err := TNSError.Create;
err := NIL;
RelativeToURL := NIL;
NewURL := NIL;

NewUrl := TNSURL.Wrap (TNSURL.OCClass.URLByResolvingBookmarkData(
Data,
NSURLBookmarkResolutionWithSecurityScope,
relativeToURL,
0,
@Err));

if (NewUrl <> NIL) and (not Assigned (err)) then begin
Result := True;
end else begin
{$IFDEF DEBUG}
if ShowDebugInfos then showMessage (ERR <> NIL in ResolvedBookmark);
{$ENDIF}
end;
end;

// Diese Hilfsfunktion prft, ob fr die angeforderte Ressource zuvor
// schon mal eine Bookmark erzeugt wurde und liefert in diesem Fall das entsprechende
// Data-Objekt (mit der Bookmark) zurck
// Hinweis: ADir kann ein Ordner oder eine Datei sein
function GetAppScopedBookMarkFromList (ADir: String; var data: NSData): Boolean;
var
P, L: Integer;
Dir, AMark: string;
begin

Result := false;

for L := 0 to slBookmarks.Count-1 do begin
P := Pos (#1, slBookMarks[L]);

if P <> 0 then begin
Dir := Copy (slBookMarks[L], 1, P-1);
AMark := Copy (slBookMarks[L], P+1, 5000);
AMark := IncludeTrailingPathDelimiter (SandBoxBookmarksFolder) + AMark;
end;

if Dir = ADir then begin
data := TNSDATA.Wrap (TNSData.OCClass.dataWithContentsOfFile (NSSTr(AMark)));
Result := True;
break;
end;
end;
end;

// Diese Hilfsfunktion prft, ob fr eine Ressource, die einen Ordner reprsentiert
// bereits ein bergeordneter Ordner mit einer Bookmark existiert. Falls ja,
// wird die Bookmark im Data-Objekt zurckgeliefert
// Da von dieser Ressource / diesem Ordner Zugriffe auf alle Unterordner
// mglich sind, muss man nicht fr dutzende von Unterordner Bookmarks
// anlegen.
function GetBaseAppScopedBookMarkFromList (ADir: String; var data: NSData): Boolean;
var
P, L, start: Integer;
Dir, AMark: string;
begin
Result := false;

for L := 0 to slBookmarks.Count-1 do begin
P := Pos (#1, slBookMarks[L]);

if P <> 0 then begin
Dir := Copy (slBookMarks[L], 1, P-1);
AMark := Copy (slBookMarks[L], P+1, 5000);

AMark := IncludeTrailingPathDelimiter (SandBoxBookmarksFolder) + AMark;


end;

start := pos (Ansilowercase (Dir), Ansilowercase (ADir));
if start = 1 then begin
data := TNSDATA.Wrap (TNSData.OCClass.dataWithContentsOfFile (NSSTr(AMark)));
Result := True;
break;
end;
end;
end;

// Prft, ob eine Bookmark fr die angefragte Ressource existiert
// ADir kann ein Ordner oder eine Datei sein
function ExistsAppScopedBookmarkForRes (ADir: String): Boolean;
var
P, L: Integer;
Dir, AMark: string;
NS: NSString;
begin
Result := false;

for L := 0 to slBookmarks.Count-1 do begin
P := Pos (#1, slBookMarks[L]);

if P <> 0 then begin
Dir := Copy (slBookMarks[L], 1, P-1);
end;

if Dir = ADir then begin
Result := True;
break;
end;
end;
end;

// Diese Funktion vereinfacht den Zugriff auf Ressoruce per App-Scoped Bookmark,
// in dem mehrere Schritte in einer Funktion zusammengefasst werden und

// somit das Handling deutlich vereinfacht wird.


// Nach dem Aufruf dieser Funktion und der Nutzung der Ressource
// muss im Programmablauf die Funktion
// CanStopAccessingSecurityScopedResource aufgerufen werden,
// damit die start und stop Aufrufe wieder ausbalanciert sind!!
function GetAppScopedAccessToRes (ADir: String; var url: NSURL): Boolean;
var
Data: NSData;
begin
Result := false;

if GetAppScopedBookMarkFromList (ADir, data) then begin
url := HSW.FMXSandbox.TNSURL.Create;

if GetResolvedAppScopedBookMark (Data, url) then begin
if CanStartAccessingSecurityScopedResource (url) then begin
Result := True;
end else begin
{$IFDEF DEBUG}
if ShowDebugInfos then begin
ShowMessage (Kein Zugriff auf das Directory mglich. Bitte erneut mit ffnen-Dialg auswhlen.);
end;
{$ENDIF}
end;
end;
end;
end;

// Wie die Funktion zuvor, nur dass bei Ordnern geprft wird,
// ob bereits eine Bookmark fr einen bergeordneten Ordner besteht
// und diese verwendet werden soll
// Aufruf macht je nach Programmlogik Sinn
function GetBaseAppScopedAccessToRes (ADir: String; var url: NSURL): Boolean;
var
Data: NSData;
begin
Result := false;

if GetBaseAppScopedBookMarkFromList (ADir, data) then begin


url := HSW.FMXSandbox.TNSURL.Create;

if GetResolvedAppScopedBookMark (Data, url) then begin
if CanStartAccessingSecurityScopedResource (url) then begin
Result := True;
end else begin
{$IFDEF DEBUG}
if ShowDebugInfos then begin
ShowMessage (Kein Zugriff auf das Directory mglich. Bitte erneut mit ffnen-Dialg auswhlen);
end;
{$ENDIF}
end;
end;
end;
end;

Initialization
if AppScopedBookMarksEnabled then begin
// Wenn die Nutzung der AppscopedBookmarks vom System untersttzt werden
// hier schon mal die Stringliste zur Verwaltung der Bookmarks-Liste
// und die entsprechenden Ablageorte erzeugen

SandboxAppSupportPath := IncludeTrailingPathDelimiter(GetHomePath) + Library +
PathDelim + Application Support + PathDelim + ChangeFileExt (ExtractFileName (paramstr (0)), );

if not DirectoryExists (SandboxAppSupportPath) then begin
ForceDirectories(SandboxAppSupportPath);
end;

SandBoxBookmarksFolder := IncludeTrailingPathDelimiter (SandboxAppSupportPath) + Bookmarks;

if not DirectoryExists (SandBoxBookmarksFolder) then begin
ForceDirectories(SandBoxBookmarksFolder);
end;

SandboxAppScopeBookmarksfile := IncludeTrailingPathDelimiter (SandboxAppSupportPath) +
AppScopeBookmarks.dat;


slBookMarks := TStringList.Create;
slBookMarks.Duplicates := dupIgnore;

if FileExists (SandboxAppScopeBookmarksfile) then begin
slBookMarks.LoadFromFile(SandboxAppScopeBookmarksfile);
end;
end else begin
UseSandboxing := false;
end;

Finalization
if AppScopedBookMarksEnabled then begin
slBookMarks.Free;
end;
end.

Atachment 2: Newly implemented Open and Save dialogs


for sandboxing

Below you will find the my current implementations of the Open and Save dialogs, and a
directory selection dialog. These dialogues allow sandboxing.

This function must be implemented in a different unit than the HSW.FMXSandbox.pas


own, since the NSURL functions are used, which are implemented in the
MACApi.Foundation.pas and not my alternative implementation. It would be possible,
purely to increase these functions in the sandbox unit, one would have in the
corresponding NSURL functions specify the MACApi.Foundation unit scope. I have also
done so not because I need just as in my own program and Unit systematics.


{$IFDEF MACOS}
// Function to select a folder to create the possibility of a bookmark
function MACSelectDirectory(const ATitle: string; var ADir: string; CreateBookMark: Boolean): Boolean;
var
LOpenDir: NSOpenPanel;
LInitialDir: NSURL;

LDlgResult: NSInteger;
Data: NSData;
begin
Result := False;
LOpenDir := TNSOpenPanel.Wrap(TNSOpenPanel.OCClass.openPanel);
LOpenDir.setAllowsMultipleSelection(False);
LOpenDir.setCanChooseFiles(False);
LOpenDir.setCanChooseDirectories(True);
LOpenDir.setPrompt (NSSTR(Auswhlen));

//LOpenDir.setCanCreateDirectories(True);
if ADir <> then
begin
LInitialDir := TNSURL.Create;
LInitialDir.initFileURLWithPath(NSSTR(ADir));
LOpenDir.setDirectoryURL(LInitialDir);
end;

if ATitle <> then LOpenDir.setTitle(NSSTR(ATitle));

LOpenDir.retain;
try
LDlgResult := LOpenDir.runModal;
if LDlgResult = NSOKButton then
begin
if LOpenDir.URL <> NIL then begin
ADir := LOpenDir.URL.path.UTF8String;
end else begin
if LOpenDir.URLs.objectAtIndex(0) <> NIL then begin
ADir := string(TNSUrl.Wrap(LOpenDir.URLs.objectAtIndex(0)).relativePath.UTF8String);
end;
end;

if FileExists (ADir) then begin
ADir := ExtractFilePath (ADir);
end;

Result := True;


{$IFDEF UseSandbox}
if (AppScopedBookMarksEnabled) and (CreateBookMark) then begin
CreateAppScopedBookMark(Data, LOpenDir.URL);
end;
{$ENDIF}
end;
finally
LOpenDir.release;
end;
end;


// Function to select the file (open) with the ability to create a bookmark
function MACSelectFile(const ATitle: string; var AFile: string; CreateBookMark: Boolean): Boolean;
var
LOpenDir: NSOpenPanel;
LInitialDir: NSURL;
LDlgResult: NSInteger;
Data: NSData;
begin
Result := False;
LOpenDir := TNSOpenPanel.Wrap(TNSOpenPanel.OCClass.openPanel);
LOpenDir.setAllowsMultipleSelection(False);
LOpenDir.setCanChooseFiles(True);
LOpenDir.setCanChooseDirectories(false);
LOpenDir.setPrompt (NSSTR(ffnen));
//LOpenDir.setCanCreateDirectories(True);
if AFile <> then
begin
LInitialDir := TNSURL.Create;
//LInitialDir.initFileURLWithPath(NSSTR(ExtractFilePath (AFile)));
if FileExists (AFile) then begin
LInitialDir := TNSURL.Wrap (TNSURL.OCClass.fileURLWithPath (NSSTR(AFile)));
end else begin
LInitialDir := TNSURL.Wrap (TNSURL.OCClass.fileURLWithPath (NSSTR(ExtractFilePath (AFile))));
end;

LOpenDir.setDirectoryURL(LInitialDir);
end;
if ATitle <> then
LOpenDir.setTitle(NSSTR(ATitle));
LOpenDir.retain;
try
LDlgResult := LOpenDir.runModal;
if LDlgResult = NSOKButton then
begin
AFile := string(TNSUrl.Wrap(LOpenDir.URLs.objectAtIndex(0)).relativePath.UTF8String);
Result := True;

{$IFDEF UseSandbox}
if (AppScopedBookMarksEnabled) and (CreateBookMark) then begin
CreateAppScopedBookMark(Data, LOpenDir.URL);
end;
{$ENDIF}
end;
finally
LOpenDir.release;
end;
end;


// Function to choose a name for a file with the possibility of this file
// Create and bookmark it
function MACSaveFile(const ATitle: string; var AFile: string; CreateBookMark: Boolean): Boolean;
var
panelSaveDir: NSSavePanel;
pnInitialDir: NSURL;
LDlgResult: NSInteger;
Data: NSData;
Error: NSError;
begin
Result := False;
panelSaveDir := TNSSavePanel.Wrap(TNSSavePanel.OCClass.SavePanel);
panelSaveDir.setNameFieldStringValue (NSSTr (ExtractFileName (AFile)));
panelSaveDir.setAllowsOtherFileTypes (True);

panelSaveDir.setPrompt (NSSTR(Sichern));
panelSaveDir.setNameFieldLabel(NSStr(Sichern als:));

if AFile <> then begin
pnInitialDir := TNSURL.Create;
if FileExists (AFile) then begin
pnInitialDir := TNSURL.Wrap (TNSURL.OCClass.fileURLWithPath (NSSTR(ExtractFilePath(AFile))));
end else begin
pnInitialDir := TNSURL.Wrap (TNSURL.OCClass.fileURLWithPath (NSSTR(ExtractFilePath (AFile))));
end;
panelSaveDir.setDirectoryURL(pnInitialDir);
end;

if ATitle <> then
panelSaveDir.setTitle(NSSTR(ATitle));
panelSaveDir.retain;
try
LDlgResult := panelSaveDir.runModal;

if LDlgResult = NSOKButton then begin

//AFile := string(TNSUrl.Wrap(panelSaveDir.URLs.objectAtIndex(0)).relativePath.UTF8String);
if panelSaveDir.directoryURL <> NIL then begin
AFile := IncludeTrailingPathDelimiter (panelSaveDir.directoryURL.path.UTF8String) +
panelSaveDir.nameFieldStringValue.UTF8String;
end else begin
if panelSaveDir.URL <> NIL then begin
AFile := panelSaveDir.URL.path.UTF8String;
end;
end;
Result := True;

{$IFDEF UseSandbox}
if (AppScopedBookMarksEnabled) and (CreateBookMark) then begin
// Zunchst muss die Datei erzeugt werden, da etwas vorhanden
// sein muss, wo die Bookmark anknpfen kann.
if panelSaveDir.directoryURL <> NIL then begin
if not FileExists (AFile) then begin

NSStr (AFile).writeToFile (NSStr (AFile), false);


end;
end else begin
if panelSaveDir.URL <> NIL then begin
if not FileExists (panelSaveDir.URL.path.UTF8String) then begin
panelSaveDir.URL.path.writeToFile (panelSaveDir.URL.path, false);
end;
end;
end;

if panelSaveDir.directoryURL <> NIL then begin
CreateAppScopedBookMark(Data, panelSaveDir.URL);
end;
end;
{$ENDIF}
end;
finally
panelSaveDir.release;
end;
end;

{$ENDIF}

Вам также может понравиться