Sei sulla pagina 1di 44

F O R D E L P H I, L A Z A R U S, A N D P A S C A L

R E L A T E D L A N G U A G E S / A N D R O I D,
I O S, M A C , W I N D O W S & L I N U X
P R I N T E D, P D F, & O N L I N E V I E W

DX

BLAISE PASCAL MAGAZINE 60

MVVM DELPHI APPLICATIONS PART 2


BY OLAF MONIEN
M2M MESSAGING WITH DELPHI OR LAZARUS AND MQTT
BY BRUNO FIERENS
WORK WITH WINEHQ, WHAT IS IT?
MAXBOX, BY MAX KLEINER
DISPLAYING ASSOCIATED FILE ICONS IN DELPHI
BY MICHAEL VAN CANNEYT
ROBOTICA, SMART ROBOT CAR KIT
BY BOIAN MITOV
BUILDING A TODO LIST AS EXAMPLE:
CLIENT DATA SET EVALUATED: PART 1,
BY DELPHI, FIREDAC AND KBMMW
BY DETLEF OVERBEEK

PRINTED ISSUE PRICE € 15,00

DOWNLOAD ISSUE PRICE € 5,00


Delphi & Pascal Events 2017
May 16. Jun 14.+15.
HowToUse Day Developer
for Intraweb in Experts Days in
Frankfurt, Geneva,
Germany (German Switzerland
event) (English event)
Sep 21.
Delphi and
Pascal Days in
Madrid, Spain
Sep 28.+29.
Sep 30. (English &
Developer
Delphi and Spanish event)
Experts Days in
Pascal Days in Frankfurt,
Frankfurt, Germany
Germany (German event)
(German &
English event)

November October 11.


Developer Delphi and
Experts Days in
Pascal Days in
Oslo, Norway
Dusseldorf,
(English event)
Germany
(German event)
date to be announced

2 Experts, Developer Experts Days, HowToUse Day,Issue


Developer DelphiNr
and
10Pascal
2016 Days are brand
BLAISE namesMAGAZINE
PASCAL of
Unternehmensberatung Monien. Delphi and Intraweb are brand names of their respective owners. Event topics and dates
are subject to change without further notice. For detailed information visit www.developer-experts.net
BLAISE PASCAL MAGAZINE 60
D E L P H I, L A Z A R U S, S M A R T M O B I L E S T U D I O,
A N D P A S C A L R E L A T E D L A N G U A G E S
F O R A N D R O I D, I O S, M A C, W I N D O W S & L I N U X

CONTENTS
ARTICLES:

M2M MESSAGING WITH DELPHI OR LAZARUS AND MQTT PAGE 6


BY BRUNO FIERENS
WORK WITH WINEHQ, WHAT IS IT? PAGE 10
MAXBOX, BY MAX KLEINER
DISPLAYING ASSOCIATED FILE ICONS IN DELPHI PAGE 14
BY MICHAEL VAN CANNEYT
MVVM DELPHI APPLICATIONS PART 2 PAGE 19
BY OLAF MONIEN
BUILDING A TODO LIST AS EXAMPLE CLIENT DATA SET EVALUATED: PART 1,
BY DELPHI, FIREDAC AND KBMMW PAGE 24
BY DETLEF OVERBEEK
ROBOTICA, SMART ROBOT CAR KIT PAGE 33
BY BOIAN MITOV

MINI DRONE ARTIFICIAL BEE

Will the Bee be replaced by a Drone for Pollinating purposes


or will the artificial insect - robot become the new Pollination Bee?

ADVERTISERS
ANDROID LOGCAT FREE TOOL COMPONENTS4DEVELOPERS PAGE 14
BARNSTEN PAGE 5
COMPONENTS4DEVELOPERS PAGE 44
DELPHI & PASCAL EVENTS PAGE 2
DEVELOPERS EXPERTS PAGE 22
ONLINE VIEW BLAISE PASCAL MAGAZINE PAGE 18

Publisher: Foundation for Supporting the Pascal Programming Language


in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
© Stichting Ondersteuning Programmeertaal Pascal

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 3


Stephen Ball Peter Bijlsma -Editor Dmitry Boyarintsev
http://delphiaball.co.uk peter @ blaisepascal.eu dmitry.living @ gmail.com
@DelphiABall

Michaël Van Canneyt, Marco Cantù David Dirkse


michael @ freepascal.org www.marcocantu.com www.davdata.nl
marco.cantu @ gmail.com E-mail: David @ davdata.nl

Benno Evers Bruno Fierens Primož Gabrijelčič


b.evers www.tmssoftware.com www.primoz @ gabrijelcic.org
@ everscustomtechnology.nl bruno.fierens @ tmssoftware.com

Fikret Hasovic Cary Jensen Peter Johnson


fhasovic @ yahoo.com www.jensendatasystems.com http://delphidabbler.com
http://caryjensen.blogspot.nl delphidabbler@gmail.com

Max Kleiner John Kuiper Wagner R. Landgraf


www.softwareschule.ch john_kuiper @ kpnmail.nl wagner @ tmssoftware.com
max @ kleiner.com

Kim Madsen Andrea Magni Boian


Kim Madsen
Mitov
kbm @ components4developers.com www.andreamagni.eu mitov
www.component4developers
@ mitov.com
andrea.magni @ gmail.com
www.andreamagni.eu/wp
Olaf MONIEN Paul Nauta PLM Solution Architect Jeremy North
olaf@developer-experts.net CyberNautics jeremy.north @ gmail.com
paul.nauta@cybernautics.nl
Detlef Overbeek - Editor in Chief Howard Page Clark Heiko Rompel
www.blaisepascal.eu hdpc @ talktalk.net info@rompelsoft.de
editor @ blaisepascal.eu

Wim Van Ingen Schenau -Editor Peter van der Sman Rik Smit
wisone @ xs4all.nl sman @ prisman.nl rik @ blaisepascal.eu
www.romplesoft.de

Bob Swart B.J. Rao Daniele Teti


www.eBob42.com contact@intricad.com www.danieleteti.it
Bob @ eBob42.com d.teti @ bittime.it

Anton Vogelaar Siegfried Zuhr


ajv @ vogelaar-electronics.com siegfried @ zuhr.nl

Editor - in - chief
Detlef D. Overbeek, Netherlands Tel.: +31 (0)30 890.66.44 / Mobile: +31 (0)6 21.23.62.68
News and Press Releases email only to editor@blaisepascal.eu

Editors
Peter Bijlsma, W. (Wim) van Ingen Schenau, Rik Smit,
Correctors
Howard Page-Clark, James D. Duff
Trademarks
All trademarks used are acknowledged as the property of their respective owners.
Caveat Whilst we endeavour to ensure that what is published in the magazine is correct, we cannot accept responsibility for any errors or omissions.
If you notice something which may be incorrect, please contact the Editor and we will publish a correction where relevant.
Subscriptions ( 2013 prices )
1: Printed version: subscription € 80.-- Incl. VAT 6 % (including code, programs and printed magazine,
10 issues per year excluding postage).
2: Electronic - non printed subscription € 50.-- Incl. VAT 21% (including code, programs and download magazine)

Subscriptions can be taken out online at www.blaisepascal.eu or by written order, or by sending an email to office@blaisepascal.eu
Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well.
Subscriptions run 365 days. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email.
Subscriptions can be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863 or by credit card: Paypal
Name: Pro Pascal Foundation-Foundation for Supporting the Pascal Programming Language (Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147 (Stichting Programmeertaal Pascal)
Subscription department Edelstenenbaan 21 / 3402 XA IJsselstein, The Netherlands / Tel.: + 31 (0) 30 890.66.44 / Mobile: + 31 (0) 6 21.23.62.68
office@blaisepascal.eu

Copyright notice
All material published in Blaise Pascal is copyright © SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may
not be copied, distributed or republished without written permission. Authors agree that code associated with their articles will be made
available to subscribers after publication by placing it on the website of the PGG for download, and that articles and code will be placed on
distributable data storage media. Use of program listings by subscribers for research and study purposes is allowed, but not for commercial
purposes. Commercial use of program listings and code is prohibited without the written permission of the author.

4 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


• development tools • training
barnsten • consultancy
• components
• hands-on workshops
• support

NOW AVAILABLE FROM STOCK!


THE NEW RAD STUDIO
10.2 TOKYO

Order directly from our shop with 10% discount on Enterprise (Upgrade) licenses and
15% on Architect (Upgrade) licenses till March 31, 2017.

www.barnsten.com .
5
Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 5
M2M MESSAGING PAGE 1/4 BY BRUNO FIERENS
WITH DELPHI OR LAZARUS AND MQTT
starter expert
DX & Lazarus
Delphi Now, whenever another client sends a message
for this topic, this message will be received by
INTRODUCTION
our client and the event
MQTT is the leading machine-to-machine (m2m) TTMSMQTTClient.OnPublishReceived()
messaging protocol and comprises a lightweight
will be triggered.
publish/subscribe messaging transport, ideal for small
code footprint IoT apps. This event returns the message packet ID, the
With the TMS MQTT Client component topic the message was sent for and the payload
http://www.tmssoftware.com/site/tmsmqtt.asp
(actual message data). The payload is an array of
we developed, it now just became extremely easy to
bytes holding the message content. This simple
tap into the power of m2m and introduce messaging
based communication between IoT devices, event handler will extract the message as string:
mobile devices, desktop & web applications procedure TForm1.TMSMQTTClient1PublishReceived(
(thanks to websocket). Our TMS MQTT Client can be ASender: TObject;
used from VCL, FMX and LCL applications, thus can APacketID: Word; ATopic: string;
target Windows, macOS, iOS, Android, Linux, APayload: TArray<system.byte>);
Raspbian. var msg: string;
begin
msg := TEncoding.UTF8.GetString(APayload);
CONCEPTS end;
MQTT is a publish/subscribe messaging
transport protocol. Messages can contain any Now, this is all to get the receiving side working.
type of data: binary or text. MQTT messages are Sending the message is equally simple. You can
routed via a broker. just call the TTMSMQTTClient.Publish() method
A broker is responsible for delivering the right which offers overloads for sending a text
message to the right clients. message or a binary message
A client can send a message for a given topic
and a client can inform the broker it is listening TMSMQTTClient1.Publish(
to a topic or range of topics by using wildcards. 'tms/mytopic', 'my first MQTT text message');
For full information about the MQTT protocol, A chat between mobile and desktop applications
see: http://docs.oasis-open.org/mqtt/ Let's put this knowledge together and implement
mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
an MQTT based chat client that will allow us to
Our TMS TTMSMQTTClient implements setup a chat between a mobile device app and a
the full MQTT protocol specification, including desktop app. We'll create a FireMonkey
the keep alive messages, auto reconnect, quality application for this so we can use exactly the
of service settings, … same code for the mobile app as for the desktop
Note that a broker is crucial and several free and
app. For this sample, we'll use the (free)
commercial brokers exist. An overview of Mosquitto MQTT test broker so we do not need to
brokers can be found here: install an MQTT broker ourselves.
https://github.com/mqtt/mqtt.github.i
o/wiki/servers
To connect to the broker and subscribe for the
GETTING STARTED
topic 'tms/chat' in this case, following code is
Setup is not more than dropping the
used:
TTMSMQTTClient component on the form and
defining the broker via begin
TMSMQTTClient1.BrokerHostName and call TMSMQTTClient1.BrokerHostName :=
TTMSMQTTClient.Connect. 'test.mosquitto.org';
When a successful connection could be made to TMSMQTTClient1.Connect();
the broker, the end;
TTMSMQTTClient.OnConnectedStatusChang
procedure
ed() event is triggered.
TForm1.TMSMQTTClient1ConnectedStatusChanged(
In this event, we can subscribe to a topic or Asender: TObject;
range of topics: const AConnected: Boolean;
procedure Astatus: TTMSMQTTConnectionStatus);
TForm1.TMSMQTTClient1ConnectedStatusChanged( begin
ASender: TObject; if AConnected then
const AConnected: Boolean; TMSMQTTClient1.Subscribe('tms/chat');
AStatus: TTMSMQTTConnectionStatus); end;
begin if AConnected then
TMSMQTTClient1.Subscribe('tms/mytopic');
end;

6 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


M2M MESSAGING WITH DELPHI OR LAZARUS AND MQTT PAGE 2/4

To send a text to the chat, we use a FireMonkey When we put this all together in a FireMonkey
TMemo control and upon a button click, the text application , the result becomes:
can be sent as chat message.
To know the originator of the message,
the unique TTMSMQTTClient.ClientID will
be used as prefix for the message. With this
unique ID of the client, we can determine upon
receipt of a message whether this message was
sent from the app itself or from another app
with which the chat is communicating as all
chat apps subscribe to the same topic and thus
receive all topic messages, including the own
sent messages on this topic. So, to send the
message, we use:
TMSMQTTClient1.Publish('tms/chat',
TMSMQTTClient1.ClientID+'!'+ memo1.Lines.Text);

For receiving messages, we use a FireMonkey


TListBox and one special feature we want to
add here is showing the received messages from
other chat clients left aligned and own sent
messages right aligned.
Fortunately, the FireMonkey TListBox allows us
to do this quite easily with the code:
procedure TForm1.AddMessage(AMessage: string;
AlignRight: boolean);
var li: Tlistboxitem;
begin
li := Tlistboxitem.Create(self);
li.StyledSettings :=
li.StyledSettings - [TStyledSetting.ssOther];
li.Text := AMessage;
li.Height := 22;
li.VertTextAlign := TTextAlign.taTrailing;
if AlignRight then
li.TextAlign := TTextAlign.taTrailing
else
li.TextAlign := TTextAlign.taLeading;
listbox1.AddObject(li);
end;

And so we can use this method AddMessage() to


add the received message to the TListBox left or
right aligned based on the client ID we detect
upon retrieval of the message from the
OnPublishReceived() event:
procedure TForm1.TMSMQTTClient1PublishReceived(
ASender: Tobject; APacketID: Word; The full source code of this sample application can
ATopic: string; APayload: TArray<system.byte>); be downloaded here:
var http://www.tmssoftware.net/public/
msg,orig: string; vp: integer; alright: boolean; TMSQuickAndEasyChat.zip
begin
msg := TEncoding.UTF8.GetString(APayload); Raspberry Pi to Delphi messaging with MQTT
vp := pos('!', msg); Where the previous sample uses Delphi, the
if vp > 0 then FireMonkey framework and our
begin TTMSMQTTClient, in the next sample, we'll
orig := copy(msg,1,vp-1);
alright := orig <> TMSMQTTClient1.ClientID; use the TTMSMQTTClient on Raspberry Pi from
msg := copy(msg, vp + 1, Length(msg)); the Lazarus Pascal development environment
AddMessage(msg, alright); and have it send messages to a Delphi based VCL
end; client.
end;

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 7


M2M MESSAGING WITH DELPHI OR LAZARUS AND MQTT PAGE 3/4

The goal of this sample is to connect an analog to var i: integer;


digital converter (ADC) to the Raspberry Pi and if TMSLCLAdaADC12B1.Open then
begin
send measurement data from this ADC to the i := TMSLCLAdaADC12B1.ReadChannel(0);
Delphi VCL Windows client app and visualize it. TMSLCLAdaADC12B1.Close;
We'll use a timer to get the measurement value end;
from the ADC every 200ms and publish this as a
message to the topic 'tms/raspi'. Now we can introduce the TTMSMQTTClient in the
To get the value from the 4-channel ADC Lazarus app and let it connect to the free Mosquitto
ADS1X15 , we use our component test broker. As the Raspberry Pi Lazarus app will only
TMSLCLAdaADC12b that is included in the free send data and not retrieve, we do not need to
library of hardware access components we have subscribe to any topic, the app will just publish its
available at: data on the topic 'tms/raspi'. We also add a TTimer
http://www.tmssoftware.com/site/ component and from the OnTimer event, have it
freetools.asp#LCLHWPack read the ADC and publish it:
procedure TForm1.FormCreate(Sender: TObject);
begin
TMSMQTTClient1.BrokerHostName :=
'test.mosquitto.org';
TMSMQTTClient1.Connect;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var i: integer;
begin
// get value from the 4 channel ADC channel 0
// to which the analog noise meter output is connected
i := TMSLCLAdaADC12B1.ReadChannel(0);
// send the value as text over MQTT for simplicity
TMSMQTTClient1.Publish('tms/raspi', IntToStr(i));
end;

Now, the Raspberry Pi part is ready and we can


look at the Delphi Windows VCL client. Here we
Hook-up the ADC breakout via i2c to the
connect also to the Mosquitto test broker and
Raspberry Pi , make sure to enable i2c on the
subscribe to the topic 'tms/raspi' to receive
Raspberry Pi and execute the app using
the messages from the Raspberry Pi app .
TMSLCLAdaADC12b with root level permissions
(this the default requirement to have permission to We use a TVrScope component from the TMS
open the i2c port). Instrumentation Workshop
(http://www.tmssoftware.com/
site/tiw.asp )
to have a visualization of the received data.
In this TVrScope component, one channel is
added and with the TVrScope configured to
automatically display data (TVrScope.Active =
true ), it will request a new sample value every
time it updates the screen (update frequency is
set via TVrScope.Frequency ) via the
OnNeedData event.
So, in the Delphi client, we implement the
OnPublishReceived event and will store the
last received ADC measurement value in a
form variable LastVal and this value will be
used in the TVrScope.OnNeedData event to
set to the scope's channel data when it
updates the display:

Capturing the ADC value from analog channel 0,


is done via: (see right top)

8 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


M2M MESSAGING WITH DELPHI OR LAZARUS AND MQTT PAGE 4/4

procedure TForm1.TMSMQTTClient1PublishReceived( ASender: Tobject; APacketID: Word;


ATopic: string; APayload: TArray<System.Byte>);
var s:string;
begin
s := TEncoding.UTF8.GetString(APayload);
// here we map the received (positive) ADC value
// to the 0..100 range of the scope
LastVal := Round(100 * strtoint(s) / 1024);
end;

procedure TForm1.VrScope1NeedData(
Sender: TObject; Channel: Integer;
var Value: Integer);
begin
Value := LastVal;
end;

When we put everything together, a sample TVrScope


visualization on the Delphi VCL desktop app is:

With these samples, we hope we did whet About the author Bruno Fierens
your appetite to enter this fascinating world Studied civil electronic engineering at university of Ghent,
of m2m communications and convinced Belgium (1987-1992) and started a career as R&D digital
you how easy the TTMSMQTTClient hardware engineer. Besides the fascination for electronics, Bruno
component makes it to add solid and robust Fierens set the first steps in programming with Turbo Pascal v3.0
m2m in desktop applications, mobile device and used all Borland Pascal & Delphi versions since that time. In
apps and IoT apps on Raspberry Pi / 1996, he founded TMS software for the activity of application
Raspbian based SBC's and component development with Delphi.
TMS software became Borland Technology Partner in 1998,
developed Delphi Informant award-winning grid & scheduling
components and now has an international team of software
developers working on a large portfolio of components. Bruno
Fierens is from 2012 Embarcadero MVP and frequent speaker at
Delphi conferences world-wide. He does and oversees VCL,
IntraWeb, .NET and FireMonkey component
Issue Nr 2 2017 development 9
WORK WITH WINEHQ, WHAT IS IT? PAGE 1/4
BY MAX KLEINER maXbox
Instead of simulating internal Windows logic like a
virtual machine or emulator, Wine translates and
converts API calls from the original Win Exe!
So it translates Win API calls into POSIX calls on-the-
fly and runtime, eliminating the performance, data
maXbox Starter 46 structure and memory penalties of other methods and
allowing you to cleanly integrate Win applications
A COMPATIBILITY LAYER FOR OPERATING into your desktop just with a separate loader.
SYSTEMS
Wine is free software and under constant development
Today we step through emulator and NOT
and improvement. Other platforms may benefit as well.
emulator.The framework Wine (originally an acronym for
• Loads Windows
“Wine Is Not an Emulator”) is a compatibility layer
9x/NT/2000/XP/Vista/7/8,10 Windows 3.x
capable of RUNNING WINDOWS APPLICATIONS and DOS programs and libraries
ON SEVERAL POSIX-COMPLIANT OPERATING • Win32 compatible memory layout,
SYSTEMS, SUCH AS LINUX (UBUNTU, REDHAT, exception handling, file-system threads
SUSE, DEBIAN ETC.), OS X, SOLARIS AND FREE and processes
BSD. • Designed for POSIX compatible operating
systems (eg. Linux, Solaris and FreeBSD)
• “bug-for-bug” stack compatibility
with Windows

It also has a registry of your own (builtin reg.exe utility):

10 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


WORK WITH WINEHQ, WHAT IS IT? PAGE 2/4 maXbox
Many of the most common configuration changes Installation of Wine is also straightforward on
can be done with the winecfg tool. We'll go Ubuntu as we did with Ubuntu 14 and Wine 1.6.2
through an easy, step-by-step introduction to (actually 1.9):
winecfg and outline the options available.
https://www.winehq.org/docs/
wineusr-guide/config-wine-main For example maXbox runs out of the box
without any settings or changes in the winecfg.
Finally, some things you might want to configure Changing settings in the Applications and
fall out of the scope of winecfg and regedit, Libraries tabs of the winecfg will have the most
and we'll go over those. impact on getting an application to run.

https://github.com/maxkleiner/maXbox4.git

HOW TO INSTALL IT? The other settings focus on getting Wine itself to
Wine provides a run-time environment for a behave the way you want it to.
Windows application by serving Windows APIs Note: The Applications, Libraries, and Graphics
called by the application. As of today, there are tabs are linked together! If you have "Default
more than 20,000 Windows applications that are Settings" selected under Applications, all of the
supported by Wine. http://www.winehq.org/ changes made within the Libraries and
Graphics tabs will be changed for all
Since Wine is included in the most default and applications to use it.
known repository of Linux (Debian, Ubuntu, Suse),
you can install it with apt-get . However, if you So this is not the end of the line, install on Mac is
are using 64-bit DEBIAN or Ubuntu, you need to a bit different.
enable multi-architecture, as Wine is a 32-bit Nowadays, Windows and Mac play almost nicely
application: $ sudo apt-get install wine together.
You can install Windows and Mac side by side
On Ubuntu-derivatives (Kubuntu or Lubuntu) or and switch between them using Boot Camp , but
Linux Mint, you can install Wine using the Wine that requires a reboot every time, and you can
PPA maintained by WineHQ team. only use one operating system at a time.
http://ask.xmodulo.com/install-wine- With wine WineBottler you get both!
linux.html

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 11


WORK WITH WINEHQ, WHAT IS IT? PAGE 3/4 maXbox
Wine is different. When any program runs,
it requests resources like memory and disk space
from the operating system. All that Wine does is
make sure that those requests get answered so
that the program can run correctly. If you want a
version of Wine that is packaged specifically for
OS X , then use WineBottler, available here:
http://winebottler.kronenberg.org/

A lot of people discover Wine because they have


one specific Windows program that they need to

As you can see also the 3 PI icons down right gets All examples can be found online:
extracted from the resource part of the exe and ..\examples\
once again, its the same exe (maXbox4.exe ) as on 161_bigint_class_maxprove3.txt
windows running.
The call respectively the calculation goes like this:
http://www.softwareschule.ch/examples/
The Wine project maintains also a database called
161_bigint_class_maxprove3.txt
the AppDB that has user reviews of how well
specific or known Windows programs work under
Wine. Search for your program and find out!
http://appdb.winehq.org/

We tested a lot of the same scripts on Win, Linux


and Mac with big numbers concerning correctness,
stability and performance and it seems nested
loops, for each and recursions are more slower
than the original computation on Win, we don't
know the reason yet.

12 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


WORK WITH WINEHQ, WHAT IS IT? PAGE 4/4 maXbox
function GetBigIntFact(aval: byte): string; Homebrew is a package manager that makes
//call of unit mybigint installing open source programs much easier.
var mbRes: TMyBigInt; i: integer;
begin In particular, trying to install a large program
mbRes:= TMyBigInt.Create(1); like Wine without the help of a package
try manager would be tremendously difficult.
//multiplication of factor
for i:= 1 to aval do
One useful application to install along with
mbRes.Multiply1(mbres, i);
Result:= mbRes.ToString; Wine is PlayOnLinux . This program allows you
finally to quickly configure Wine for a pool of well-
//FreeAndNil(mbResult); known Windows applications and games.
mbRes.Free;
end;
end;
CONCLUSION
Many people want to be able to run Win programs
Or you want the power of 100 like the same way they run other programs on Win with
2^100 = 12676506002282299670376 Wine! Wine also prints out error messages in the
Terminal when something goes wrong.
function BigPow(aone, atwo: integer): string;
var tbig1, tbig2: TInteger; Feedback @ max@kleiner.com
begin Literature: Kleiner et al., Patterns konkret, 2003,
tbig1:= TInteger.create(aone); Software & Support
//tbig2:= TInteger.create(10);
try http://www.softwareschule.ch/download
tbig1.pow(atwo); /XXL_BigInt_Tutorial.pdf
finally
result:= tbig1.toString(false); http://www.ultimatetech.org/install-
tbig1.Free; configure-wine-ubuntu/
end;
end; http://ask.xmodulo.com/
install-wine-linux.html
At least one really big, it's 333^4096 (10332
https://github.com/maxkleiner/maXbox4
decimal digits)! /releases
With wine -dbg you can also find out more of
the application you want to run or recompile the
whole project like Homebrew on mac.

13 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


DISPLAYING ASSOCIATED FILE ICONS IN DELPHI PAGE 1/4
BY MICHAËL VAN CANNEYT
starter expert
from the collection. The component can also be
ABSTRACT used to add the necessary images to an image list:
Showing a list of files in some directory is something by setting the ImageList property to a
one often needs to do. Showing the associated file
TImageList instance, the component will add
icon and descriptive text next to the filename can be a
little harder. In this article we show how to do this.
any icons it finds to the image list. It will store the
index of the image in the list in the collection.
INTRODUCTION The image list can then be used to show a file
Often, you need to display a list of files to the user. type image for instance in a listview or a
These filenames need not actually exist on disk, treeview.
they can be names of files inside a .zip archive, or a
list of filenames stored in a database. To make it
more pleasant and recognizable for the user, it TFileInfoCollector = Class(TComponent)
helps if the associated file icon or file description Function IndexOfExtension(AExtension : String;
(as displayed in the Explorer) is shown next to the CachedOnly : Boolean = False) : Integer;
filename. Function FindDescription(AExtension : String;
CachedOnly : Boolean = False) : String;
The operating system has this information Function FindExtensionInfo(AExtension : String;
CachedOnly : Boolean = False) : TExtensionInfo;
available. On Windows, the ShellAPI offers a call
to retrieve this information: SHGetFileInfo. In this Property Extensions[AIndex : Integer] : String;
article, a component is presented that uses this Property Descriptions[AIndex : Integer] : String;
call to fetch 3 kinds of information based on a file Property MimeTypes[AIndex : Integer] : String;
Property IconHandles[AIndex : Integer] : Thandle;
extension:
Property ImageIndex[AIndex : Integer] : Integer;
1. Associated File icon Property InfoCount : Integer;
2. File description Published
3. Mime type. The mime type is useful for Property ImageList : TImageList;
instance when you want to serve a file in a Property SmallIcons : Boolean;
Property FreeIconHandles : Boolean;
webserver or send it by mail. end;
THE TFILEINFOCOLLECTOR COMPONENT
This component will load the necessary
information on demand and keeps it in memory in
a collection. If the same extension is queried a
second time, the information will be retrieved

FIGURE : 1 Running application

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 14


DISPLAYING ASSOCIATED FILE ICONS IN DELPHI PAGE 2/4

starter

The component exposes 3 methods to fetch file QUERYING THE OS


information, based on an extension Aextension: The search methods will search through the
● IndexOfExtension collection with file information.
Returns the index of the If no info is found, and CachedOnly is False, then
relevant item in the collection. the component will query the OS for the relevant
● FindDescription info, in the FetchExtensionInfo call:
Returns the description of
the relevant item in the collection.
function TFileInfoCollector.FetchExtensionInfo(
● FindExtensionInfo
AExtension: String): TextensionInfo;
Returns the relevant item in the
collection.The search is performed case Const
insensitively. If CachedOnly is True, the IconOptions : Array[Boolean] of DWORD
= (SHGFI_LARGEICON,SHGFI_SMALLICON);
search is only performed in the in-memory
collection. If it is false (the default) then it will Var
first search the in-memory collection. FileInfo : SHFILEINFO;
If it doesn't find the necessary information Attr : DWORD;
Info : TextensionInfo;
there, then it will query the OS for the
AnIcon : TIcon;
relevant information.
begin
Armed with an index, the necessary information Result:=Nil;
Attr:=SHGFI_ICON or SHGFI_TYPENAME
can be retrieved through the various Array
or SHGFI_USEFILEATTRIBUTES
properties:
or IconOptions[SmallIcons];
● Extensions if (SHGetFileInfo(PChar('*'+AExtension),
The file extension. FILE_ATTRIBUTE_NORMAL,
● Descriptions FileInfo,SizeOf(FileInfo),Attr)<>0)
The textual description of the file type. then
begin
● MimeTypes Info:=FExtensions.Add as TextensionInfo;
The MIME type of the file, if available. Info.Extension:=AExtension;
● IconHandles Info.Description:=FileInfo.szTypeName;
A handle to an icon Info.hIcon:=FileInfo.hIcon;
Result:=Info;
(if FreeIconHandles is not True). if Assigned(ImageList) then
● ImageIndex begin
If ImageList is assigned, the index of the icon AnIcon:=TIcon.Create;
in the image list. try
AnIcon.Handle:=Info.hIcon;
● InfoCount Info.ImageIndex:=ImageList.AddIcon(anIcon);
The number of items in the collection. finally
if FreeIconHandles then
The three published properties control the Info.hIcon:=0
behaviour of the component: else
AnIcon.Handle:=0;
● ImageList AnIcon.Free;
If set, the component will add any found end;
icons to this imagelist. The ImageIndex array end
contains the indexes in the image list. else
begin
● SmallIcons Info.ImageIndex:=-1;
If set, the component will retrieve small icons if FreeIconHandles then
from the Operating System. The default is to begin
fetch large icons. DestroyIcon(Info.hIcon);
Info.hIcon:=0;
● FreeIconHandles end;
If set to True, the Icon handles returned by end;
the OS will be returned at once. It can be if FRegistry.OpenKeyReadOnly(AExtension) then
safely set to True if the image list is used: Info.MimeType:=
the icon is immediatly copied to the image Fregistry.ReadString('Content Type');
end;
list. end;

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 15


DISPLAYING ASSOCIATED FILE ICONS IN DELPHI PAGE 3/4

USING THE COMPONENT


To demonstrate the component, a small
application can be created, which allows the
user to select a directory, and at the push of a
button, the files in the directory are listed, with
their associated icon, mime type and
description.
In order to avoid having to install a package in
order to run the demo, the
TFileInfoCollector instance is created in
the OnCreate method of the form:

procedure TMainForm.FormCreate(Sender:
TObject);
begin
FIL:=TFileInfoCollector.Create(Self);
Fil.ImageList:=ImageList1;
Fil.SmallIcons:=True;
BEDir.Text:=ExtractFilePath(ParamStr(0));
FetchFiles;
end;

After setting the necessary properties for the


component (ImageList and SmallIcons), the
contents of the directory in which the binary lives,
are displayed using the FetchFiles method,
which is a simple FindFirst/FindNext loop:

procedure TMainForm.FetchFiles;

var
Info : TSearchRec;
anItem : TListItem;
Ext : String;
I : integer;

begin
LVDir.Items.Clear;
If FindFirst(BEDir.Text+PathDelim+'*.*',0,Info)=0
then
try
Repeat
FIGURE : 2 Select Directory to Display Ext:=ExtractFileExt(Info.Name);
AnItem:=LVDir.Items.Add;
The code is pretty straightforward. AnItem.Caption:=Info.Name;
If the SHGetFileInfo call succeeds, a new item I:=FIL.IndexOfExtension(ext,false);
is added to the collection, and the icon is copied if (I<>-1) then
to the image list. Depending on the begin
AnItem.ImageIndex:=Fil.ImageIndex[I];
FreeIconHandles property, the icon handle is
AnItem.SubItems.Add(Ext);
freed. The last thing that is done is read the mime AnItem.SubItems.Add(Fil.Descriptions[i]);
type from the registry: known extensions are AnItem.SubItems.Add(Fil.MimeTypes[i]);
present as keys below HKEY_CLASSES_ROOT, end;
and the actual Mime Type (if present) is in the until (FindNext(Info)<>0);
finally
stringContent Type below the extension key. FindClose(Info);
end;
end;

16 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


DISPLAYING ASSOCIATED FILE ICONS IN DELPHI PAGE 4/4

All files are added to the listview.


If IndexOfExtension returns a valid index,
then the additional information is copied to the
subitems of the list item, so they can be displayed.
The result of all this can be seen in figure  3.
Figure  : 3The file information collector in action
[No BMP or WMF for image file fileinfodemo]

FIGURE : 3 Working Icons

CONCLUSION
Using the icons which the operating system shows when
displaying files is not hard, as can be seen in the small
code snippets displayed here.
To make things work a bit more optimal, a component
has been presented which caches the results of querying
the OS; For optimal convenience an imagelist can be
filled to make displaying the icon in controls that use an
image list (such as a listview or treeview). The component can
probably be improved by having 2 image lists: one for
small and one for large images. This improvement is left
as an exercise to the reader.

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 17


READ EVERYWHERE IN YOUR BROWSER

ONLINE VIEW
IF YOU SUBSCRIBE OR RENEW
IT WILL BE AVAILBLE FOR FREE.
FOR ONE YEAR.
DON'T ASK. SIMPLY SUBSCRIBE.
http://www.blaisepascal.eu/subscribers/UK/UK_R enewal_Department.html
MVVM DELPHI APPLICATIONS PART II PART 2 EX PERTS
DX
BY OLAF MONIEN
starter expert
Delphi & Lazarus begin
Application.Initialize;
MVVM is a design pattern that has been created
Application.MainFormOnTaskbar := True;
to help developing applications that separate UI Application.CreateForm(TModelMain, ModelMain);
from business logic. Application.CreateForm(TViewModelMain,
In this second article, we will look at templates ViewModelMain);
for Views, Models and ViewModels. We will also Application.CreateForm(TViewMain, ViewMain);
use TMessageCenter for notification. Application.Run;
In the first part of this article series I introduced end.
the MVVM design pattern and its general goals
and advantages for developing applications with
user interfaces. As the flow of ownership in MVVM starts with a
The core idea behind MVVM is to keep things View, we would want Views only created in the DPR
apart and organized. In terms of MVVM
“Separation of Concerns (SoC)” means that
file. Each view should then create its ViewModel,
UI, UI logic/state and business/data logic are which in turn should create its Model. So the DPR
three different concerns and that these should be should look more like this:
de-coupled as much as possible.
begin
ReportMemoryLeaksOnShutdown := DebugHook <> 0;
Accordingly, our simple demo application was Application.Initialize;
Application.MainFormOnTaskbar := true;
separated into three units: Application.CreateView(TViewMain);
● MainView.pas/dfm – implemented as Application.Run;
TForm descendant, holding all the UI controls, end.
implementing event handlers that connect
the controls with logic in the ViewModel.
(Notice the line
● MainViewModel.pas/dfm – implemented
“ReportMemoryLeaksOnShutDown”. That's what I
as TDataModule descendant, holding the code
always have as first line in my projects – it warns you
that reacts to UI events, by executing code in
of any memory leaks while running the project
model and pulling data from the Model.
through the Delphi debugger, so that you will be
● MainModel.pas/dfm – implemented as aware of any oversights as soon as possible).
TDataModule descendant, holding the
business logic and data access code. As you can see there is a call to
Important is that the Model neither knows about “Application.CreateView” with just a class as
the ViewModel nor does it know about the View. parameter. Obviously “CreateView” does not
The ViewModel only knows about (and owns) the exisits in TApplication (neither in VCL, nor in
Model and the View only knows about the FMX). It's a Class Helper that I created, to simplify
ViewModel (and owns it) creation of MVVM applications in Delphi. It has a
The demo application, that we started with, descriptive name “CreateView” to more comply
doesn't comply with the flow of ownerships as with the MVVM terms and, more important,
depicted in though. It just used global variables, it avoids the usage of global variables.
that are created by default when adding new
forms/datamodules to Delphi applications.

Figure : Ownership of MVVM Components

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 19


MVVM DELPHI APPLICATIONS PART II PART 2 EX PERTS

GLOBAL VARIABLES This is the adapted interface of the MainView that


Global variables, as uses by Delphi's standard we used in Part I of this article series:
template for Forms are easy to use and TViewMain = class(TVCLView)
especially for beginners they make it a snap to GridOrders: TDBGrid;
get started with application development. EditParcelService: TDBEdit;
Label1: TLabel;
After a while though, they will easily lead ButtonShipped: TButton;
to cluttered and not so well organized code. ActionList1: TActionList;
They also make it hard to have multiple Form ActionOrderShipped: TAction;
instances (think of multiple, simultaneously procedure ActionOrderShippedExecute(Sender: TObject);
open detail views of different records). procedure ActionOrderShippedUpdate(Sender: TObject);
procedure FormCreate(Sender: TObject);
If used for non-UI purposes, such as state procedure ButtonShippedClick(Sender: TObject);
variables, global variables will prevent your procedure GridOrdersDrawColumnCell(Sender: Tobject;
code to scale into multiple threads. const Rect: TRect; DataCol: Integer; Column: Tcolumn;
In other words, in this more advanced MVVM State: TGridDrawState);
protected
demo, I have removed all global variables procedure CreateViewModel; override;
from any TForm or TDataModule. public
function ViewModel: TViewModelMain;
end;
TAPPLICATIONHELPER
The implementation of “CreateView” is very simple:
function TApplicationHelper.CreateView(AViewClass: TVCLViewClass): IView;
var LView: TVCLView;
begin
CreateForm(AViewClass, LView);
result := LView;
end;

It basically just calls It descends from TVCLView and will implement


TApplication.CreateForm, using a local two methods:
variable to hold the instance. It actually returns Procedure CreateViewModel; override;
that new instance, but in most cases we won't use Function ViewModel: TViewModelMain;
that. The signature comes with a couple yet
unknown types:
function TApplicationHelper.CreateView(AViewClass: TVCLViewClass): IView;

TVCLViewClass is basically the class of a VCL CreateViewModel is responsible to create an


Form ,leaving it to the reader to implement this for instance of the View's ViewModel and will be called
FMX in the same way. The TVCLView is then the automatically when the View is created.
actual trick to implement the ownership flow, that Fucntion ViewModel just returns a correctly
I mentioned previously: typed instance of the View's ViewModel.
TVCLView = class abstract(TForm, IView) function TViewMain.ViewModel:
private TViewModelMain;
FViewModel: IViewModel; begin
protected result := GetViewModel as TViewModelMain;
function GetViewModel: IViewModel; end;
procedure SetViewModel(const AValue: IViewModel);
procedure CreateViewModel; virtual; abstract; procedure TViewMain.CreateViewModel;
public begin
constructor Create(AOwner: TComponent); override; SetViewModel(TViewModelMain.Create(self));
end; end;
TVCLViewClass = class of TVCLView;

It carries a reference to a ViewModel, and has an


abstract method “CreateViewModel” which needs to
be implemented in actual View implementations.
TVCLView is declared abstract, as we will never
create a direct instance of it, but we will create a
new descendant for every view in our application.

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 20


MVVM DELPHI APPLICATIONS PART II PART 2 EX PERTS

The implementation of these two methods is very ONE MORE THING


easy, you only have to put in the correct type for So far this application design looks pretty much
the ViewModel – TViewModelMain in this demo. straight forward, but there is one more thing that
The same idea is used for the ViewModel to create the Delphi IDE needs. We are using TVCLView,
the Model. I created TViewModel and TModel TViewModel and TModel classes as descendants
classes similar to TVCLView, the only difference for our “Forms” and “DataModules”. The IDE really
is, that they will work for FMX, VCL etc without doesn't like that and will not show the correct
changes. Form designers.
Obviously, only the View is dependent on the UI- To convince Delphi that TViewModel and TModel
platform. TModel is dumb and does not are actually TDataModules and that TVCLView
introduce any new behavior. TViewModel has is a TForm, we need to install a runtime package
the same CreateModel method for creating its that registers these classes for the IDE. The code
Model, as the View has for creating its ViewModel: for that package (and the full demo code) can be
TModel = class abstract(TDataModule, IModel) found on my blog:
end; www.developer-experts.net/blog

TViewModel = class abstract(TDataModule, IViewModel)


private LAST NEWS:
FModel: IModel; I will teach MVVM application development in
protected hands-on workshops as well.
function GetModel: IModel; The next workshop will be on June 14.-15. 2017
procedure SetModel(const AValue: IModel); in Geneva/Switzerland. There I will go into
procedure CreateModel; virtual; abstract; things like exchanging the UI-platform using
public minimal code changes. Also Dependency-Injection
constructor Create(AOwner: TComponent); override; will be shown – even though DI is not required by
end; the original MVVM pattern.
More information and tickets:
https://www.developer-experts.net/events/
To create Views, ViewModels and Models in your
application, you only need to use:
● DX.MVVM.Types.VCL.pas About the Author:
– this implements TVCLView and the Unternehmensberatung Monien and Developer
TApplicationHelper for VCL. Experts, LLC are managed by Olaf Monien as CEO.
Create a new Form and change its Descendant
Supported by other Delphi professionals, we offer
from TForm to TVCLView, then implement
qualified consulting in the area of software
CreateViewModel and ViewModel methods.
development. Our important focus is
● DX.MVVM.Types.pas Embarcadero’s Delphi development platform. This
– this implements TViewModel and Tmodel, is manifested through our brand “Developer
then implement CreateModel and Model Experts”, which we have been using for years to
methods. market events like workshops and trainings.
● Remove all global Variables and change the
Olaf Monien has been working as an IT consultant
DPR file to the code shown above.
for international companies for many years. His
areas of special interest are software architecture,
With these modifications, you can have code like database design, Internet aplications and mobile
this: devices. Olaf Monien received a Master of
procedure TViewMain.ButtonShippedClick(Sender: TObjectComputer
); Science degree and has more than 20
begin years of software development experience.
ViewModel.OrderShipped; Olaf Monien is an Embarcadero
end; Technologies MVP and Embarcadero
Technology Partner.

 There is always a correctly type ViewModel (or


Model) which can easily be used in your code.
The instances of the Model and ViewModel get
cleaned up automatically once their View is
destroyed. If you create all Views in the DPR,
then TApplication will destroy all Views once
the application is terminated.
EX PERTS

21 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


Experts
CLIENT DATASETS IN MANY VARIATIONS PART 1 PAGE 1/8
DELPHI, FIREDAC, KBMMW AND FOR FPC/LAZARUS:BUFDATASET BY DETLEF OVERBEEK
starter expert DX Delphi

My initial design suggested that this app would need


ABSTRACT CREATING A TO LIST Dates for the start and completion of the task, a Subject
Several Object Pascal libraries offer client dataset
field, a Memo field for longer notes, and a Finished flag
implementations. How do they compare in usage and
to register the completion of the task.
performance? A small application is presented which
demonstrates some comparative advantages and
disadvantages among offerings from Delphi, FireDAC, To summarize the proposed data structure, there would
kbmMW and Lazarus (TBufDataset). be fields for:

INTRODUCTION 1 Task Description Subject


I decided to create a simple ToDo list application as a
2 Begin Date Begin
way of comparing the various types of Pascal-based
3 End Date End
client datasets I am aware of, and how they perform
4 Finished Done
when deployed on the PC desktop, on Android
5 Priority level Priority
systems and on the iPhone.
6 Memo field Description
My initial motivation was the frustration I felt in using
Microsoft Office ToDo functionality, and several other 7 Client Dataset for registering data
mobile app offerings which all required either that I 8 Client Dataset for backup
give up my privacy, or that I get bombarded with
Altogether a small app.
adverts (or both). I don’t want third parties to know
Of course the GUI needs buttons, grids and so on which
my friends list, nor do my friends want their personal
you can see in the source attached.
details spread about. Hence my decision to develop
my own app that does securely only what I want from I chose to create a form that contains all the GUI elements
it (and no more). All this without any adverts (except needed. But a notable feature, since I want to keep the
for our own – the series describing the desktop and resulting app view small, is that secondary components
mobile versions of this app will be published in our (those related to the backup facility) only become visible
Magazine). when you click a button which expands the view.
So the form is actually twice the application’s apparent
THE PURPOSE window size.
The application’s purpose is to hold a list of ToDo items
and associated data. It will have a facility for completed In Normal mode the app simply shows the upper part of
items to be removed and archived; but completed items the form, whereas in Backup mode the entire form is
must be readily accessible for referring to later if desired, made visible, and the choice is determined by the user at
and also it must have a facility for completed ToDos to be runtime if s/he wants to backup or archive the current
reintroduced if a completed task recurs (or turns out not to data (see the illustration in Figure 1).
be completed) in future.

Figure 1: The running application

24 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


CLIENT DATASETS TO-DO LIST PART 1 PAGE 2/8

The appearance of the app is kept small by making NOHALPAs


optional functionality initially hidden. I want to develop a number of similar applications
which I will term NOHALPAs: you can use them without
Because there is little code it is easy to order it well. any danger of being HArvested, Losing Privacy or
You can download the entire project from your private being bombarded with Advertisements. HALPA-less apps
download area. for your desktop, iPad, Android and other Oss.
A separate project is needed for the mobile app design, My personal view is that I would rather pay to get a
but its functionality will be identical to the VCL / LCL completely ad-free, snooping-free product (a NOHALPAs)
desktop application. than I would use a free HALPA. Anyway the NOHALPA
applications from our magazine are all free.

Figure 2: The app’s main form with all viewable components.

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 25


CLIENT DATASETS TO-DO LIST PART 1 PAGE 3/8

THE APPLICATION
If you examine the form design you will understand how
the app works. I would not like you to drag and drop all
of the elements on to the form, but I want to show you
some of the basic principles, of which the first is to give
each control its own self-descriptive name, such as:
BtnCreate_Archive, BtnRestore.

We do not need a data container separate from the main


form (such as a datamodule) because that would make this
small app unnecessarily complex. In Delphi the main
form can be created by this simple route: FILE | NEW |
VCL FORMS APPLICATION.
I named the main form unit U_GUI_MAIN.pas.
U tells me it is a unit and GUI designates it as a
Graphical User Interface form.
Based on its purpose the form class instance is named
F_TODO (F since it is a Form). I saved the project under
the name CDS because of its use of a client dataset.
The project developed for FireDAC is named
CDS_FireDac and that for kbmMW is named
CDS_KBMMW. The Lazarus project will have the Laz
prefix: Laz_CDS_BufTable.

See Figure 2 On page 21:The app’s main form with all


viewable components.

THE CLIENT DATASET


TClientDataset is very easy to use. You create a
number of fields, initialize them, and then make sure they
will be saved, that you can retrieve them again, and that
you can create a backup. From that a logical structure
follows as you will see in the code:

In the FormCreate we want to make sure that some of


these requirements are met:

procedure TF_TODO.FormCreate(Sender: TObject);


begin
F_Todo.Height:= 604; // the height of the application to keep it small

ClientDataSet1.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoListUTF.xml');
ClientDataSet2.LoadFromFile(ExtractFilePath(Application.exename)+'ToDoListBackUpUTF.xml');
// the Extract file path is added to make sure the Data can be found

DateTimePicker1.DateTime := (Now); // set the starting date of the calendar


DateTimePicker2.DateTime := (Now) // no semicolon, not necessary here at the end of the procedure
// set the starting date of the calendar number 2
end;

Data-saving methods are placed in the FormClose


handler, not because it MUST be done this way, but
because logically it fits best here:
procedure TF_TODO.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ClientDataSet1.MergeChangeLog; // Merge change log makes sure that all changes etc. are actually saved
ClientDataSet1.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoListUTF.xml');
// Name of the xml file for sving
ClientDataSet2.MergeChangeLog;
ClientDataSet2.SaveToFile(ExtractFilePath(Application.exename) + 'ToDoListBackUpUTF.xml');
end;

26 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


CLIENT DATASETS TO-DO LIST PART 1 PAGE 4/8
This establishes a basis for adding all further
functionality.
But before we continue we must create the client dataset
(hereafter called a CDS). We start with the easiest task:
creating the fields.
BUT, think twice before you start this. What fields do you
need to create? Best practice is to start by listing the fields
you require. Creating fields in the Delphi CDS is very
unforgiving. Once you have created the dataset, it’s very
difficult to add a further field later. That requires some
extra skills. If you have already inserted data into your
dataset for testing, it will be impossible thereafter to
change the data structure! Well almost impossible – but
you will almost certainly lose data. My advice is: always
back up your project before you start altering it.

Figure 5: Add New Field


It’s easy to create the fields you need. Go to the Tool
palette and search for “Data Access”. Then right-click the empty Fields Editor and choose:
Three classes are listed there: TDataSource (we need New field.
that), TClientDataSet (which we are calling CDS) and a This displays a dialog where you can create the field and
TDataSetProvider (which is not interesting for this set its properties appropriately. Enter the field’s details
project). Drop a CDS component on your form and right- making sure that in the Field type radio group the field’s
click. You will see something like the illustration in type is set to Data. At the top right you can see the
Figure 3. I renamed the CDS to TEMP CDS to make clear Component field.
that it’s not part of the project, but only added for It is important to be able to identify the Delphi-assigned
demonstration purposes. name of this component, since we will need to refer to it
later. You’ll see that the field’s name has been
TEMP CDS constructed simply by appending the property name to
TEMPCDS. Click OK.

Figure 6: FieldCreation Dialogue

Figure 3: The Fields Editor becomes available

HOW TO CREATE A DATASET:


First let me explain how to create the
required dataset.
You begin by adding the required fields,
and after that you should save the newly
created table structure. If you right-click
on the form a menu of options pops up:
choose Fields Editor, which displays an
empty Editor (see Figure 4).

Figure 4: Empty Fields Editor Figure 7: Entering the details

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 27


CLIENT DATASETS TO-DO LIST PART 1 PAGE 5/8

Now right-click again and the same list reappears.


Choose: → Create DataSet.

Figure 8: Create Dataset


It seems like nothing has happened. Actually the IDE
has reconfigured quite a lot internally, and if you
right-click again a substantially different menu appears,
with several additional menu choices now visible.

Choose Save to MYBASE XML UTF8 TABLE.


I chose this utf8 format since if you want to
become friends with Russian or Turkish people
and create a ToDo list which mentions them, this
will ensure that their names are shown correctly.
And it’s also more modern...

Notice that our data file hasn’t yet been created,


so it doesn’t yet appear in the listing.
The next illustration shows how you name and
save the file:
c:\ToDoListClientDatSet\Win32\Debug\T
EMP_CDS_FILE.xml
(see next page)

Figure 9: Save to Mybase Xml UTF8 table

28 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


CLIENT DATASETS TO-DO LIST PART 1 PAGE 6/8

Figure 10: Total Commander Showing the files and path


You will use this filename in the form’s OnCreate and
OnClose methods.
Once the data file exists you can retrieve its contents,
and now you can also link the datasources etc. to the
Figure 13: showing the fielddefs
appropriate form controls to yield a working GUI-data
connection. In the Fields Editor you now can see the field
“Subject”after you have added all the fields.
To test the CDS you need of course to
set Active to True in the Object
Inspector (or via code).
In the Structure Editor you can now
see the newly created field:
If you select the field in the Structure Editor, you can
Figure 11: The new field examine its properties, properties which can be altered in
the Object Inspector. Choose FieldDefs, click on the plus-
sign and you will see the field Subject.

Figure 12: The Structure Editor


showing the field

Figure 14: The Object Inspector


Issue Nr 2 2017 BLAISE PASCAL MAGAZINE showing the propertys of the field
29
CLIENT DATASETS TO-DO LIST PART 1 PAGE 7/8

All in all this is pretty simple and fast if handled correctly. The CDS doesn’t know SQL. You will have to use filters.
But if you make a mistake or forget something you should Filtering is far less attractive then creating SQL. To have
be aware that it may be easier and less time consuming to the advantage of SQL functionality you will have to use
start over again. one of the other CDSs: FireDAC’s MemTable, or
kbmMW’s MemTable. A follow-up article in the next
Recovering from mistakes is hard and frustrating. issue will address that possibility.
Please enter data only AFTER the project’s structure is Meanwhile you would do well to read one of Cary
finished and all the fields are set up correctly. That may Jensen’s books about client datasets.
seem obvious, but often you realise after your initial setup Filtering is of course possible in all CDSs, and the
that you have missed a needed field (such as “Progress”). following example shows how it works (and that it is
If this is the case, first remove any data, right-click on the relatively complex to code compared to using SQL).
dataset and choose Clear Data. For example:

There is still something you should be aware of: begin

DataMod.CDSUpdatedLeden.Filter : = 'Date < 1-1-2011'


+ 'and Date =' + QuotedStr ('one item')
+ 'or Date < 1-1-2017'
+ 'and Extra =' + QuotedStr ('printed 1 another item')
+ 'or Date < 1-1-2017'
+ 'and Extra =' + QuotedStr ('download 1 item');

DataMod.CDSUpdatedLeden.Filtered := True;

Conclusion: you can use filtering for a simple


experiment like this, but usually well-constructed SQL
queries are clearer and more reliable.

EXTRA SKILLS
If you really need to repair or extend the number of
fields or otherwise change them, make a copy of your
dataset and rename it to something like
CDS_ORIGINAL so you will still have your data and
structure. Make sure you also copy the xml file and
rename it: ToDoListUTF → ToDoListUTF_ORIGINAL.
After you have finished altering your CDS you will
then be able to import your original data.

NOW THE WAY TO ALTER YOUR FIELDS:


go to the FieldDefs and alter your field property (for
instance you might want to change Date to be a
String).
You can use any appropriate type.
Make sure you save it and test it, before you trust that it
really works. Just run the program. If the changes are
not what you expected, try again.
If you have coupled the dbgrid to your data you will
need to update the grid’s columns through its Columns
editor. It will work in the end. But please keep in mind
that the structure of your CDS needs to be rebuilt:
→ empty dataset → create dataset and test by
running the program.

Remember to create a backup!

30 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


CLIENT DATASETS TO-DO LIST PART 1 PAGE 8/8

HOW TO ADD NEW FIELDS:


I think that part was straightforward enough not to need
Add the new field. Clear the data from the dataset and
further explanation. So now we have a complete working
then recreate the dataset.
ToDo List without HALPA!
Open the Fields Editor. First choose: → remove all
fields and then: → add all fields.
Next issue: The FIREDAC CDS (further skills
To create the backup files for our “Done” items you can
needed).
use the following Code:
procedure TF_TODO.BtnCreate_ArchiveClick(Sender: TObject);
var i: integer;
begin
ClientDataSet1.First;
for i := 0 to ClientDataSet1.recordCount - 1 do
begin
If (ClientDataSet1Done.Text = 'Done') then
begin
ClientDataSet1.Edit;
ClientDataSet2.Edit;
ClientDataSet2.Append;
ClientDataSet2.FieldByName('Subject').AsString := ClientDataSet1.FieldByName('Subject').AsString ;
ClientDataSet2.FieldByName('Done').AsString := ClientDataSet1.FieldByName('Done').AsString ;
ClientDataSet2.FieldByName('Begin').AsDateTime := ClientDataSet1.FieldByName('Begin').AsDateTime ;
ClientDataSet2.FieldByName('End').AsDateTime := ClientDataSet1.FieldByName('End').AsDateTime ;
ClientDataSet2.FieldByName('Priority').Text := ClientDataSet1.FieldByName('Priority').Text ;
ClientDataSet2.FieldByName('Description').AsString :=
ClientDataSet1.FieldByName('Description').AsString;
ClientDataSet1.Delete;
end;
ClientDataSet1.Next;
end;
end;

COMPONENTS
DEVELOPERS 4 Android logcat tool
People developing for Android knows that checking it’s logfiles is a must, debugging any new
application. One can do it directly using adb.exe with the logcat command, but it outputs data to the
console and is generally not really very useful due to the typical high amount of data produced by it.
Android Studio contains a log viewer application, but alas that requires installation of the complete
Android Studio, which perhaps is overkill to check the contents of the log.
Some 3rdparty options also exists. Unfortunately they typically require Java or .Net to be available, and
most of them are trials or limited commercials.
So when nothing exists that fulfills the requirements, the option is to make it yourself.
Thats what I did. May I present kbmLogCat!

su
sb
sc
r
F ibe at h
R ttps:
E //com
E pon
e
T nts4de
O velop
O ers.w
L ordpre
s. s
Issue Nr 2 2017 BLAISE PASCAL MAGAZINE
co 31
m
/
32 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE
BLUETOOTH CONTROLLED ROBOT WITH PART 1
VISUINO AND DELPH PAGE 1 / 11
BY BOIAN MITOV
starter expert
- +
In the previous articles I have shown you how you
can program Arduino and ESP8266 boards to read
data from sensors or control Relays and Servos. I
have also shown you how you can connect to the
micro controllers from Delphi applications and
Android apps over Ethernet, Wi-Fi and Bluetooth
LE. We can also use Bluetooth classic to connect to
the boards. Again, there are many options, but
one of the most popular is the HC-06 Bluetooth
module. You can see how the HC-06 module looks
on this picture:

I have also posted detailed instructions on how to


assemble the robot here:
https://www.instructables.com/id/
Assemble-Elegoo-Smart-Car-Robot-Kit/

The module implements Serial communication You can see the assembled robot and the Android
Bluetooth service, and can be connected directly to phone that I used for the article here:
Arduino serial port. The Arduino micro-controllers are
also a popular choice for controlling robots. There are
many Arduino Robot kits, and some of them come with
the HC-06 module included for remote control of the
robot. -
+
In this article I will show you how you can program a
mobile robot to control it from Android Delphi app
over Bluetooth. You can use any mobile robot kit that
includes the HC-06 module. I used the Elegoo robot kit.
It is quite good and affordable kit available on Amazon.
https://www.amazon.com/gp/product/
B01DPH0SWY/ref=od_aui_detailpages00?ie=
UTF8&psc=1
The Robot kit includes Arduino UNO, Sensor First we will use Visuino to program the robot.
Shield for the Arduino, Motors, and Motor Driver Start Visuino.
board, Ultrasonic Distance Sensor with servo for The robot uses L298N module to control the
rotating left and right, line tracking sensors, motors, and we will need to add component for it.
Infrared Receiver sensor, HC-06 Bluetooth Type "motor" in the Filter box of the Component
module, Rechargeable Batteries, and Battery Toolbox then select the "Dual DC Motor Driver 3
Charger. Pin Bridge(L298N)" component:

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 33


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 2 / 11

And drop it in the design area. Next we need to Connect the "Reverse" output pin of the
connect the DualMotorDriver1 component to the "Motors[ 1 ]" channel of the DualMotorDriver1
Arduino pins to control the motors. Connect the component to the "Digital" input pin of
"Forward" output pin of the "Motors[ 0 ]" channel “Digital[ 9 ]" channel of the Arduino component:
of the DualMotorDriver1 component to the
"Digital" input pin of "Digital[ 7 ]" channel of the
Arduino component:

Connect the "Speed" output pin of the "Motors[ 1


Connect the "Reverse" output pin of the ]" channel of the DualMotorDriver1 component
"Motors[ 0 ]" channel of the DualMotorDriver1 to the "Analog" input pin of "Digital[ 10 ]"
component to the "Digital" input pin of channel of the Arduino component:
"Digital[ 6 ]" channel of the Arduino component:

Connect the "Speed" output pin of the


“Motors[ 0 ]" channel of the DualMotorDriver1
component to the "Analog" input pin of "Digital[
5 ]" channel of the Arduino component: You can control the motor speeds directly,
however if you suddenly change the motor from
stopped to full speed, or from full speed to stop,
this will make the robot movements "jumpy",
and may lead to wheels easily detaching from
the robot. It is better to speed up and slow down
the motors gradually using a ramp.
Visuino includes "Ramp To Value" component
designed for such purpose.
Type "ramp" in the Filter box of the
Connect the "Forward" output pin of the Component Toolbox then select the "Ramp To
“Motors[ 1 ]" channel of the DualMotorDriver1 Value" component
component to the "Digital" input pin of
“Digital[ 8 ]" channel of the Arduino component:

34 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 3 / 11
And drop 2 of them in the design area.
Select the 2 "Ramp To Value" components.
In the Object Inspector, set the value of the "Initial
Value" property to "0.5" - this specifies that the
motors will initially be off

Connect the "Out" output


pin of the RampToValue2
component to the "In"
input pin of the "Motors[
1 ]" channel of the
DualMotorDriver1
component:

In the Object Inspector, set the value of the


"Slope" property to "2" - this will specify how fast
the motors will ramp up and down:

To control the motor speeds for the 4 commands


from the remote (Forward, Backward, Left, and
Right), and the Stop, we can use "Analog Value"
Connect the "Out" output pin of the component for each motor with value 0.5 - Stop,
RampToValue1 component to the "In" input pin and 4 "Set Value State" elements specifying the
of the "Motors[ 0 ]" channel of the motor speeds for the 4 commands.
DualMotorDriver1 component: Type "analog value" in the Filter box of the
Component Toolbox then select the "Analog
Value" component:

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 35


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 4 / 11
And drop it in the design area. In the Object In the Elements Editor select the
Inspector set the value of the "Value" property “Set Value State2".
to "0.5" - this will make sure that by default the
motor is off

In the Object Inspector set the value of the


Click on the "Tools" button of the AnalogValue1
"Value" property of the element to "1":
component to open the "Elements" dialog:

In the "Elements" editor select the "Set Value


State" in the right window, and clicking on
the "+" button 4 times to add 4 of them:

Leave the other 2 Elements unchanged.


The value of the "Value" property for them
will be "0" - this will specify the motor to
rotate backward.
Close the "Elements" editor.
Type "analog value" in the Filter box of the
Component Toolbox then select the
“Analog Value" component:

In the Elements Editor select the "Set Value


State1"

In the Object Inspector set the value of the


"Value" property of the element to "1" - this will
specify the motor to rotate forward:

36 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 5 / 11
And drop it in the design area. In the Object
Inspector set the value of the "Value" property of
the AnalogValue2 component to "0.5":

Click on the "Tools" button of the AnalogValue1 In the Elements Editor select the "Set Value State1".
component to open the "Elements" dialog:

In the Object Inspector set the value of the "Value"


property of the element to "1":

In the "Elements" editor select the "Set Value


State" in the right window, and clicking on the
"+" button 4 times to add 4 of them:

In the Elements Editor select the "Set Value


State4".

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 37


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 6 / 11

In the Object Inspector set the value of the Type "compare" in the Filter box of the
"Value" property of the element to "1": Component Toolbox then select the "Compare
Char Value" component:

Leave the other 2 Elements unchanged.


Close the "Elements" editor.

Connect the "Out" output pin of the AnalogValue1


component to the "In" input pin of the
RampToValue1 component:

And drop 4 of them it in the


design area.
Select the first component –
CompareCharValue1.
In the object inspector set the
value of the "Value" property of
the CompareCharValue1
component to "f" - this character
will be used to drive the robot
Forward (f):

Connect the "Out" output pin of the


AnalogValue2 component to the "In" input pin
of the RampToValue2 component:

Select the first component –


CompareCharValue2.
In the object inspector set
the value of the "Value"
property of the
CompareCharValue2
component to "r" - this
character will be used to
drive the robot to Turn
Right (r):
To decode the 4 character commands (Left, Right,
Forward, and Back) from the Infrared Receiver,
we can use 4 Compare Char Value components:

38 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 7 / 11
Select the first component - CompareCharValue3. Connect the "Out" output pin of the
In the object inspector set the value of the "Value" CompareCharValue1 component to the "In" input
property of the CompareCharValue3 component pin of the "Elements.Set Value State1" element of
to "b" - this character will be used to drive the the AnalogValue2 component:
robot Backward (b):

Connect the "Out" output pin of the


Select the first component - CompareCharValue4. CompareCharValue2 component to the "In" input
In the object inspector set the value of the "Value" pin of the "Elements.Set Value State2" element of
property of the CompareCharValue4 component the AnalogValue1 component:
to "l" - this character will be used to drive the robot
Turn Left (l):

Connect the "Out" output pin of the


Connect the "Out" output pin of the CompareCharValue2 component to the "In" input
CompareCharValue1 component to the "In" input pin of the "Elements.Set Value State2" element of
pin of the "Elements.Set Value State1" element of the AnalogValue2 component:
the AnalogValue1 component:

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 39


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 8 / 11
Connect the "Out" output pin of the Connect the "Out" output pin of the
CompareCharValue3 component to the "In" input CompareCharValue4 component to the "In" input
pin of the "Elements.Set Value State3" element of pin of the "Elements.Set Value State4" element of
the AnalogValue1 component: the AnalogValue1 component:

Connect the "Out" output pin of the


CompareCharValue3 component to the "In" input Connect the "Out" output pin of the
pin of the "Elements.Set Value State3" element of CompareCharValue4 component to the "In" input
the AnalogValue2 component: pin of the "Elements.Set Value State4" element of
the AnalogValue2 component:

40 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 9 / 11
Connect the "Out" output pin of the "Serial[ 0 ]"
channel of the Arduino component to the "In"
input pin of the CompareCharValue1 component:

Connect the "Out" output pin of the "Serial[ 0 ]"


channel of the Arduino component to the "In"
input pin of the CompareCharValue2 component:

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 41


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 10 / 11
Connect the "Out" output pin of the "Serial[ 0 ]"
channel of the Arduino component to the "In"
input pin of the CompareCharValue3 component:

Connect the "Out" output pin of the "Serial[ 0 ]"


channel of the Arduino component to the "In"
input pin of the CompareCharValue4 component:

Here is how the complete Visuino diagram


looks like:

42 Issue Nr 2 2017 BLAISE PASCAL MAGAZINE


BLUETOOTH CONTROLLED ROBOT WITH PART 1 - +
VISUINO AND DELPHI PAGE 11 / 11
Make sure that the HC-06 Bluetooth module is The Sensor Shield on top of the
removed from the robot, otherwise, you will not Arduino board of the robot has dedicated
be able to upload the Arduino code. connector for the HC-06 that has the same signals:
Connect the Robots Arduino board to the
computer with a USB cable.
In the Arduino IDE select the Arduino/Genuino
Uno as a board, and select the Arduino's Com
port.
Click on the button to compile and
upload the code:

Insert the HC-06 module matching the pins.


Please note that the TX pin on the shield is
connected to the RXD pin of the HC-06 and the RX
pin is connected to TXD. This is normal, as the
transmission pin on the Arduino should connect to
receiving pin on the module and vice versa. +
should connect to VCC, and – to GND:

The robot is ready to use.


The Robot is programmed. Now we can install the
HC-06 module, and the Robot will be ready to In this article, you have learned how to
control. program Arduino controlled Robot for remote
The module has 4 pins – RXD, TXD, GND, and control over Bluetooth.
VCC:
IN THE NEXT ARTICLE, WE WILL
SHOW YOU HOW YOU CAN WRITE
AN ANDROID APP TO CONTROL
THE ROBOT FROM DELPHI.

Some important links:


Here is where is the robot from:
http://www.elegoo.com/
they sell it from Amazon
https://www.amazon.com/gp/product/B01DPH0SWY
/ref=od_aui_detailpages00?ie=UTF8&psc=1
They have also posted detailed instructions
on how to assemble the robot here:
https://www.instructables.com/id/
Assemble-Elegoo-Smart-Car-Robot-Kit/

Issue Nr 2 2017 BLAISE PASCAL MAGAZINE 43


TOKIO SUPERTRAIN BY っ, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=370468

V. 5.00
- New!
Native BSON, JSON, MessagePack, YAML and XML. - Native improved XSD importer
Convert forth and back via unified object for generating marshal
notation object trees! able Delphi objects from XML schemas.
- New! - High speed, unified database access
Marshal Delphi objects to and from BSON, JSON, (35+ supported database APIs) with connection
MessagePack, YAML and XML. pooling, metadata and data caching on all tiers
- New! - Multi head access to the application server,
High performance and simple Spring Boot inspired REST via REST/AJAX, native binary, Publish/Subscribe, SOAP,
with full Delphi object support. XML, RTMP from web browsers, embedded devices,
- New! linked application servers, PCs, mobile devices, Java
High performance HTTP.SYS server transport for REST. systems and many more clients
- New! - Full FastCGI hosting support.
High quality pronouncable password Host PHP/Ruby/Perl/Python applications in kbmMW!
generator framework! - Native AMQP support ( Advanced Message Queuing
- New! Protocol) with AMQP 0.91 client side gateway
High quality random functions for cryptographic use! support and sample.
- Many improvements - Fully end 2 end secure brandable Remote Desktop
– Improved log framework with backup rollover and with near REALTIME HD video, 8 monitor support,
LogFormatter, REST CORS support, custom enum texture detection, compression and clipboard sharing.
support for object marshalling, improved scheduler - Bundled kbmMemTable Professional
features, anonymous function callback in WIB, which is the fastest and most feature rich in
improved data resolver features and more. memory table for Embarcadero products.

- Multimonitor remote desktop V5 (VCL and FMX) kbmMemTable is the fastest and most feature rich in
- Rad studio and Delphi 2009 to memory table for Embarcadero products.
Latest 10.1 BERLIN support - Easily supports large datasets with millions of records
- Win32, Win64, Android, IOS 32, IOS 64 - Easy data streaming support
and OSX client and server support! - Optional to use native SQL engine
- Native PHP, Java, OCX, ANSI C, C#, - Supports nested transactions and undo
Apache Flex client support! - Native and fast build in M/D, aggregation /grouping,
- High performance LZ4 and Jpeg compression range selection features
- Native high performance 100% developer defined app - Advanced indexing features for extreme performance
server with support for loadbalancing and failover

COMPONENTS
DEVELOPERS 4 DX
EESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI /
C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON WIN32 / 64, .NET, LINUX, UNIX
MAINFRAMES, MINIS, EMBEDDED DEVICES, SMART PHONES AND TABLETS.

Potrebbero piacerti anche